From 352d7bb035c0d260330683e874c660709dd66095 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Tue, 10 Dec 2024 14:38:38 +0100 Subject: [PATCH] Enable System credential extension on release + translations (#3706) Task/Issue URL: https://app.asana.com/0/1201645688934642/1207540610577903/f Tech Design URL: CC: **Description**: Enables the app to be a system level credential provider on the main app target + translations and final ship review feedback --- .../AutofillCredentialProvider.entitlements | 12 +- .../CredentialProviderActivatedView.swift | 8 +- ...CredentialProviderActivatedViewModel.swift | 3 + ...dentialProviderListItemTableViewCell.swift | 101 ++--- ...CredentialProviderListViewController.swift | 82 +++- .../CredentialProviderListViewModel.swift | 21 +- .../CredentialProviderListDetailsView.swift | 359 ++++++++++++++++++ ...ialProviderListDetailsViewController.swift | 125 ++++++ ...edentialProviderListDetailsViewModel.swift | 192 ++++++++++ .../CredentialProviderViewController.swift | 35 +- .../Contents.json | 38 ++ .../Copy-24.imageset/Contents.json | 16 + .../Copy-24.imageset/Copy-24.pdf | Bin 0 -> 2550 bytes .../Eye-24.imageset/Contents.json | 16 + .../Eye-24.imageset/Eye-24.pdf | Bin 0 -> 1723 bytes .../Eye-Closed-24.imageset/Contents.json | 16 + .../Eye-Closed-24.imageset/Eye-Closed-24.pdf | Bin 0 -> 3300 bytes .../Logo.imageset/Contents.json | 12 + .../Logo.imageset/Dax_default.pdf | Bin 0 -> 9546 bytes .../Resources/UserText.swift | 35 +- .../Shared/ActionMessageView.swift | 197 ++++++++++ .../Shared/ActionMessageView.xib | 76 ++++ .../Shared/FaviconHelper.swift | 87 +++++ .../Shared/NibLoading.swift | 34 ++ AutofillCredentialProvider/Info.plist | 7 + .../bg.lproj/InfoPlist.strings | 9 + .../bg.lproj/Localizable.strings | 96 +++++ .../cs.lproj/InfoPlist.strings | 9 + .../cs.lproj/Localizable.strings | 96 +++++ .../da.lproj/InfoPlist.strings | 9 + .../da.lproj/Localizable.strings | 96 +++++ .../de.lproj/InfoPlist.strings | 9 + .../de.lproj/Localizable.strings | 96 +++++ .../el.lproj/InfoPlist.strings | 9 + .../el.lproj/Localizable.strings | 96 +++++ .../es.lproj/InfoPlist.strings | 9 + .../es.lproj/Localizable.strings | 96 +++++ .../et.lproj/InfoPlist.strings | 9 + .../et.lproj/Localizable.strings | 96 +++++ .../fi.lproj/InfoPlist.strings | 9 + .../fi.lproj/Localizable.strings | 96 +++++ .../fr.lproj/InfoPlist.strings | 9 + .../fr.lproj/Localizable.strings | 96 +++++ .../hr.lproj/InfoPlist.strings | 9 + .../hr.lproj/Localizable.strings | 96 +++++ .../hu.lproj/InfoPlist.strings | 9 + .../hu.lproj/Localizable.strings | 96 +++++ .../it.lproj/InfoPlist.strings | 9 + .../it.lproj/Localizable.strings | 96 +++++ .../lt.lproj/InfoPlist.strings | 9 + .../lt.lproj/Localizable.strings | 96 +++++ .../lv.lproj/InfoPlist.strings | 9 + .../lv.lproj/Localizable.strings | 96 +++++ .../nb.lproj/InfoPlist.strings | 9 + .../nb.lproj/Localizable.strings | 96 +++++ .../nl.lproj/InfoPlist.strings | 9 + .../nl.lproj/Localizable.strings | 96 +++++ .../pl.lproj/InfoPlist.strings | 9 + .../pl.lproj/Localizable.strings | 96 +++++ .../pt.lproj/InfoPlist.strings | 9 + .../pt.lproj/Localizable.strings | 96 +++++ .../ro.lproj/InfoPlist.strings | 9 + .../ro.lproj/Localizable.strings | 96 +++++ .../ru.lproj/InfoPlist.strings | 9 + .../ru.lproj/Localizable.strings | 96 +++++ .../sk.lproj/InfoPlist.strings | 9 + .../sk.lproj/Localizable.strings | 96 +++++ .../sl.lproj/InfoPlist.strings | 9 + .../sl.lproj/Localizable.strings | 96 +++++ .../sv.lproj/InfoPlist.strings | 9 + .../sv.lproj/Localizable.strings | 96 +++++ .../tr.lproj/InfoPlist.strings | 9 + .../tr.lproj/Localizable.strings | 96 +++++ .../AutofillInterfaceEmailTruncator.swift | 4 +- {DuckDuckGo => Core}/PasswordHider.swift | 10 +- Core/PixelEvent.swift | 29 +- Core/UserDefaultsPropertyWrapper.swift | 1 + DuckDuckGo.xcodeproj/project.pbxproj | 175 ++++++++- DuckDuckGo/AppDelegate.swift | 10 +- DuckDuckGo/AutofillLoginPromptViewModel.swift | 1 + DuckDuckGo/AutofillUsageMonitor.swift | 17 +- DuckDuckGo/DuckDuckGo.entitlements | 2 + ...AutofillInterfaceEmailTruncatorTests.swift | 1 + adhocExportOptions.plist | 3 + appStoreExportOptions.plist | 2 + fastlane/Matchfile | 8 +- 86 files changed, 4122 insertions(+), 133 deletions(-) create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsView.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewController.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewModel.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillCellSelectedBackground.colorset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Copy-24.pdf create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Eye-24.pdf create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Eye-Closed-24.pdf create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Dax_default.pdf create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.xib create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/FaviconHelper.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/NibLoading.swift create mode 100644 AutofillCredentialProvider/bg.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/bg.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/cs.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/cs.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/da.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/da.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/de.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/de.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/el.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/el.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/es.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/es.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/et.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/et.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/fi.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/fi.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/fr.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/fr.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/hr.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/hr.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/hu.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/hu.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/it.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/it.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/lt.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/lt.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/lv.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/lv.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/nb.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/nb.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/nl.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/nl.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/pl.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/pl.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/pt.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/pt.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/ro.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/ro.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/ru.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/ru.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/sk.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/sk.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/sl.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/sl.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/sv.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/sv.lproj/Localizable.strings create mode 100644 AutofillCredentialProvider/tr.lproj/InfoPlist.strings create mode 100644 AutofillCredentialProvider/tr.lproj/Localizable.strings rename {DuckDuckGo => Core}/AutofillInterfaceEmailTruncator.swift (91%) rename {DuckDuckGo => Core}/PasswordHider.swift (83%) diff --git a/AutofillCredentialProvider/AutofillCredentialProvider.entitlements b/AutofillCredentialProvider/AutofillCredentialProvider.entitlements index b50ab55a01..f7959669d1 100644 --- a/AutofillCredentialProvider/AutofillCredentialProvider.entitlements +++ b/AutofillCredentialProvider/AutofillCredentialProvider.entitlements @@ -3,6 +3,16 @@ 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 index 1f6a32564b..fe36f9e3b2 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift @@ -49,12 +49,6 @@ struct CredentialProviderActivatedView: View { .padding(.top, 16) .multilineTextAlignment(.center) - Text(UserText.credentialProviderActivatedSubtitle) - .daxBodyRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - .padding(.top, 8) - .multilineTextAlignment(.center) - Spacer() Button { @@ -67,7 +61,7 @@ struct CredentialProviderActivatedView: View { } .padding(.horizontal, 24) - .navigationBarItems(trailing: Button(UserText.actionCancel) { + .navigationBarItems(trailing: Button(UserText.actionDone) { viewModel.dismiss() }) } diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift index 759683d555..bdf4eba455 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift @@ -18,6 +18,7 @@ // import Foundation +import Core struct CredentialProviderActivatedViewModel { @@ -30,10 +31,12 @@ struct CredentialProviderActivatedViewModel { } 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 index 2e659cc21e..a0701ec178 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift @@ -24,7 +24,9 @@ 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) @@ -64,7 +66,23 @@ class CredentialProviderListItemTableViewCell: UITableViewCell { 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() @@ -85,22 +103,29 @@ class CredentialProviderListItemTableViewCell: UITableViewCell { 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: margins.trailingAnchor), + contentStackView.trailingAnchor.constraint(equalTo: disclosureButton.leadingAnchor, constant: -12), contentStackView.topAnchor.constraint(equalTo: margins.topAnchor), contentStackView.bottomAnchor.constraint(equalTo: margins.bottomAnchor) ]) @@ -109,7 +134,7 @@ class CredentialProviderListItemTableViewCell: UITableViewCell { private func setupContentView(with item: AutofillLoginItem) { titleLabel.text = item.title subtitleLabel.text = item.subtitle - iconImageView.image = loadImageFromCache(forDomain: item.account.domain) + iconImageView.image = FaviconHelper.loadImageFromCache(forDomain: item.account.domain, preferredFakeFaviconLetters: item.preferredFaviconLetters) } override func layoutSubviews() { @@ -118,67 +143,9 @@ class CredentialProviderListItemTableViewCell: UITableViewCell { separatorInset = UIEdgeInsets(top: 0, left: contentView.layoutMargins.left + textStackView.frame.origin.x, bottom: 0, right: 0) } - - - private func loadImageFromCache(forDomain domain: 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 Kingisher 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 = createFakeFavicon(forDomain: domain, size: 32, backgroundColor: UIColor.forDomain(domain), preferredFakeFaviconLetters: item?.preferredFaviconLetters) - return image - } - - return UIImage(data: data)?.toSRGB() - } - - private 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 prefferedPrefix = preferredFakeFaviconLetters?.droppingWwwPrefix().prefix(letterCount).capitalized { - label.text = prefferedPrefix - } else { - label.text = item?.preferredFaviconLetters.capitalized ?? "#" - } - - context.translateBy(x: padding, y: padding) - - label.layer.draw(in: context) - } - - return icon.withRenderingMode(.alwaysOriginal) + @objc private func handleDisclosureButtonTap() { + disclosureButtonTapped?() } - + } diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift index e0d0e8eaff..6094773e0c 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift @@ -21,13 +21,17 @@ 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 = [] @@ -80,21 +84,35 @@ final class CredentialProviderListViewController: UIViewController { 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) + 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) - authenticate() + 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) { @@ -106,6 +124,10 @@ final class CredentialProviderListViewController: UIViewController { title = UserText.credentialProviderListTitle + if let itemPrompt = viewModel.serviceIdentifierPromptLabel { + navigationItem.prompt = itemPrompt + } + let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped)) navigationItem.rightBarButtonItem = doneItem @@ -117,6 +139,8 @@ final class CredentialProviderListViewController: UIViewController { registerForKeyboardNotifications() navigationItem.searchController = searchController + + Pixel.fire(pixel: .autofillExtensionPasswordsOpened) } override func viewWillDisappear(_ animated: Bool) { @@ -156,19 +180,17 @@ final class CredentialProviderListViewController: UIViewController { } private func authenticate() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in - self?.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) + 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) } } } @@ -270,6 +292,7 @@ final class CredentialProviderListViewController: UIViewController { @objc private func doneTapped() { onDismiss() + Pixel.fire(pixel: .autofillExtensionPasswordsDismissed) } } @@ -293,6 +316,11 @@ extension CredentialProviderListViewController: UITableViewDataSource { } 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() @@ -312,6 +340,14 @@ extension CredentialProviderListViewController: UITableViewDataSource { 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 { @@ -321,9 +357,14 @@ extension CredentialProviderListViewController: UITableViewDelegate { switch viewModel.sections[indexPath.section] { case .suggestions(_, items: let items), .credentials(_, let items): let item = items[indexPath.row] - onRowSelected(item) + if shouldProvideTextToInsert { + presentDetailsForCredentials(item: item) + } else { + onRowSelected(item) + Pixel.fire(pixel: .autofillExtensionPasswordSelected) + } default: - break + return } } @@ -378,5 +419,12 @@ extension CredentialProviderListViewController { (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 index 40664022a6..1b0ab0c601 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift @@ -36,7 +36,13 @@ final class CredentialProviderListViewModel: ObservableObject { case searchingNoResults } - var isSearching: Bool = false + var isSearching: Bool = false { + didSet { + if oldValue != isSearching, isSearching { + Pixel.fire(pixel: .autofillExtensionPasswordsSearch) + } + } + } var authenticationNotRequired = false private let serviceIdentifiers: [ASCredentialServiceIdentifier] @@ -45,7 +51,7 @@ final class CredentialProviderListViewModel: ObservableObject { private var accounts = [SecureVaultModels.WebsiteAccount]() private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]() private var cancellables: Set = [] - private let tld: TLD = TLD() + private let tld: TLD private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher() private let autofillDomainNameUrlSort = AutofillDomainNameUrlSort() @@ -55,6 +61,13 @@ final class CredentialProviderListViewModel: ObservableObject { 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 { @@ -64,10 +77,12 @@ final class CredentialProviderListViewModel: ObservableObject { init(serviceIdentifiers: [ASCredentialServiceIdentifier], secureVault: (any AutofillSecureVault)?, - credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging) { + credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging, + tld: TLD) { self.serviceIdentifiers = serviceIdentifiers self.secureVault = secureVault self.credentialIdentityStoreManager = credentialIdentityStoreManager + self.tld = tld if let count = getAccountsCount() { authenticationNotRequired = count == 0 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 index 397b1a5781..35f730611e 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift @@ -112,26 +112,39 @@ class CredentialProviderViewController: ASCredentialProviderViewController { Task { 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], returnString: Bool = false) { + 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 - } + 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) + let credential = self.vaultCredentialManager.fetchCredential(for: item.account) - self.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil) + self.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil) - }, onDismiss: { + }, 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)) }) @@ -150,20 +163,24 @@ class CredentialProviderViewController: ASCredentialProviderViewController { 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) { 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/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 0000000000000000000000000000000000000000..56407ef84b8b52a3a4b83724fd3953318ecbeb66 GIT binary patch literal 2550 zcmZuzO^@3)5WVwP@UlP>AT&Qk5(ok$yG>EFMYm3GK@N^q**K7`#&U|ZzrK%@IOOhH z2V?vwzL|M5RZpq|-6O&I8ek1}-7?8)NOB_il+K5)Kj zGB%(UT1J(FT={E;#8RQ2xx!Mxu>nKj>vFsi^^7#D;Mjn~ z+<>ttiozyoB@c@WIFm~!#RADxbW(18Po|m^Nb!wHdPwS(^EOefaUWPLX3B3_9viR; zvc4&cz_A#G5nceSz_*N73gBCN*Mp_V4d7m6$mf&R_w9VkJxZgOHJ z3JW(Fsa_ZAnomD9B&ukY#AV0xB#~Mx+R@yB9h{6WU`2lw7ojDg~JCjrQHT zesPs}py!3CQgeQO!H(=tmPYhCf6K5=b9ou!D#}PR+!Bo0zIk&zPUo3__=s7LXY<#; ze-C_fySpD&;P>Hfzk56V;2$o`H%&oRt1xYcdHR0phnc6ZiS0^vJsr=(5gya0L;-Ji zCt%#IQ1ii&Ao>v5{JOtGs61t`<;BnS^c{QEllW&K4O91kDJ3|Q!%iysLxh%-F|DU8@hkVa2EhEzn>4>oFz5mTV^X}2GT z6HC?mfjN5WV|X%%xI`)WqX&OI0Phr3e9{Y`Ik&LN;v|braa6sPO99Z-LUN>I7=4;ZJNCyI8)}yuiwdX;$F0^p zgXjt;jc|G-v(y<#dMa-z-00JI>NX}Q5aiMc3Ya{gk|B`Ma?V0~6TrxbR!rGS;+`BM zVOf+H)C*d6Hp@&mrk5=@oFz zRZilCTw~|Hime;w%rr#ii4%s)IEj!ukp_`UFb$~kb=!7B57%F5V9_eS{Ql8Ed0E{y z6Y#NFt*f){5w3G11PQ_dktE2wnQ7Vd-NUwSdI%$NIngb;c4%7i80O&&URGPeKu^%l z)C^FXrsezfib92u!4hUai|&E$;7eGw4i4UYy7&tQ|7mLg literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..78b200d65baaf237c19c5af399f60cf67924f3a6 GIT binary patch literal 3300 zcmchaO>Z1U5QgvbEBX>CIe50K`df-35r-fIh~RLGI4tW)Vr1`vcY^}IK2Oc;&aQ1F zLJsU9YQIz6UG={8(RuXh#mh74OV^3>Zu9GJopVp0x@XV2VSQbZIdvTgg?RYjh2nj>vshR^9vVZMW`z`p;rOQAkPkGsF;u8vOQ7JQ5Tw!Q{}s zwU$#dG@)zad+4P`qplH7iHMF_F0nNQl2RH{5Ulp8h6ok7QF9CmbAUlxljdq)St&w7 zY=({iRD#K45Qy>-EB2BjoJX=XAA;F6@wH4&rRZgZa*U>k}phn25PLP6{oThJvMuNMc_w zRf8t5kbw$TT1%GVs2%k!jM6tP8md7lx3a&ni7Mq|V2#B$NYg&H+}J{`TPfm@^i_N* zK)!E9Ff@;9;1YvarI9q!F`%ktT&$824VRJ+-tswR&CSZsXP=;*f>h$9c8{eyv4Mz| z^=4PcV0UTn2n%}G(wy?q6QC7RN_9-G2buZh zB|!s8sU`UO_XgES{^F6q#F#cw626xR3zI|>3PvhdvcW8KDV|;fG7)aUR=?BCs(ma_>X8^6$iWF3H7<)X}UV+$065i7;PO+JX6Eyd9 zRx2EOjruI63b#*69hsP+Ro8l?6Q;|l9e0V_xq#H9z)vmBMN|t$XteFI#?#pa1%OaEn*Vcf%g|VYs|nKHq$FZ)}d;d#GwWK*!bLq>Ev@`Ea`$ zw$9$p7yIMRH|yQ7=0uRQR7 z3vzN2WBBAG3vfGMcnth}xm#Xu-re2$@pgFIc~`nAeEZi?oMbBS{zvMME4Q8@^t=lx z)9L|KAi{q0M&4&ceFqAj_uK*6-7c@LhuhB7`}PHNJl<|@h9~av)#a0kl*PqnvvU^k eNbutI)!zf&kN+Q8-tNY5f-kju^yup!Uw#6{W}b2Y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..46ebb1057c8b4bdcf9e318f5a81f2f0a292f57a8 GIT binary patch literal 9546 zcmbu_OOGViaRuOg{fgWWkPJ*s-prdXKoB54Vc3Rf(hC8C7R_dh9D&nKPd5!pzuw=u zRb5%b8GxkJ3o5VEK0dv~US_Qx_z+p=`)bR5TR8HVom^})bpts`9>%eoHZ zOQbp-gi(9LKUP{m_l$ zv6gx6$6a)L7^mYrmA)>!aGZ~Z4^y#P9s6lnyKyx)KiK zGH$k5j^i}uCeIgcvNF_u%iMn3hQ&2Ax1ZN_jj#-3sU>s!GHg>WTXs58u`s}o0qAMSC=K)N=|7i^(<@FV}-Jov38>i(esu| zOs><{jy7{Ni`aGUSIgc;#kCIWoGtocDP_pGewxadI}BSL2fBXOI=6>!4}U$}AO8EF z@QdMS$HhT@-Y@F0uXUZB-dSeK;1=_7D77hr&x}1~FGs3aCK@~T>$J{mx5WtuCLWG! z-!FaduG?{3wtkIEEVZBAd83Nx_vjmt8UjMFgn%*Hay#5->fYy4|1lzQC8flGAr z(f=1O>v5g8va-u@+DfJF@wiU?GG-oErN?&mt#Z65zVGK{Yj&Dy{3#NDV;rk6!&Xgf zPFBQFq;**n0sV3A`%?Odrhc5o`QiWcYd0Wgc894#4eJKC$K^lL!7a57nuE zs?>kQnvO9p{mh4dHp)4)n3!g_Sj8t*EbJ+ON48z+m6S`bwV%q=y;zQLVc@`5GJzqY zXtX*1^T?53O&euitq7qgKc;EXXYuX%VuF%$e9=YG9Lh;jz z*Ru1yn+yKiKZhubKxQz0_47Vhf+UOB=w8eLeeh4`S5g+5w~=q>TC2L(vD<54&dGqSiio58t_3dF7R>0QJb%QU@%MHickI4o5z84XM$q6Mx zcezNfZgN@gN_MsJw}%3vdZ575d3DdP;(u3C78|4rDHHyTaM4JfIxPiIkx3Mzth~#D zve6GX*o6{G>9?hgn+6Fg_#7lGT=%kZrtZRdnKw|K)j%7!mmcf10*R|_FS4eJ=L24( zR~JDCP7A&zJ74+A^$vIUU!VDor?9$1Slfx-!Tcoyni!g2vA#3kYlQAk)D@<;^R7>R zeERImr<pYAt zN98r?6`MpcXTViuq@8Wfa;iVsi3xY{c`0zXFM9QQ-~UR8%$#) zL*;sMgZbMUw6)`X*3Cl$zx52IL?Xrgf2^Y{3+QtRY zIkzcN=dk!kw@EKUL2$bgasW>HZgWAYWeb%P#pW(tjdWS0{T@OhccFYl)Fd;)(G7E( zcu~@){I{4p9|7hHt0M$Uyp*V}L(Ve835hlki~>v;8j(|O_*R%;+0+AVT*0AHgbi0f z>#c@gR4}ht!HCMzyZT1KE5)=UGpc$A3g*Id?MY*dX_ogRn+wgg*@C3FdSs}ilQvY{ zhT$Z1k=&fBw_(IIjT=->{nkzGFwu0hiv>p0lY$C^v|cd|>s&If_LLPXtwm`uB;`oX zxKl(B8vSpeichUP&RjJSCe@XRkw&yPGTFSOphCc>nxbx9xr&7v6{W6GO*2P2qXTv> z>dbLn<~9<+tLU-WYagewnxiQodTPLku_2t=X#zuNb)q6dWg0_{)ENV0+%n9AbQLq1 z(EO4*crej4aGqFc7?gtH)*^UVdo@Y7$EP4cjur>KgsiL-CyH0@!Z>XcewPyKS1}?1 zy^XwfOfVE4=uqFn!uC$CO)o2p|3yOM6 zL<#kX0C4qvT>RsCwWYMF+~~Z4WtHBk2BZ2^z*q+4B9j(Wo-DvHbgSS`rD}aPpm`_n zRnM*R##985Vu;X@`r;`u5bjVRRaHV1qG7HQSwtXGZnJBI_JU)Cvm8~r?+$Ubc?@>U zlU>?6Tyj#$8M(r5%v*?a=vjSLo0G4-yTzKMTfa?wtp$YR7#SM00-SD5b&4;xa9CZ& z95U6Kl#tni(dj1fIi&0pFM$epv;}DRAW*@Ca&XEQvy9Y3g$B#<VN0-4R?~q0_a%2RFf-N_+oSGwR{f8!FKd0bu63^)1M$&`o zRxwif5!9umiO%IEfL)V%crsuQZCB=IZ_=W?vm>b~^xjoDR)PanWT_Wuw#G+c7Pz+# zGc|Nen`r>}ZJ)81Xmos1619{w1-_*$KEr7MUHWV3hY5=8O{#Y{!qpU{I&6w3vXqwb zGp31qDOaJc^Kzxg0Ba+7*a6oOe<>gPN*UgBI`6~l0sexH(8arm)F{@kDGsM_5^XtU zoSQ5>hnuEBF$AxW>ju3PYTP0&x7LOXBf?s6j3KbjnynyD!^jQU*n1eR4dpKKw##%w zkD*{S&42W0IFWF1Xe6q_=+Vk3kR;(xj$gbcvDBLeSy8Y2M?0Gf!qXEUjh z3yB-H4bCrv5*Prbju8&6C#by&cTnq$=|N0l{nu9BQ=^34Z0haw+!>>DN^<=PGV*0cy~hU}oSImamN(HzDqf#;?N&W2Vb@CZ}I3y83T z94oA6tIdUz6q`$W&~$4S7FRw!%-l0HfIm0t2f32IA>%BeHjGgzd z&NLmC1~6)Dpn!lBnpShP3bMH&dINMpwm7E_S7<=(3q;^2X{Vi*Z*obB=CC<0uE(-u zn~ade5%te;b$%@=3q)$wt$_z^uCi}(8}3n8Rc=B@?U1&wn@9yowHHxLt%csA=^{*3 z4D)!A)9ZETcAS)ZNEtlq9l!k7Lc+(L)CxZz5`oM>PV}!qA7T`KtrkURg26SQj|;XK ziM=_ds!19(gI|P~x(UcsB*in|BQ-|_ZY9Vdv#QIRxvd;f3(t)z+g!=IkBwEgs_ypn z0n@RYKCiAqQV91@DB(Or=t)q}RQd(nnsoNp1ryC7y!q~hOr_NvXMN9Yul1ZSP)LlX zrvP`}R`y&T&hI9iN;#t%2n$Ck(|Ne6NgxZ9XnFXm!9+tUg6Im(Xt|f3rIU0sgp&da z|IfE79u1b~Nbz3eRRU%6b+NCmI4ihQ++*wf2a@GFjFUK_0L|*qA^dgF1~AAdy%_*zk$; zmdVmJLsZDD0w~(oyMRREsUk=zik(t3G}IbU8VQhrlJiJO+FhZ03hOC1Je{1En$1Yq z%?T%zgdq(HTjz=TV2rPRJk2_8n6?k)0tVWM*0Lj2^Dq@0?Ma-RJPg}xZ$-5-w;_*I zj&;*B0u*zb9RN@un8P|ypOkNN(B#R)^QxZWg3n+xdKXrfmFPZQl-qm zjP&FX;6eT0YQRwe)8G#iUt6;!z9x^WtXs*UfNb^>3GlnZFZrvPJzthY1R7H$;qaay zUN{q9z^K?L%tHa1c1f6(K1NDQP>Bdy-sS}~n;7mQlyVD+hu62ycfO5DUslC?@;&%H zuHAy_xzK;9J^an%;iWF}#}B{!^J=5fiKENIb(Foo^2hci#nYtx&+OL+1uH~PCrH9O z36Z&$%f07=@_}^Ck{98=4lV5BeWm^B>h(VBN+|E0caT)}r7lO=SGtJybyC5ekNms} z6u7K+dAZNHh+=|W%neUB*VJZKZr{GapM_BMq7G!!_n9#MUAV6kbzH{oEA3BLulHG3 zLh5+m4wv)oD_unUI=A`J#pxdVD*Myr0^zehZ<((Tmo_-kf{HaE}?`ox7 z@$s_Ze)W%To^HN-_~z}YKfby9>p>U$a57Z>tKpPp1@ZY0khG`n{w(rS0*BAvl_-6= zw?*N!Z;KQ(Z1>hEWvJu7CE8HxpNepgi|Fai&8zS3-W;N~fB%@$e)IV7`tG;gZ(e=< r+mq^Ee(~_|)I}}L3@^WY^^ZGuY`-PCdGpjRt+_HEKKS61KYsfEd2wwl literal 0 HcmV?d00001 diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift index 74922adc7a..9068764c55 100644 --- a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift +++ b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift @@ -21,18 +21,18 @@ import Foundation final class UserText { - static let credentialProviderActivatedTitle = NSLocalizedString("credential.provider.activated.title", value: "Autofill activated!", comment: "The title of the screen confirming DuckDuckGo can now be used for autofilling passwords") - - static let credentialProviderActivatedSubtitle = NSLocalizedString("credential.provider.activated.subtitle", value: "You can now autofill your DuckDuckGo passwords from anywhere.", comment: "The subtitle of the screen confirming DuckDuckGo can now be used for autofilling passwords") + 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 actionCancel = NSLocalizedString("action.button.cancel", value: "Cancel", comment: "Cancel button title") + static let actionClose = NSLocalizedString("action.button.close", value: "Close", comment: "Close button title") - static let actionClose = NSLocalizedString("action.button.cancel", 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") @@ -58,6 +58,29 @@ final class UserText { 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.providerdevice.type.default", value: "device", comment: "Default string used if users device is not iPhone or 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/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/Info.plist b/AutofillCredentialProvider/Info.plist index cb9ba8ea30..c00abcf6dc 100644 --- a/AutofillCredentialProvider/Info.plist +++ b/AutofillCredentialProvider/Info.plist @@ -12,6 +12,13 @@ $(PRODUCT_MODULE_NAME).CredentialProviderViewController ASCredentialProviderExtensionShowsConfigurationUI + ASCredentialProviderExtensionCapabilities + + ProvidesPasswords + + ProvidesTextToInsert + + NSExtensionPointIdentifier com.apple.authentication-services-credential-provider-ui 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/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/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/PixelEvent.swift b/Core/PixelEvent.swift index 4c9d909115..29eadee0f7 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -312,6 +312,8 @@ extension Pixel { case autofillOnboardedUser case autofillToggledOn case autofillToggledOff + case autofillExtensionToggledOn + case autofillExtensionToggledOff case autofillLoginsStacked case autofillManagementOpened @@ -332,7 +334,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 @@ -1203,6 +1216,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" @@ -1232,6 +1247,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)" 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/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7257f3f214..fce5b52b68 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -175,7 +175,6 @@ 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 */; }; @@ -919,6 +918,15 @@ 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 */; }; @@ -945,6 +953,8 @@ 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 */; }; @@ -1205,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 */; }; @@ -1542,7 +1551,6 @@ 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 = ""; }; @@ -2707,14 +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 = ""; }; @@ -2722,23 +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 = ""; }; @@ -2748,15 +2784,30 @@ 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 = ""; }; @@ -2776,11 +2827,19 @@ 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; }; @@ -2791,6 +2850,12 @@ 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 = ""; }; CB1143DD2AF6D4B600C1CCD3 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -3076,7 +3141,6 @@ F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Onboarding.xcassets; sourceTree = ""; }; F4B0B78B252CAFF700830156 /* OnboardingWidgetsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingWidgetsViewController.swift; sourceTree = ""; }; F4B0B795252CB35700830156 /* OnboardingWidgetsDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingWidgetsDetailsViewController.swift; sourceTree = ""; }; - F4C9FBF428340DDA002281CC /* AutofillInterfaceEmailTruncator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceEmailTruncator.swift; sourceTree = ""; }; F4CE6D1A257EA33C00D0A6AA /* FireButtonAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireButtonAnimator.swift; sourceTree = ""; }; F4D7220F26F29A70007D6193 /* BookmarkDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDetailsCell.swift; sourceTree = ""; }; F4D9C4F925117A0F00814B71 /* HomeMessageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageStorage.swift; sourceTree = ""; }; @@ -3721,7 +3785,6 @@ 31951E9328230D8900CAF535 /* Shared */ = { isa = PBXGroup; children = ( - F4C9FBF428340DDA002281CC /* AutofillInterfaceEmailTruncator.swift */, 31A42563285A09E800049386 /* FaviconView.swift */, 31A42565285A0A6300049386 /* FaviconViewModel.swift */, C160544029D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift */, @@ -5329,6 +5392,16 @@ name = AutofillLoginUI; sourceTree = ""; }; + C1C1FF442D085A280017ACCE /* CredentialProviderListDetails */ = { + isa = PBXGroup; + children = ( + C1C1FF412D085A280017ACCE /* CredentialProviderListDetailsView.swift */, + C1C1FF422D085A280017ACCE /* CredentialProviderListDetailsViewController.swift */, + C1C1FF432D085A280017ACCE /* CredentialProviderListDetailsViewModel.swift */, + ); + path = CredentialProviderListDetails; + sourceTree = ""; + }; C1CAA3D52A630ECB00807703 /* EmailSignup */ = { isa = PBXGroup; children = ( @@ -5355,6 +5428,7 @@ C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */, C1CAAA682CF8B8C400C37EE6 /* CredentialProviderActivation */, C1CAAA862CF9FFBE00C37EE6 /* CredentialProviderList */, + C1C1FF442D085A280017ACCE /* CredentialProviderListDetails */, C1CAAA6E2CF8BBC900C37EE6 /* Extensions */, C1CAAA742CF8BDC400C37EE6 /* Resources */, C1CAAAA42CFCBD6B00C37EE6 /* Shared */, @@ -5398,6 +5472,8 @@ C1CAAA962CFCAB8000C37EE6 /* Autofill */ = { isa = PBXGroup; children = ( + C1C1FF482D085A4C0017ACCE /* AutofillInterfaceEmailTruncator.swift */, + C1C1FF492D085A4C0017ACCE /* PasswordHider.swift */, C19D90D02CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift */, C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */, C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */, @@ -5416,7 +5492,11 @@ C1CAAAA42CFCBD6B00C37EE6 /* Shared */ = { isa = PBXGroup; children = ( + C1C1FF4C2D085AD70017ACCE /* ActionMessageView.swift */, + C1C1FF4D2D085AD70017ACCE /* ActionMessageView.xib */, + C1C1FF4E2D085AD70017ACCE /* FaviconHelper.swift */, C1CAAAA52CFCBD7900C37EE6 /* LockScreenView.swift */, + C1C1FF4F2D085AD70017ACCE /* NibLoading.swift */, C1CAAAA92CFCC13E00C37EE6 /* SecureVaultReporter.swift */, C13C076B2D00A6B7006386CF /* VaultCredentialManager.swift */, ); @@ -5439,6 +5519,8 @@ C1CAAA6D2CF8BBBC00C37EE6 /* CredentialProvider */, C1CAAA672CF8B74200C37EE6 /* AutofillCredentialProviderAlpha.entitlements */, C1EF5B2A2CC0457B002980E6 /* Info.plist */, + C1E4E9A72D0861AD00AA39AF /* Localizable.strings */, + C1E4E9A42D0861AD00AA39AF /* InfoPlist.strings */, C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */, ); path = AutofillCredentialProvider; @@ -6633,7 +6715,6 @@ C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */, C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */, C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */, - 319A370F28299A850079FBCE /* PasswordHider.swift */, 31C70B5428045E3500FB6AD1 /* SecureVaultReporter.swift */, C1AFFC4B2B8773060060448E /* AuthConfirmation */, F407605328131910006B1E0B /* AutofillLoginUI */, @@ -7396,6 +7477,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1C1FF502D085AD70017ACCE /* ActionMessageView.xib in Resources */, + C1E4E9A92D0861AD00AA39AF /* Localizable.strings in Resources */, + C1E4E9A62D0861AD00AA39AF /* InfoPlist.strings in Resources */, C1CAAA7A2CF8BE0200C37EE6 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7616,7 +7700,6 @@ 6F03CAFE2C32DD08004179A8 /* HomePageMessagesConfiguration.swift in Sources */, 851952682CE2522700578553 /* AutocompleteSuggestionsDataSource.swift in Sources */, EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */, - 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, 6FD1BAE42B87A107000C475C /* AdAttributionPixelReporter.swift in Sources */, @@ -7865,7 +7948,6 @@ 6F5CC0812C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift in Sources */, D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */, 31DE43C22C2C480D00F8C51F /* DuckPlayerFeaturePresentationView.swift in Sources */, - F4C9FBF528340DDA002281CC /* AutofillInterfaceEmailTruncator.swift in Sources */, 1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */, 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */, CB4FA44E2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift in Sources */, @@ -8536,8 +8618,14 @@ C1CAAA9E2CFCB78700C37EE6 /* UIColorExtension.swift in Sources */, C1CAAA8A2CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift in Sources */, C1CAAAA02CFCB7C200C37EE6 /* UImageExtension.swift in Sources */, + C1C1FF512D085AD70017ACCE /* FaviconHelper.swift in Sources */, + C1C1FF522D085AD70017ACCE /* ActionMessageView.swift in Sources */, + C1C1FF532D085AD70017ACCE /* NibLoading.swift in Sources */, C177D9F62CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift in Sources */, C1CAAA882CF9FFE100C37EE6 /* CredentialProviderListViewController.swift in Sources */, + C1C1FF452D085A280017ACCE /* CredentialProviderListDetailsView.swift in Sources */, + C1C1FF462D085A280017ACCE /* CredentialProviderListDetailsViewController.swift in Sources */, + C1C1FF472D085A280017ACCE /* CredentialProviderListDetailsViewModel.swift in Sources */, C1CAAAAA2CFCC13E00C37EE6 /* SecureVaultReporter.swift in Sources */, C1CAAAA82CFCBE4800C37EE6 /* EmptySearchView.swift in Sources */, ); @@ -8577,6 +8665,8 @@ 85BDC3192436161C0053DB07 /* LoginFormDetectionUserScript.swift in Sources */, 37DF000A29F9C416002B7D3E /* SyncMetadataDatabase.swift in Sources */, F143C3291E4A9A0E00CFDE3A /* URLExtension.swift in Sources */, + C1C1FF4A2D085A4C0017ACCE /* AutofillInterfaceEmailTruncator.swift in Sources */, + C1C1FF4B2D085A4C0017ACCE /* PasswordHider.swift in Sources */, 85E065BC2C73A54700D73E2A /* UsageSegmentationStorage.swift in Sources */, F143C3271E4A9A0E00CFDE3A /* Logger+Multiple.swift in Sources */, 85372447220DD103009D09CD /* UIKeyCommandExtension.swift in Sources */, @@ -9314,6 +9404,68 @@ name = OmniBar.xib; sourceTree = ""; }; + C1E4E9A42D0861AD00AA39AF /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + C1E4E9A52D0861AD00AA39AF /* bg */, + C164F9472D0861D600BAE88E /* cs */, + C18D7C412D08620D00FB3F87 /* da */, + C1DCF3502D0862330055F8B0 /* de */, + C174E08E2D08625300ACE1AF /* el */, + C1F883372D08627900DFF79A /* es */, + C1CB0AA52D08629800335287 /* et */, + C132F5A32D0862B8000C81D0 /* fi */, + C129DF092D0862D7007AB046 /* fr */, + C1406D862D0862F30082CB50 /* hr */, + C1B783DB2D0863110071C53B /* hu */, + C1FEDCEA2D08633100BFBF3F /* it */, + C1A001092D08635100372C87 /* lt */, + C12854D82D08636E00C8353F /* lv */, + C163677A2D08638C001D1094 /* nb */, + C1FE93E52D0863AA009F8F5E /* nl */, + C1453E322D0863C80024449B /* pl */, + C180CAFA2D0863E500ADB0FE /* pt */, + C1B7BF522D08640B0024FF56 /* ro */, + C1193F602D08642900CB3239 /* ru */, + C1588FC42D08644800C9BE70 /* sk */, + C1E490582D08646400F86C5A /* sl */, + C11C4D302D08648100288E85 /* sv */, + C14D37D62D08649E00FCFC59 /* tr */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + C1E4E9A72D0861AD00AA39AF /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + C1E4E9A82D0861AD00AA39AF /* bg */, + C164F9482D0861D600BAE88E /* cs */, + C18D7C422D08620D00FB3F87 /* da */, + C1DCF3512D0862330055F8B0 /* de */, + C174E08F2D08625300ACE1AF /* el */, + C1F883382D08627900DFF79A /* es */, + C1CB0AA62D08629800335287 /* et */, + C132F5A42D0862B8000C81D0 /* fi */, + C129DF0A2D0862D7007AB046 /* fr */, + C1406D872D0862F30082CB50 /* hr */, + C1B783DC2D0863110071C53B /* hu */, + C1FEDCEB2D08633100BFBF3F /* it */, + C1A0010A2D08635100372C87 /* lt */, + C12854D92D08636E00C8353F /* lv */, + C163677B2D08638C001D1094 /* nb */, + C1FE93E62D0863AA009F8F5E /* nl */, + C1453E332D0863C80024449B /* pl */, + C180CAFB2D0863E500ADB0FE /* pt */, + C1B7BF532D08640B0024FF56 /* ro */, + C1193F612D08642900CB3239 /* ru */, + C1588FC52D08644800C9BE70 /* sk */, + C1E490592D08646400F86C5A /* sl */, + C11C4D312D08648100288E85 /* sv */, + C14D37D72D08649E00FCFC59 /* tr */, + ); + name = Localizable.strings; + sourceTree = ""; + }; CB1143DC2AF6D4B600C1CCD3 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -10357,7 +10509,7 @@ ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.AutofillCredentialProvider; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.CredentialExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -10435,7 +10587,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.CredentialExtension; PRODUCT_NAME = "$(TARGET_NAME)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AdHoc com.duckduckgo.mobile.ios.alpha.CredentialExtension"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.alpha.CredentialExtension"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -10472,8 +10624,9 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.AutofillCredentialProvider; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.CredentialExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.CredentialExtension"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 75296ae8ea..38ff685038 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -1108,7 +1108,7 @@ import os.log autofillPixelReporter = AutofillPixelReporter( userDefaults: .standard, autofillEnabled: AppDependencyProvider.shared.appSettings.autofillCredentialsEnabled, - eventMapping: EventMapping {event, _, params, _ in + eventMapping: EventMapping {[weak self] event, _, params, _ in switch event { case .autofillActiveUser: Pixel.fire(pixel: .autofillActiveUser) @@ -1118,8 +1118,16 @@ import os.log Pixel.fire(pixel: .autofillOnboardedUser) case .autofillToggledOn: Pixel.fire(pixel: .autofillToggledOn, withAdditionalParameters: params ?? [:]) + if let autofillExtensionToggled = self?.autofillUsageMonitor.autofillExtensionEnabled { + Pixel.fire(pixel: autofillExtensionToggled ? .autofillExtensionToggledOn : .autofillExtensionToggledOff, + withAdditionalParameters: params ?? [:]) + } case .autofillToggledOff: Pixel.fire(pixel: .autofillToggledOff, withAdditionalParameters: params ?? [:]) + if let autofillExtensionToggled = self?.autofillUsageMonitor.autofillExtensionEnabled { + Pixel.fire(pixel: autofillExtensionToggled ? .autofillExtensionToggledOn : .autofillExtensionToggledOff, + withAdditionalParameters: params ?? [:]) + } case .autofillLoginsStacked: Pixel.fire(pixel: .autofillLoginsStacked, withAdditionalParameters: params ?? [:]) default: diff --git a/DuckDuckGo/AutofillLoginPromptViewModel.swift b/DuckDuckGo/AutofillLoginPromptViewModel.swift index da3a3e551a..4193ba26af 100644 --- a/DuckDuckGo/AutofillLoginPromptViewModel.swift +++ b/DuckDuckGo/AutofillLoginPromptViewModel.swift @@ -20,6 +20,7 @@ import Foundation import UIKit import BrowserServicesKit +import Core protocol AutofillLoginPromptViewModelDelegate: AnyObject { func autofillLoginPromptViewModel(_ viewModel: AutofillLoginPromptViewModel, didSelectAccount account: SecureVaultModels.WebsiteAccount) diff --git a/DuckDuckGo/AutofillUsageMonitor.swift b/DuckDuckGo/AutofillUsageMonitor.swift index 4302aec266..5c9f9bf87f 100644 --- a/DuckDuckGo/AutofillUsageMonitor.swift +++ b/DuckDuckGo/AutofillUsageMonitor.swift @@ -18,13 +18,28 @@ // import Core +import AuthenticationServices final class AutofillUsageMonitor { init() { NotificationCenter.default.addObserver(self, selector: #selector(didReceiveSaveEvent), name: .autofillSaveEvent, object: nil) + + ASCredentialIdentityStore.shared.getState({ state in + if state.isEnabled { + self.autofillExtensionEnabled = true + } else { + if self.autofillExtensionEnabled != nil { + Pixel.fire(pixel: .autofillExtensionDisabled) + self.autofillExtensionEnabled = false + } + } + }) } - + + @UserDefaultsWrapper(key: .autofillExtensionEnabled, defaultValue: nil) + var autofillExtensionEnabled: Bool? + @UserDefaultsWrapper(key: .autofillFirstTimeUser, defaultValue: true) private var autofillFirstTimeUser: Bool diff --git a/DuckDuckGo/DuckDuckGo.entitlements b/DuckDuckGo/DuckDuckGo.entitlements index b798cc1099..ee77dff2bf 100644 --- a/DuckDuckGo/DuckDuckGo.entitlements +++ b/DuckDuckGo/DuckDuckGo.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.authentication-services.autofill-credential-provider + com.apple.developer.browser.app-installation com.apple.developer.networking.networkextension diff --git a/DuckDuckGoTests/AutofillInterfaceEmailTruncatorTests.swift b/DuckDuckGoTests/AutofillInterfaceEmailTruncatorTests.swift index c247ea0d97..a3d9340697 100644 --- a/DuckDuckGoTests/AutofillInterfaceEmailTruncatorTests.swift +++ b/DuckDuckGoTests/AutofillInterfaceEmailTruncatorTests.swift @@ -18,6 +18,7 @@ // import XCTest +import Core @testable import DuckDuckGo class AutofillInterfaceEmailTruncatorTests: XCTestCase { diff --git a/adhocExportOptions.plist b/adhocExportOptions.plist index 8af7edc154..de2b86d155 100644 --- a/adhocExportOptions.plist +++ b/adhocExportOptions.plist @@ -25,6 +25,9 @@ com.duckduckgo.mobile.ios.NetworkExtension match AdHoc com.duckduckgo.mobile.ios.NetworkExtension + + com.duckduckgo.mobile.ios.CredentialExtension + match AdHoc com.duckduckgo.mobile.ios.CredentialExtension diff --git a/appStoreExportOptions.plist b/appStoreExportOptions.plist index ff60631b5c..ed346823e8 100644 --- a/appStoreExportOptions.plist +++ b/appStoreExportOptions.plist @@ -18,6 +18,8 @@ match AppStore com.duckduckgo.mobile.ios.Widgets com.duckduckgo.mobile.ios.NetworkExtension match AppStore com.duckduckgo.mobile.ios.NetworkExtension + com.duckduckgo.mobile.ios.CredentialExtension + match AppStore com.duckduckgo.mobile.ios.CredentialExtension diff --git a/fastlane/Matchfile b/fastlane/Matchfile index 4212b95ef5..f577d97171 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -4,7 +4,7 @@ git_branch "ios" platform "ios" type "appstore" -app_identifier ["com.duckduckgo.mobile.ios", "com.duckduckgo.mobile.ios.ShareExtension", "com.duckduckgo.mobile.ios.OpenAction2", "com.duckduckgo.mobile.ios.Widgets", "com.duckduckgo.mobile.ios.NetworkExtension"] +app_identifier ["com.duckduckgo.mobile.ios", "com.duckduckgo.mobile.ios.ShareExtension", "com.duckduckgo.mobile.ios.OpenAction2", "com.duckduckgo.mobile.ios.Widgets", "com.duckduckgo.mobile.ios.NetworkExtension", "com.duckduckgo.mobile.ios.CredentialExtension"] generate_apple_certs false for_lane :sync_signing_adhoc do @@ -21,7 +21,7 @@ end for_lane :adhoc do type "adhoc" - app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension"] + app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension", "com.duckduckgo.mobile.ios.alpha.CredentialExtension"] force_for_new_devices true template_name "Default Web Browser iOS (Dist)" end @@ -40,11 +40,11 @@ for_lane :alpha_adhoc do end for_lane :sync_signing_alpha do - app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension"] + app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension", "com.duckduckgo.mobile.ios.alpha.CredentialExtension"] template_name "Default Web Browser iOS (Dist)" end for_lane :release_alpha do - app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension"] + app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension", "com.duckduckgo.mobile.ios.alpha.CredentialExtension"] template_name "Default Web Browser iOS (Dist)" end