From 4a220f879267dad7f6e629a3937d65015f800e69 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 4 Mar 2024 13:09:11 +0000 Subject: [PATCH 01/20] if dax dialogs are showing then dismiss (#2506) --- .github/workflows/end-to-end.yml | 1 + .../data_clearing_tests/02_duckduckgo_settings.yml | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index c0a2e39498..5cef39c0a7 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -3,6 +3,7 @@ name: End-to-End tests on: schedule: - cron: '0 4 * * *' # run at 4 AM UTC + workflow_dispatch: jobs: end-to-end-tests: diff --git a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml index 9de815dc35..e2da0f3e3a 100644 --- a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml +++ b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml @@ -5,9 +5,10 @@ tags: --- # Set up +- clearKeychain - clearState - launchApp -- runFlow: +- runFlow: when: visible: text: "Let’s Do It!" @@ -22,6 +23,13 @@ tags: - inputText: "privacy blogs" - pressKey: Enter +# Dismiss Dax Dialogs if visible +- runFlow: + when: + visible: "Phew!" + commands: + - tapOn: "Phew!" + # Change settings - tapOn: "Safe search: moderate ▼" - tapOn: "Off" From f77f1a7666bdadcc815afe001ffae76b0df277a5 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 4 Mar 2024 18:14:16 +0000 Subject: [PATCH 02/20] Release 7.111.0-0 (#2534) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 190 ++++++++++++++---- DuckDuckGo.xcodeproj/project.pbxproj | 56 +++--- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 5 files changed, 185 insertions(+), 69 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 6921fbe607..96f77f5f65 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.110.0 +MARKETING_VERSION = 7.111.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 2eb52672b6..37ce38ac13 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"20ceae04600e9d3c46898ac4471fcd70\"" - public static let embeddedDataSHA = "987f63a393724a34fe9d190c335f14b58398d2e9e02ccf38b84a7bd57c37b8ab" + public static let embeddedDataETag = "\"a427a69043b2baa27604bc10edb13de1\"" + public static let embeddedDataSHA = "1d5c2e4113713fbf02bc617fc689981604ea53be172569a9fd744054b7355c39" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index eaba26e1a1..d1f5828cd3 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1708953918019, + "version": 1709563256742, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -71,15 +71,15 @@ { "domain": "www.audiosciencereview.com" }, - { - "domain": "golf.com" - }, { "domain": "thehustle.co" }, { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -98,6 +98,7 @@ "^https?:\\/\\/\\S+ampproject\\.org\\/\\S\\/s\\/(\\S+)$" ], "keywords": [ + "amp=", "=amp", "&", "amp&", @@ -113,7 +114,7 @@ ] }, "state": "enabled", - "hash": "369b8aac2cb358e0df3a828c1e3b9a29" + "hash": "a92fae2cdccf479cc1bbe840cd32627b" }, "androidBrowserConfig": { "exceptions": [], @@ -263,6 +264,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -271,6 +275,9 @@ }, { "domain": "sundancecatalog.com" + }, + { + "domain": "instagram.com" } ], "settings": { @@ -298,7 +305,7 @@ } } }, - "hash": "7b3ce9784ee7348bb7a07d1868c0f3ae" + "hash": "b72f756b119c762183d04867e8927892" }, "autofill": { "exceptions": [ @@ -956,6 +963,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -981,13 +991,16 @@ } }, "state": "disabled", - "hash": "36e8971fa9bb204b78a5929a14a108dd" + "hash": "9c70121360bcdfeb63770d8d9aeee770" }, "clickToPlay": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1008,7 +1021,7 @@ } }, "state": "disabled", - "hash": "4390af06f967ef97a827aeab0ac0d1ca" + "hash": "ba97e20bd75a4dcd4ef376ec9b7fccc1" }, "clientBrandHint": { "exceptions": [], @@ -1039,6 +1052,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1049,7 +1065,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "e37447d42ee8194f185e35e40f577f41" + "hash": "910e25ffe4d683b3c708a1578d097a16" }, "cookie": { "settings": { @@ -1094,6 +1110,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1105,7 +1124,7 @@ } ], "state": "disabled", - "hash": "37a27966915571085613911b47e6e2eb" + "hash": "7c7ceca9eeb664059750ea96938669b0" }, "customUserAgent": { "settings": { @@ -1229,6 +1248,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -3977,13 +3999,16 @@ ] }, "state": "enabled", - "hash": "01a63fb9be9c1708398761f62f5f9598" + "hash": "70c18c8d5c08210844cb238822888fd7" }, "exceptionHandler": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -3995,7 +4020,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "fingerprintingAudio": { "state": "disabled", @@ -4006,6 +4031,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4016,7 +4044,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "f25a8f2709e865c2bd743828c7ee2f77" + "hash": "40b13d6ca36cd3de287345ab9e5839fb" }, "fingerprintingBattery": { "exceptions": [ @@ -4026,6 +4054,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4037,7 +4068,7 @@ } ], "state": "enabled", - "hash": "440f8d663d59430c93d66208655d9238" + "hash": "038608803499bebc30460a84ed27579f" }, "fingerprintingCanvas": { "settings": { @@ -4131,6 +4162,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4142,7 +4176,7 @@ } ], "state": "disabled", - "hash": "ea4c565bae27996f0d651300d757594c" + "hash": "98b5e91ff539dfb6c81699e32b76f70c" }, "fingerprintingHardware": { "settings": { @@ -4188,6 +4222,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4199,7 +4236,7 @@ } ], "state": "enabled", - "hash": "46fbcd4738329731c1b11e88e3afcb7b" + "hash": "ed0d208ef9ffcba9851eddf68a005583" }, "fingerprintingScreenSize": { "settings": { @@ -4242,6 +4279,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4253,7 +4293,7 @@ } ], "state": "enabled", - "hash": "0fb22f84b750e0d29bad55bd95d9ce2b" + "hash": "264749fcf7f5e7e03478bb6f0df4a48a" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -4269,6 +4309,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4280,13 +4323,16 @@ } ], "state": "enabled", - "hash": "f1632b92379847c92c95bcffefbc1bd2" + "hash": "c8f4dcd850359636b47ebc31a26f1f1d" }, "googleRejected": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4298,7 +4344,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "gpc": { "state": "enabled", @@ -4336,6 +4382,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4358,7 +4407,7 @@ "privacy-test-pages.site" ] }, - "hash": "549a6e76edaf16c1fffced31b97e9553" + "hash": "d1dd05d2cbbb9425a925cc162aaa681f" }, "harmfulApis": { "settings": { @@ -4463,6 +4512,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4474,7 +4526,7 @@ } ], "state": "disabled", - "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" + "hash": "9d0f5f4f8c02e79246e2d809cada2fdb" }, "history": { "state": "enabled", @@ -4496,6 +4548,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4506,7 +4561,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "f772808ed34cc9ea8cbcbb7cdaf74429" + "hash": "ea6d5ad048e35c75c451bff6fe58cb11" }, "incontextSignup": { "exceptions": [], @@ -4544,6 +4599,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4562,7 +4620,7 @@ ] }, "state": "enabled", - "hash": "698de7b963d7d7942c5c5d1e986bb1b1" + "hash": "f8dc40f1f5687f403f381452d66eb0d0" }, "networkProtection": { "state": "enabled", @@ -4590,6 +4648,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4601,7 +4662,7 @@ } ], "state": "disabled", - "hash": "841fa92b9728c9754f050662678f82c7" + "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, "notificationPermissions": { "exceptions": [], @@ -4616,10 +4677,20 @@ "rollout": { "steps": [] } + }, + "toggleReports": { + "state": "internal", + "rollout": { + "steps": [ + { + "percent": 5 + } + ] + } } }, - "state": "disabled", - "hash": "dede7e70939822f5ecb9eb5fae577fa3" + "state": "enabled", + "hash": "0d76cb4a367fc6738f7c4aa6a66f0a04" }, "privacyProtectionsPopup": { "state": "disabled", @@ -4649,6 +4720,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4660,7 +4734,7 @@ } ], "state": "disabled", - "hash": "0d3df0f7c24ebde89d2dced4e2d34322" + "hash": "1679be76968fe50858b3cc664b8fcbad" }, "requestFilterer": { "state": "disabled", @@ -4668,6 +4742,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4681,7 +4758,7 @@ "settings": { "windowInMs": 0 }, - "hash": "0fff8017d8ea4b5609b8f5c110be1401" + "hash": "219a51a9aafbc9c1bae4bad55d7ce437" }, "runtimeChecks": { "state": "disabled", @@ -4689,6 +4766,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4700,13 +4780,16 @@ } ], "settings": {}, - "hash": "800a19533c728bbec7e31e466f898268" + "hash": "e2246d7c78df2167134e1428b04d51ca" }, "serviceworkerInitiatedRequests": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4718,7 +4801,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "sync": { "state": "enabled", @@ -5833,6 +5916,12 @@ "domains": [ "channel4.com" ] + }, + { + "rule": "7cbf2.v.fwmrm.net/ad/g/1", + "domains": [ + "6play.fr" + ] } ] }, @@ -7649,6 +7738,12 @@ "domains": [ "fashionnova.com" ] + }, + { + "rule": "rapid-cdn.yottaa.com/rapid/lib/ZfJxptseJcUQIA.js", + "domains": [ + "aviatornation.com" + ] } ] }, @@ -7718,6 +7813,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7728,7 +7826,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "86a7c891d57513e67356a82da2a2aa1d" + "hash": "6768849b4f63b2e635698ac9dde79aa3" }, "trackingCookies1p": { "settings": { @@ -7741,6 +7839,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7752,7 +7853,7 @@ } ], "state": "disabled", - "hash": "4dddf681372a2aea9788090b13db6e6f" + "hash": "bfd8b32efe8d633fe670bf6ab1b00240" }, "trackingCookies3p": { "settings": { @@ -7762,6 +7863,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7773,7 +7877,7 @@ } ], "state": "disabled", - "hash": "841fa92b9728c9754f050662678f82c7" + "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, "trackingParameters": { "exceptions": [ @@ -7783,6 +7887,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7826,7 +7933,7 @@ ] }, "state": "enabled", - "hash": "1df4ca1a649e81401fb5e872212b4dd0" + "hash": "f64c29121e46b2c79c23e8e7efc58c59" }, "userAgentRotation": { "settings": { @@ -7836,6 +7943,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7847,7 +7957,7 @@ } ], "state": "disabled", - "hash": "f65d10dfdf6739feab99a08d42734747" + "hash": "4498ff835bed7ce27ff2a568db599155" }, "voiceSearch": { "exceptions": [], @@ -7859,6 +7969,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7909,7 +8022,7 @@ } ] }, - "hash": "592a1fb6314f04875fc44a66ef7c2433" + "hash": "1b8acba9eed9ba83fdfe0da1e9d8db87" }, "windowsPermissionUsage": { "exceptions": [], @@ -7921,6 +8034,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7932,7 +8048,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "windowsWaitlist": { "exceptions": [], diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 992037d17f..09fa88e566 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8210,7 +8210,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8247,7 +8247,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8339,7 +8339,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8367,7 +8367,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8517,7 +8517,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8543,7 +8543,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8608,7 +8608,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8643,7 +8643,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8677,7 +8677,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8708,7 +8708,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8995,7 +8995,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9026,7 +9026,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9055,7 +9055,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9089,7 +9089,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9120,7 +9120,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9153,11 +9153,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9391,7 +9391,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9418,7 +9418,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9451,7 +9451,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9489,7 +9489,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9525,7 +9525,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9560,11 +9560,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9738,11 +9738,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9771,10 +9771,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index a984845555..d0dff36535 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.110.0 + 7.111.0 Key version Title From 5e056b50106e58a0062a901568dc1ed405521ddb Mon Sep 17 00:00:00 2001 From: Brian Hall Date: Tue, 5 Mar 2024 05:34:41 -0600 Subject: [PATCH 03/20] Bump BrowserServicesKit (#2532) Co-authored-by: Fernando Bunn --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 09fa88e566..d92b4cb973 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9981,7 +9981,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.1.0; + version = 114.2.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 018b6e092f..d8c92e6a58 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", - "version" : "114.1.0" + "revision" : "457443c10861ea1253383408fe3763f6320d25b4", + "version" : "114.2.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "a3690b7666a3617693383d948cb492513f6aa569", - "version" : "5.0.0" + "revision" : "59752eb7973d3e3b0c23255ff51359f48b343f15", + "version" : "5.2.0" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" From 38d6eb267895fd220b7a935c698c8382cd06eece Mon Sep 17 00:00:00 2001 From: amddg44 Date: Tue, 5 Mar 2024 14:21:12 +0100 Subject: [PATCH 04/20] Autofill support for deleting all passwords (#2497) Task/Issue URL: https://app.asana.com/0/1201462886803403/1206303310228833/f Tech Design URL: CC: Description: Give users the ability to delete all passwords they have saved --- DuckDuckGo.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AuthConfirmationPromptView.swift | 130 ++++++++++++ ...AuthConfirmationPromptViewController.swift | 106 ++++++++++ .../AuthConfirmationPromptViewModel.swift | 60 ++++++ DuckDuckGo/AutofillLoginDetailsView.swift | 4 +- .../AutofillLoginDetailsViewController.swift | 5 +- .../AutofillLoginDetailsViewModel.swift | 14 +- DuckDuckGo/AutofillLoginListViewModel.swift | 54 ++++- ...ofillLoginSettingsListViewController.swift | 177 ++++++++++++++-- DuckDuckGo/AutofillViews.swift | 1 + DuckDuckGo/PasswordGenerationPromptView.swift | 50 ++--- DuckDuckGo/UserText.swift | 27 ++- DuckDuckGo/bg.lproj/Localizable.strings | 32 ++- DuckDuckGo/bg.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/cs.lproj/Localizable.strings | 32 ++- DuckDuckGo/cs.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/da.lproj/Localizable.strings | 32 ++- DuckDuckGo/da.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/de.lproj/Localizable.strings | 32 ++- DuckDuckGo/de.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/el.lproj/Localizable.strings | 32 ++- DuckDuckGo/el.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/en.lproj/Localizable.strings | 30 ++- DuckDuckGo/en.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/es.lproj/Localizable.strings | 32 ++- DuckDuckGo/es.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/et.lproj/Localizable.strings | 32 ++- DuckDuckGo/et.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/fi.lproj/Localizable.strings | 32 ++- DuckDuckGo/fi.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/fr.lproj/Localizable.strings | 34 +++- DuckDuckGo/fr.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/hr.lproj/Localizable.strings | 32 ++- DuckDuckGo/hr.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/hu.lproj/Localizable.strings | 32 ++- DuckDuckGo/hu.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/it.lproj/Localizable.strings | 32 ++- DuckDuckGo/it.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/lt.lproj/Localizable.strings | 32 ++- DuckDuckGo/lt.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/lv.lproj/Localizable.strings | 32 ++- DuckDuckGo/lv.lproj/Localizable.stringsdict | 116 +++++++++++ DuckDuckGo/nb.lproj/Localizable.strings | 32 ++- DuckDuckGo/nb.lproj/Localizable.stringsdict | 103 ++++++++++ DuckDuckGo/nl.lproj/Localizable.strings | 32 ++- DuckDuckGo/nl.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/pl.lproj/Localizable.strings | 32 ++- DuckDuckGo/pl.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/pt.lproj/Localizable.strings | 32 ++- DuckDuckGo/pt.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/ro.lproj/Localizable.strings | 32 ++- DuckDuckGo/ro.lproj/Localizable.stringsdict | 116 +++++++++++ DuckDuckGo/ru.lproj/Localizable.strings | 32 ++- DuckDuckGo/ru.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/sk.lproj/Localizable.strings | 32 ++- DuckDuckGo/sk.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/sl.lproj/Localizable.strings | 32 ++- DuckDuckGo/sl.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/sv.lproj/Localizable.strings | 32 ++- DuckDuckGo/sv.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/tr.lproj/Localizable.strings | 32 ++- DuckDuckGo/tr.lproj/Localizable.stringsdict | 102 ++++++++++ .../AutofillLoginListViewModelTests.swift | 190 +++++++++++++++++- DuckDuckGoTests/MockSecureVault.swift | 10 + 65 files changed, 4271 insertions(+), 158 deletions(-) create mode 100644 DuckDuckGo/AuthConfirmationPromptView.swift create mode 100644 DuckDuckGo/AuthConfirmationPromptViewController.swift create mode 100644 DuckDuckGo/AuthConfirmationPromptViewModel.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d92b4cb973..074ad901eb 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -707,6 +707,9 @@ C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */; }; C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */; }; C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */; }; + C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */; }; + C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */; }; + C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */; }; C14882DA27F2011C00D59F0C /* BookmarksExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D727F2011C00D59F0C /* BookmarksExporter.swift */; }; C14882DC27F2011C00D59F0C /* BookmarksImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D927F2011C00D59F0C /* BookmarksImporter.swift */; }; C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882E127F20D9A00D59F0C /* BookmarksExporterTests.swift */; }; @@ -2354,6 +2357,9 @@ C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewModel.swift; sourceTree = ""; }; C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewController.swift; sourceTree = ""; }; C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSettingStatus.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 = ""; }; 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 = ""; }; @@ -4457,6 +4463,16 @@ name = PasswordGeneration; sourceTree = ""; }; + C1AFFC4B2B8773060060448E /* AuthConfirmation */ = { + isa = PBXGroup; + children = ( + C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */, + C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */, + C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */, + ); + name = AuthConfirmation; + sourceTree = ""; + }; C1B7B51D28941F160098FD6A /* RemoteMessaging */ = { isa = PBXGroup; children = ( @@ -5593,6 +5609,7 @@ C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */, 319A370F28299A850079FBCE /* PasswordHider.swift */, 31C70B5428045E3500FB6AD1 /* SecureVaultErrorReporter.swift */, + C1AFFC4B2B8773060060448E /* AuthConfirmation */, F407605328131910006B1E0B /* AutofillLoginUI */, 310C4B4A281B69BC00BA79A9 /* Management */, C17B59552A03AAC40055F2D1 /* PasswordGeneration */, @@ -6860,6 +6877,7 @@ 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, + C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */, @@ -6867,6 +6885,7 @@ F17922DB1E717C8D006E3D97 /* Suggestion.swift in Sources */, 020108A729A6ABF600644F9D /* AppTPToggleView.swift in Sources */, 02A54A982A093126000C8FED /* AppTPHomeViewModel.swift in Sources */, + C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */, F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */, 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, @@ -6879,6 +6898,7 @@ 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, + C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, EE4BE0092A740BED00CD6AA8 /* ClearTextField.swift in Sources */, @@ -9981,7 +10001,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.2.0; + version = 114.3.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8c92e6a58..5f6b2bab9d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "457443c10861ea1253383408fe3763f6320d25b4", - "version" : "114.2.0" + "revision" : "1fa06fb43fb0f26fc1a74b710fb1402573264bf5", + "version" : "114.3.0" } }, { diff --git a/DuckDuckGo/AuthConfirmationPromptView.swift b/DuckDuckGo/AuthConfirmationPromptView.swift new file mode 100644 index 0000000000..6c8a157718 --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptView.swift @@ -0,0 +1,130 @@ +// +// AuthConfirmationPromptView.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 + +struct AuthConfirmationPromptView: View { + + @State var frame: CGSize = .zero + @ObservedObject var viewModel: AuthConfirmationPromptViewModel + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.verticalSizeClass) var verticalSizeClass + + var body: some View { + GeometryReader { geometry in + makeBodyView(geometry) + } + } + + private func makeBodyView(_ geometry: GeometryProxy) -> some View { + DispatchQueue.main.async { self.frame = geometry.size } + + return VStack { + HStack { + Button { + viewModel.cancelButtonPressed() + } label: { + Text(UserText.actionCancel) + .foregroundColor(Color(designSystemColor: .textSecondary)) + } + Spacer() + } + .padding([.top, .leading], Const.Size.closeButtonPadding) + + Group { + Spacer() + .frame(height: Const.Size.topPadding) + Image + .lock + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Headline(title: UserText.autofillDeleteAllPasswordsAuthenticationPromptTitle) + contentViewSpacer + AutofillViews.PrimaryButton(title: UserText.autofillDeleteAllPasswordsAuthenticationPromptButton, + action: { viewModel.authenticatePressed() }) + .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad + : Const.Size.bottomPadding) + } + .padding(.horizontal, horizontalPadding) + } + .background(GeometryReader { proxy -> Color in + DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } + return Color.clear + }) + .useScrollView(shouldUseScrollView(), minHeight: frame.height) + } + + private var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.horizontalPaddingPortraitSmallFrame + } else { + return Const.Size.horizontalPaddingPortrait + } + } else { + return Const.Size.horizontalPadding + } + } + + private var contentViewSpacer: some View { + VStack { + if AutofillViews.isIPhoneLandscape(verticalSizeClass) { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeightLandscape) + } else { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeight) + } + } + } + + private func shouldUseScrollView() -> Bool { + var useScrollView: Bool = false + + if #available(iOS 16.0, *) { + useScrollView = AutofillViews.contentHeightExceedsScreenHeight(viewModel.contentHeight) + } else { + useScrollView = viewModel.contentHeight > frame.height + Const.Size.ios15scrollOffset + } + + return useScrollView + } +} + +private enum Const { + enum Size { + static let closeButtonPadding: CGFloat = 16.0 + static let horizontalPadding: CGFloat = 48.0 + static let horizontalPaddingPortrait: CGFloat = 44.0 + static let horizontalPaddingPortraitSmallFrame: CGFloat = 16.0 + static let topPadding: CGFloat = 36.0 + static let headlineTopPadding: CGFloat = 24.0 + static let ios15scrollOffset: CGFloat = 80.0 + static let contentSpacerHeight: CGFloat = 44.0 + static let contentSpacerHeightLandscape: CGFloat = 50.0 + static let bottomPadding: CGFloat = 12.0 + static let bottomPaddingIPad: CGFloat = 24.0 + } +} + +private extension Image { + static let lock = Image("AutofillLock") +} + +#Preview { + AuthConfirmationPromptView(viewModel: AuthConfirmationPromptViewModel()) +} diff --git a/DuckDuckGo/AuthConfirmationPromptViewController.swift b/DuckDuckGo/AuthConfirmationPromptViewController.swift new file mode 100644 index 0000000000..fabcf3273c --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptViewController.swift @@ -0,0 +1,106 @@ +// +// AuthConfirmationPromptViewController.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 + +final class AuthConfirmationPromptViewController: UIViewController { + + typealias AuthConfirmationCompletion = (_ authenticated: Bool) -> Void + + private let didBeginAuthenticating: () -> Void + private let authConfirmationCompletion: AuthConfirmationCompletion + + private var viewModel: AuthConfirmationPromptViewModel? + + init(didBeginAuthenticating: @escaping () -> Void, + authConfirmationCompletion: @escaping AuthConfirmationCompletion) { + self.didBeginAuthenticating = didBeginAuthenticating + self.authConfirmationCompletion = authConfirmationCompletion + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor(named: "AutofillPromptLargeBackground") + + setupAuthConfirmationPromptView() + } + + private func setupAuthConfirmationPromptView() { + let authConfirmationPromptViewModel = AuthConfirmationPromptViewModel() + authConfirmationPromptViewModel.delegate = self + viewModel = authConfirmationPromptViewModel + + let authConfirmationPromptView = AuthConfirmationPromptView(viewModel: authConfirmationPromptViewModel) + let controller = UIHostingController(rootView: authConfirmationPromptView) + controller.view.backgroundColor = .clear + presentationController?.delegate = self + installChildViewController(controller) + } + +} + +// MARK: UISheetPresentationControllerDelegate + +extension AuthConfirmationPromptViewController: UISheetPresentationControllerDelegate { + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + authConfirmationCompletion(false) + } + +} + +// MARK: AuthConfirmationPromptViewModelDelegate + +extension AuthConfirmationPromptViewController: AuthConfirmationPromptViewModelDelegate { + + func authConfirmationPromptViewModelDidBeginAuthenticating(_ viewModel: AuthConfirmationPromptViewModel) { + didBeginAuthenticating() + } + + func authConfirmationPromptViewModelDidAuthenticate(_ viewModel: AuthConfirmationPromptViewModel, success: Bool) { + dismiss(animated: true) { + self.authConfirmationCompletion(success) + } + } + + func authConfirmationPromptViewModelDidCancel(_ viewModel: AuthConfirmationPromptViewModel) { + dismiss(animated: true) { + self.authConfirmationCompletion(false) + } + } + + func authConfirmationPromptViewModelDidResizeContent(_ viewModel: AuthConfirmationPromptViewModel, contentHeight: CGFloat) { + if #available(iOS 16.0, *) { + if let sheetPresentationController = self.presentationController as? UISheetPresentationController { + sheetPresentationController.animateChanges { + sheetPresentationController.detents = [.custom(resolver: { _ in contentHeight })] + } + } + } + } + +} diff --git a/DuckDuckGo/AuthConfirmationPromptViewModel.swift b/DuckDuckGo/AuthConfirmationPromptViewModel.swift new file mode 100644 index 0000000000..0ac7cadc50 --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptViewModel.swift @@ -0,0 +1,60 @@ +// +// AuthConfirmationPromptViewModel.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 + +protocol AuthConfirmationPromptViewModelDelegate: AnyObject { + func authConfirmationPromptViewModelDidBeginAuthenticating(_ viewModel: AuthConfirmationPromptViewModel) + func authConfirmationPromptViewModelDidAuthenticate(_ viewModel: AuthConfirmationPromptViewModel, success: Bool) + func authConfirmationPromptViewModelDidCancel(_ viewModel: AuthConfirmationPromptViewModel) + func authConfirmationPromptViewModelDidResizeContent(_ viewModel: AuthConfirmationPromptViewModel, contentHeight: CGFloat) +} + +final class AuthConfirmationPromptViewModel: ObservableObject { + + weak var delegate: AuthConfirmationPromptViewModelDelegate? + private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillDeleteAllPasswordsAuthenticationReason) + + var contentHeight: CGFloat = AutofillViews.deleteAllPromptMinHeight { + didSet { + guard contentHeight != oldValue else { + return + } + delegate?.authConfirmationPromptViewModelDidResizeContent(self, + contentHeight: max(contentHeight, AutofillViews.deleteAllPromptMinHeight)) + } + } + + func authenticatePressed() { + delegate?.authConfirmationPromptViewModelDidBeginAuthenticating(self) + + authenticator.authenticate { [weak self] error in + self?.authCompleted(with: error == nil) + } + } + + func cancelButtonPressed() { + delegate?.authConfirmationPromptViewModelDidCancel(self) + } + + private func authCompleted(with success: Bool) { + delegate?.authConfirmationPromptViewModelDidAuthenticate(self, success: success) + } + +} diff --git a/DuckDuckGo/AutofillLoginDetailsView.swift b/DuckDuckGo/AutofillLoginDetailsView.swift index 589d858324..ba9eee9654 100644 --- a/DuckDuckGo/AutofillLoginDetailsView.swift +++ b/DuckDuckGo/AutofillLoginDetailsView.swift @@ -291,8 +291,8 @@ struct AutofillLoginDetailsView: View { let deleteAction = ActionSheet.Button.destructive(Text(UserText.autofillLoginDetailsDeleteConfirmationButtonTitle)) { viewModel.delete() } - return ActionSheet(title: Text(UserText.autofillLoginDetailsDeleteConfirmationTitle), - message: nil, + return ActionSheet(title: Text(UserText.autofillDeleteAllPasswordsActionTitle(for: 1)), + message: Text(viewModel.deleteMessage()), buttons: [deleteAction, ActionSheet.Button.cancel()]) }) .foregroundColor(Color.red) diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index c44a48f94f..803e5d1cce 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -22,6 +22,7 @@ import SwiftUI import BrowserServicesKit import Common import Combine +import DDGSync protocol AutofillLoginDetailsViewControllerDelegate: AnyObject { func autofillLoginDetailsViewControllerDidSave(_ controller: AutofillLoginDetailsViewController, account: SecureVaultModels.WebsiteAccount?) @@ -69,8 +70,8 @@ class AutofillLoginDetailsViewController: UIViewController { constant: 144) }() - init(authenticator: AutofillLoginListAuthenticator, account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, authenticationNotRequired: Bool = false) { - self.viewModel = AutofillLoginDetailsViewModel(account: account, tld: tld) + init(authenticator: AutofillLoginListAuthenticator, syncService: DDGSyncing, account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, authenticationNotRequired: Bool = false) { + self.viewModel = AutofillLoginDetailsViewModel(account: account, syncService: syncService, tld: tld) self.authenticator = authenticator self.authenticationNotRequired = authenticationNotRequired super.init(nibName: nil, bundle: nil) diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 0e9ec04872..2c7ec10da8 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -26,6 +26,7 @@ import SwiftUI import Core import DesignResourcesKit import SecureStorage +import DDGSync protocol AutofillLoginDetailsViewModelDelegate: AnyObject { func autofillLoginDetailsViewModelDidSave() @@ -63,6 +64,7 @@ final class AutofillLoginDetailsViewModel: ObservableObject { weak var delegate: AutofillLoginDetailsViewModelDelegate? var account: SecureVaultModels.WebsiteAccount? var emailManager: EmailManager + private let syncService: DDGSyncing private let tld: TLD private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher() @@ -178,9 +180,11 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } internal init(account: SecureVaultModels.WebsiteAccount? = nil, + syncService: DDGSyncing, tld: TLD, emailManager: EmailManager = EmailManager()) { self.account = account + self.syncService = syncService self.tld = tld self.headerViewModel = AutofillLoginDetailsHeaderViewModel() self.emailManager = emailManager @@ -228,7 +232,15 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } } } - + + func deleteMessage() -> String { + if syncService.authState == .inactive { + return UserText.autofillDeleteAllPasswordsActionMessage(for: 1) + } else { + return UserText.autofillDeleteAllPasswordsSyncActionMessage(for: 1) + } + } + func copyToPasteboard(_ action: PasteboardCopyAction) { var message = "" switch action { diff --git a/DuckDuckGo/AutofillLoginListViewModel.swift b/DuckDuckGo/AutofillLoginListViewModel.swift index 20abb86e80..bead3e9829 100644 --- a/DuckDuckGo/AutofillLoginListViewModel.swift +++ b/DuckDuckGo/AutofillLoginListViewModel.swift @@ -50,6 +50,7 @@ internal enum EnableAutofillRows: Int, CaseIterable { case resetNeverPromptWebsites } +// swiftlint:disable file_length type_body_length final class AutofillLoginListViewModel: ObservableObject { enum ViewState { @@ -63,8 +64,13 @@ final class AutofillLoginListViewModel: ObservableObject { let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) var isSearching: Bool = false + var isEditing: Bool = false { + didSet { + sections = makeSections(with: accounts) + } + } var authenticationNotRequired = false - private var accounts = [SecureVaultModels.WebsiteAccount]() + @Published private var accounts = [SecureVaultModels.WebsiteAccount]() private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]() private var cancellables: Set = [] private var appSettings: AppSettings @@ -87,6 +93,16 @@ final class AutofillLoginListViewModel: ObservableObject { var hasAccountsSaved: Bool { return !accounts.isEmpty } + + var accountsCount: Int { + accounts.count + } + + var accountsCountPublisher: AnyPublisher { + $accounts + .map { $0.count } + .eraseToAnyPublisher() + } var isAutofillEnabledInSettings: Bool { get { appSettings.autofillCredentialsEnabled } @@ -124,6 +140,10 @@ final class AutofillLoginListViewModel: ObservableObject { return false } + func deleteAllCredentials() -> Bool { + return deleteAll() + } + func undoLastDelete() { guard let cachedDeletedCredentials = cachedDeletedCredentials else { return @@ -134,7 +154,17 @@ final class AutofillLoginListViewModel: ObservableObject { func clearUndoCache() { cachedDeletedCredentials = nil } - + + func clearAllAccounts() { + accounts = [] + accountsToSuggest = [] + sections = makeSections(with: accounts) + } + + func undoClearAllAccounts() { + updateData() + } + func lockUI() { authenticationNotRequired = !hasAccountsSaved authenticator.logOut() @@ -233,7 +263,9 @@ final class AutofillLoginListViewModel: ObservableObject { var newSections = [AutofillLoginListSectionType]() if !isSearching { - newSections.append(.enableAutofill) + if !isEditing { + newSections.append(.enableAutofill) + } if !accountsToSuggest.isEmpty { let accountItems = accountsToSuggest.map { AutofillLoginListItemViewModel(account: $0, @@ -278,6 +310,8 @@ final class AutofillLoginListViewModel: ObservableObject { } else { newViewState = .searching } + } else if isEditing { + newViewState = sections.count >= 1 ? .showItems : .empty } else { newViewState = sections.count > 1 ? .showItems : .empty } @@ -338,7 +372,21 @@ final class AutofillLoginListViewModel: ObservableObject { Pixel.fire(pixel: .secureVaultError, error: error) } } + + @discardableResult + private func deleteAll() -> Bool { + guard let secureVault = secureVault else { return false } + + do { + try secureVault.deleteAllWebsiteCredentials() + return true + } catch { + Pixel.fire(pixel: .secureVaultError, error: error) + return false + } + } } +// swiftlint:enable type_body_length extension AutofillLoginListItemViewModel: Comparable { static func < (lhs: AutofillLoginListItemViewModel, rhs: AutofillLoginListItemViewModel) -> Bool { diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index c3b6e26c05..7b90ad5868 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -55,7 +55,34 @@ final class AutofillLoginSettingsListViewController: UIViewController { target: self, action: #selector(addButtonPressed)) }() - + + private lazy var deleteAllButtonItem: UIBarButtonItem = { + let button = UIBarButtonItem(title: UserText.autofillLoginListToolbarDeleteAllButton, + style: .plain, + target: self, + action: #selector(deleteAll)) + button.tintColor = .systemRed + return button + }() + + private lazy var accountsCountLabel: UILabel = { + let label = UILabel() + label.font = .daxCaption() + label.textColor = UIColor(designSystemColor: .textSecondary) + label.text = UserText.autofillLoginListToolbarPasswordsCount(viewModel.accountsCount) + return label + }() + + private lazy var accountsCountButtonItem: UIBarButtonItem = { + let item = UIBarButtonItem(customView: accountsCountLabel) + return item + }() + + private lazy var flexibleSpace: UIBarButtonItem = { + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + return space + }() + private var cancellables: Set = [] private lazy var searchController: UISearchController = { let searchController = UISearchController(searchResultsController: nil) @@ -78,8 +105,6 @@ final class AutofillLoginSettingsListViewController: UIViewController { tableView.registerCell(ofType: AutofillListItemTableViewCell.self) tableView.registerCell(ofType: EnableAutofillSettingsTableViewCell.self) tableView.registerCell(ofType: AutofillNeverSavedTableViewCell.self) - // Have to set tableHeaderView height otherwise tableView content will jump when adding / removing searchController due to tableView insetGrouped style - tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 24)) return tableView }() @@ -102,7 +127,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { multiplier: 1, constant: (tableView.frame.height / 2)) }() - + var selectedAccount: SecureVaultModels.WebsiteAccount? init(appSettings: AppSettings, currentTabUrl: URL? = nil, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, selectedAccount: SecureVaultModels.WebsiteAccount?) { @@ -156,6 +181,9 @@ final class AutofillLoginSettingsListViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + if isMovingFromParent { + navigationController?.isToolbarHidden = true + } if viewModel.authenticator.canAuthenticate() && viewModel.authenticator.state == .loggedIn { AppDependencyProvider.shared.autofillLoginSession.startSession() } @@ -192,13 +220,19 @@ final class AutofillLoginSettingsListViewController: UIViewController { tableView.setEditing(editing, animated: animated) + // trigger re-build of table sections + viewModel.isEditing = editing + tableView.reloadData() + updateNavigationBarButtons() updateSearchController() + updateToolbar() } - + @objc func addButtonPressed() { let detailsController = AutofillLoginDetailsViewController(authenticator: viewModel.authenticator, + syncService: syncService, tld: tld, authenticationNotRequired: viewModel.authenticationNotRequired) detailsController.delegate = self @@ -209,6 +243,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { func makeAccountDetailsScreen(_ account: SecureVaultModels.WebsiteAccount) -> AutofillLoginDetailsViewController { let detailsController = AutofillLoginDetailsViewController(authenticator: viewModel.authenticator, + syncService: syncService, account: account, tld: tld, authenticationNotRequired: viewModel.authenticationNotRequired) @@ -253,24 +288,31 @@ final class AutofillLoginSettingsListViewController: UIViewController { self?.tableView.reloadData() } .store(in: &cancellables) + + viewModel.accountsCountPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateToolbarLabel() + } + .store(in: &cancellables) + } private func configureNotification() { - let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, - selector: #selector(appWillMoveToForegroundCallback), - name: UIApplication.willEnterForegroundNotification, object: nil) - - notificationCenter.addObserver(self, - selector: #selector(appWillMoveToBackgroundCallback), - name: UIApplication.willResignActiveNotification, object: nil) + addObserver(for: UIApplication.didBecomeActiveNotification, selector: #selector(appDidBecomeActiveCallback)) + addObserver(for: UIApplication.willResignActiveNotification, selector: #selector(appWillResignActiveCallback)) + addObserver(for: AutofillLoginListAuthenticator.Notifications.invalidateContext, selector: #selector(authenticatorInvalidateContext)) + } - notificationCenter.addObserver(self, - selector: #selector(authenticatorInvalidateContext), - name: AutofillLoginListAuthenticator.Notifications.invalidateContext, object: nil) + private func addObserver(for notification: Notification.Name, selector: Selector) { + NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: nil) } - - @objc private func appWillMoveToForegroundCallback() { + + private func removeObserver(for notification: Notification.Name) { + NotificationCenter.default.removeObserver(self, name: notification, object: nil) + } + + @objc private func appDidBecomeActiveCallback() { // AutofillLoginDetailsViewController will handle calling authenticate() if it is the top view controller guard navigationController?.topViewController is AutofillLoginDetailsViewController else { authenticate() @@ -278,7 +320,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } - @objc private func appWillMoveToBackgroundCallback() { + @objc private func appWillResignActiveCallback() { viewModel.lockUI() } @@ -324,7 +366,83 @@ final class AutofillLoginSettingsListViewController: UIViewController { userInfo: [FireproofFaviconUpdater.UserInfoKeys.faviconDomain: domain]) }) } - + + @objc private func deleteAll() { + let message = self.syncService.authState == .inactive ? UserText.autofillDeleteAllPasswordsActionMessage(for: viewModel.accountsCount) + : UserText.autofillDeleteAllPasswordsSyncActionMessage(for: viewModel.accountsCount) + let alert = UIAlertController(title: UserText.autofillDeleteAllPasswordsActionTitle(for: viewModel.accountsCount), + message: message, + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: UserText.actionCancel, style: .cancel)) + let deleteAllAction = UIAlertAction(title: UserText.actionDelete, style: .destructive) {[weak self] _ in + self?.presentAuthConfirmationPrompt() + } + alert.addAction(deleteAllAction) + alert.preferredAction = deleteAllAction + present(controller: alert, fromView: tableView) + } + + private func presentAuthConfirmationPrompt() { + let authConfirmationPromptViewController = AuthConfirmationPromptViewController( + didBeginAuthenticating: { [weak self] in + self?.configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: true) + }, authConfirmationCompletion: { [weak self] authenticated in + self?.configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: false) + + if authenticated { + let accountsCount = self?.viewModel.accountsCount ?? 0 + self?.viewModel.clearAllAccounts() + self?.presentDeleteAllConfirmation(accountsCount) + } + } + ) + + if #available(iOS 15.0, *) { + if let presentationController = authConfirmationPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.deleteAllPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] + } + } + } + + present(authConfirmationPromptViewController, animated: true) + } + + private func configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: Bool) { + if isAuthenticating { + addObserver(for: UIApplication.didEnterBackgroundNotification, selector: #selector(appWillResignActiveCallback)) + removeObserver(for: UIApplication.willResignActiveNotification) + } else { + addObserver(for: UIApplication.willResignActiveNotification, selector: #selector(appWillResignActiveCallback)) + removeObserver(for: UIApplication.didEnterBackgroundNotification) + } + } + + private func presentDeleteAllConfirmation(_ numberOfAccounts: Int) { + var shouldDeleteAccounts = true + + ActionMessageView.present(message: UserText.autofillAllPasswordsDeletedToastMessage(for: numberOfAccounts), + actionTitle: UserText.actionGenericUndo, + presentationLocation: .withoutBottomBar, + onAction: { + shouldDeleteAccounts = false + }, onDidDismiss: { + if shouldDeleteAccounts { + if self.viewModel.deleteAllCredentials() { + self.syncService.scheduler.notifyDataChanged() + self.viewModel.resetNeverPromptWebsites() + self.viewModel.updateData() + } + } else { + self.viewModel.undoClearAllAccounts() + } + }) + } + // MARK: Subviews Setup private func updateViewState() { @@ -370,6 +488,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } updateNavigationBarButtons() updateSearchController() + updateToolbar() tableView.reloadData() } @@ -424,6 +543,24 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } + private func updateToolbar() { + if tableView.isEditing && viewModel.viewState == .showItems { + updateToolbarLabel() + navigationController?.isToolbarHidden = false + toolbarItems = [deleteAllButtonItem, flexibleSpace, accountsCountButtonItem, flexibleSpace] + } else { + toolbarItems?.removeAll() + navigationController?.isToolbarHidden = true + } + } + + private func updateToolbarLabel() { + guard tableView.isEditing else { return } + + accountsCountLabel.text = UserText.autofillLoginListToolbarPasswordsCount(viewModel.accountsCount) + accountsCountLabel.sizeToFit() + } + private func installSubviews() { view.addSubview(tableView) tableView.addSubview(emptySearchView) diff --git a/DuckDuckGo/AutofillViews.swift b/DuckDuckGo/AutofillViews.swift index aae551c98f..633d2fe261 100644 --- a/DuckDuckGo/AutofillViews.swift +++ b/DuckDuckGo/AutofillViews.swift @@ -29,6 +29,7 @@ struct AutofillViews { static let updateUsernameMinHeight = 310.0 static let passwordGenerationMinHeight: CGFloat = 310.0 static let emailSignupPromptMinHeight: CGFloat = 260.0 + static let deleteAllPromptMinHeight: CGFloat = 360.0 struct CloseButtonHeader: View { let action: () -> Void diff --git a/DuckDuckGo/PasswordGenerationPromptView.swift b/DuckDuckGo/PasswordGenerationPromptView.swift index 470084db8f..8c01fdabf6 100644 --- a/DuckDuckGo/PasswordGenerationPromptView.swift +++ b/DuckDuckGo/PasswordGenerationPromptView.swift @@ -41,32 +41,32 @@ struct PasswordGenerationPromptView: View { .offset(x: horizontalPadding) .zIndex(1) - VStack { - Spacer() - .frame(height: Const.Size.topPadding) - AutofillViews.AppIconHeader() - Spacer() - .frame(height: Const.Size.headlineTopPadding) - AutofillViews.Headline(title: UserText.autofillPasswordGenerationPromptTitle) - if #available(iOS 16.0, *) { - passwordView - .padding([.top, .bottom], passwordVerticalPadding) - } else { - AutofillViews.LegacySpacerView() - passwordView - AutofillViews.LegacySpacerView() - } - AutofillViews.Description(text: UserText.autofillPasswordGenerationPromptSubtitle) - contentViewSpacer - ctaView - .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad - : Const.Size.bottomPadding) + VStack { + Spacer() + .frame(height: Const.Size.topPadding) + AutofillViews.AppIconHeader() + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Headline(title: UserText.autofillPasswordGenerationPromptTitle) + if #available(iOS 16.0, *) { + passwordView + .padding([.top, .bottom], passwordVerticalPadding) + } else { + AutofillViews.LegacySpacerView() + passwordView + AutofillViews.LegacySpacerView() } - .background(GeometryReader { proxy -> Color in - DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } - return Color.clear - }) - .useScrollView(shouldUseScrollView(), minHeight: frame.height) + AutofillViews.Description(text: UserText.autofillPasswordGenerationPromptSubtitle) + contentViewSpacer + ctaView + .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad + : Const.Size.bottomPadding) + } + .background(GeometryReader { proxy -> Color in + DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } + return Color.clear + }) + .useScrollView(shouldUseScrollView(), minHeight: frame.height) } .padding(.horizontal, horizontalPadding) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index ca0a7e8886..8a769e5c59 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -728,6 +728,32 @@ But if you *do* want a peek under the hood, you can find more information about public static let autofillResetNeverSavedActionConfirmButton = NSLocalizedString("autofill.logins.list.never.saved.reset.action.confirm", value: "Reset Excluded Sites", comment: "Confirm button to reset list of never saved sites") public static let autofillResetNeverSavedActionCancelButton = NSLocalizedString("autofill.logins.list.never.saved.reset.action.cancel", value: "Cancel", comment: "Cancel button for resetting list of never saved sites") + public static let autofillLoginListToolbarDeleteAllButton = NSLocalizedString("autofill.logins.list.delete.all", value:"Delete All", comment: "Title for button to delete all saved autofill passwords") + public static func autofillLoginListToolbarPasswordsCount(_ count: Int) -> String { + let message = NSLocalizedString("autofill.number.of.passwords", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count) + } + public static func autofillDeleteAllPasswordsActionTitle(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.confirmation.title", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count) + } + public static func autofillDeleteAllPasswordsActionMessage(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.confirmation.body", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count, count) + } + public static func autofillDeleteAllPasswordsSyncActionMessage(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.sync.confirmation.body", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count, count) + } + + public static let autofillDeleteAllPasswordsAuthenticationPromptTitle = NSLocalizedString("autofill.logins.delete.all.authentication.prompt.title", value: "Authenticate To Delete All Passwords", comment: "Title of prompt requiring authentication before all passwords are deleted") + public static let autofillDeleteAllPasswordsAuthenticationPromptButton = NSLocalizedString("autofill.logins.delete.all.authentication.prompt.button", value: "Authenticate Now", comment: "Title of button in prompt requiring authentication before all passwords are deleted") + public static let autofillDeleteAllPasswordsAuthenticationReason = NSLocalizedString("autofill.logins.delete.all.authentication.reason", value:"Authenticate to confirm you want to delete all passwords", comment: "Reason for authentication when deleting all logins") + public static func autofillAllPasswordsDeletedToastMessage(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.completion", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count) + } + public static let autofillLoginPromptAuthenticationCancelButton = NSLocalizedString("autofill.logins.prompt.auth.cancel", value:"Cancel", comment: "Cancel button for auth during login prompt") public static let autofillLoginPromptAuthenticationReason = NSLocalizedString("autofill.logins.prompt.auth.reason", value:"Unlock to use saved password", comment: "Reason for auth during login prompt") public static let autofillLoginPromptTitle = NSLocalizedString("autofill.logins.prompt.title", value:"Use a saved password?", comment: "Title for autofill login prompt") @@ -766,7 +792,6 @@ But if you *do* want a peek under the hood, you can find more information about public static let autofillLoginDetailsEditTitle = NSLocalizedString("autofill.logins.details.edit-title", value:"Edit Password", comment: "Title when editing autofill login details") public static let autofillLoginDetailsNewTitle = NSLocalizedString("autofill.logins.details.new-title", value:"Add password", comment: "Title when adding new autofill login") public static let autofillLoginDetailsDeleteButton = NSLocalizedString("autofill.logins.details.delete", value:"Delete Password", comment: "Delete button when deleting an autofill login") - public static let autofillLoginDetailsDeleteConfirmationTitle = NSLocalizedString("autofill.logins.details.delete-confirmation.title", value:"Are you sure you want to delete this password?", comment: "Title of confirmation alert when deleting an autofill login") public static let autofillLoginDetailsDeleteConfirmationButtonTitle = NSLocalizedString("autofill.logins.details.delete-confirmation.button", value:"Delete Password", comment: "Autofill alert button confirming delete autofill login") public static func autofillLoginListLoginDeletedToastMessage(for title: String) -> String { diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index 4813d1983a..52ec7e2e9d 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL адресът е копиран"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Изтриване"; /* Disable protection action */ "action.title.disable.protection" = "Деактивиране на защита на поверителността"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Когато DuckDuckGo открие изскачащи прозорци за съгласие за използване на бисквитки в сайтовете, които посещавате, можем да опитаме автоматично да настроим предпочитанията Ви за бисквитки, така че бисквитките да са сведени до минимум, а поверителността да е максимална, след което да затворим изскачащите прозорци. Някои сайтове не предоставят възможност за управление на предпочитанията за бисквитки, затова можем само да скрием техните изскачащи прозорци."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Активиране на Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Потребителското име е копирано"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Удостоверяване сега"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Удостоверете се, за да изтриете всички пароли"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Удостоверете се, за да потвърдите, че искате да изтриете всички пароли"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Адрес на уебсайта"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Изтриване на парола"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Сигурни ли сте, че искате да изтриете тази парола?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Редактиране на парола"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Затваряне"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Изтриване на всички"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Запазване и автоматично попълване на паролите"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Няма резултати"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Паролите се съхраняват сигурно на Вашето устройство."; diff --git a/DuckDuckGo/bg.lproj/Localizable.stringsdict b/DuckDuckGo/bg.lproj/Localizable.stringsdict index 31ab40f11b..b39ecd6dc3 100644 --- a/DuckDuckGo/bg.lproj/Localizable.stringsdict +++ b/DuckDuckGo/bg.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d парола + other + %1$d пароли + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Сигурни ли сте, че искате да изтриете %1$d парола? + other + Сигурни ли сте, че искате да изтриете %1$d пароли? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Вашите пароли ще бъдат изтрити от това устройство. Уверете се, че все още имате друг начин за достъп до Вашия %2$#@accounts@. + other + Вашите пароли ще бъдат изтрити от това устройство. Уверете се, че все още имате друг начин за достъп до Вашите %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + акаунт + other + акаунти + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Вашите пароли ще бъдат изтрити от всички синхронизирани устройства. Уверете се, че все още имате друг начин за достъп до Вашия %2$#@accounts@. + other + Вашите пароли ще бъдат изтрити от всички синхронизирани устройства. Уверете се, че все още имате друг начин за достъп до Вашите %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + акаунт + other + акаунти + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d изтрита парола + other + %1$d изтрити пароли + + diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index c3b5ed0be1..876f184fb9 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Adrese URL zkopírována"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Smazat"; /* Disable protection action */ "action.title.disable.protection" = "Zakázat ochranu soukromí"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Když DuckDuckGo na navštívené stránce zjistí vyskakovací okno žádající o souhlas se soubory cookie, můžeme zkusit automaticky nastavit tvou předvolbu na co nejméně souborů cookie a maximální ochranu soukromí a potom vyskakovací okno zavřít. Některé weby nenabízejí možnost si nastavit předvolby souborů cookie, takže podobná vyskakovací okna můžeme jen skrýt."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Povol funkci Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Uživatelské jméno zkopírované"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Ověřit"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Pro smazání všech hesel proveď ověření"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Ověřením potvrď, že chceš smazat všechna hesla"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL webové stránky"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Smazat heslo"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Opravdu chceš smazat tohle heslo?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Upravit heslo"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zavřít"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Smazat vše"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Ukládání a automatické vyplňování hesel"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Žádné výsledky"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Hesla se bezpečně ukládají do tvého zařízení."; diff --git a/DuckDuckGo/cs.lproj/Localizable.stringsdict b/DuckDuckGo/cs.lproj/Localizable.stringsdict index 5d4152ff8d..001afa04dd 100644 --- a/DuckDuckGo/cs.lproj/Localizable.stringsdict +++ b/DuckDuckGo/cs.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Zablokovali jsme je. d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d heslo + few + %1$d hesla + many + %1$d hesla + other + %1$d hesel + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Opravdu chceš smazat %1$d heslo? + few + Opravdu chceš smazat %1$d hesla? + many + Opravdu chceš smazat %1$d hesla? + other + Opravdu chceš smazat %1$d hesel? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoje heslo se z tohohle zařízení smaže. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + few + Tvoje hesla se z tohohle zařízení smažou. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + many + Tvoje hesla se z tohohle zařízení smažou. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + other + Tvoje hesla se z tohohle zařízení smažou. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účtu + few + účtům + many + účtům + other + účtům + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoje heslo se smaže ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + few + Tvoje hesla se smažou ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + many + Tvoje hesla se smažou ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + other + Tvoje hesla se smažou ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účtu + few + účtům + many + účtům + other + účtům + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d smazané heslo + few + %1$d smazaná hesla + many + %1$d smazaného hesla + other + %1$d smazaných hesel + + diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 5175783178..b9b4c0fdcf 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL kopieret"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Slet"; /* Disable protection action */ "action.title.disable.protection" = "Deaktiver Beskyttelse af privatlivet"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Når DuckDuckGo registrerer pop op-vinduer med samtykke til brug af cookies på websteder, du besøger, kan vi automatisk indstille dine cookiepræferencer til at minimere cookies og maksimere privatlivets fred og derefter lukke pop op-vinduer. Nogle websteder giver ikke mulighed for at administrere cookiepræferencer, så vi kan kun skjule pop op-vinduer som disse."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Aktivér Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Brugernavn kopieret"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Godkend nu"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Godkend for at slette alle adgangskoder"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Bekræft, at du vil slette alle adgangskoder"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL til webstedet"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Slet adgangskode"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Er du sikker på, at du vil slette denne adgangskode?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Rediger adgangskode"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Luk"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Slet alle"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Gem og udfyld adgangskoder automatisk"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ingen resultater"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Adgangskoder gemmes sikkert på din enhed."; diff --git a/DuckDuckGo/da.lproj/Localizable.stringsdict b/DuckDuckGo/da.lproj/Localizable.stringsdict index f7962120bd..e2ddc6808d 100644 --- a/DuckDuckGo/da.lproj/Localizable.stringsdict +++ b/DuckDuckGo/da.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Jeg blokerede dem! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d adgangskode + other + %1$d adgangskoder + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Er du sikker på, at du vil slette %1$d adgangskode? + other + Er du sikker på, at du vil slette %1$d adgangskoder? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Din adgangskode slettes fra denne enhed. Sørg for, at du stadig har en måde at få adgang til din %2$#@accounts@. + other + Dine adgangskoder slettes fra denne enhed. Sørg for, at du stadig har en måde at få adgang til dine %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konti + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Din adgangskode slettes fra alle synkroniserede enheder. Husk at sikre, at du stadig har adgang til dine konti.%2$#@accounts@ + other + Dine adgangskoder slettes fra alle synkroniserede enheder. Husk at sikre, at du stadig har adgang til dine konti.%2$#@accounts@ + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konti + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d adgangskode slettet + other + %1$d adgangskoder slettet + + diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 6dd24b2bb8..f8fe8a095c 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL wurde kopiert"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Löschen"; /* Disable protection action */ "action.title.disable.protection" = "Datenschutz deaktivieren"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Wenn DuckDuckGo Pop-ups zur Cookie-Zustimmung auf den von dir besuchten Websites erkennt, können wir deine Cookie-Einstellungen automatisch so festlegen, dass Cookies minimiert werden und der Datenschutz maximiert wird, und dann die Pop-ups schließen. Einige Websites bieten keine Option zur Verwaltung von Cookie-Einstellungen, sodass wir Pop-ups wie diese nur ausblenden können."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Email Protection aktivieren"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Benutzername kopiert"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Jetzt authentifizieren"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Authentifiziere dich, um alle Passwörter zu löschen"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Authentifiziere dich, um zu bestätigen, dass du alle Passwörter löschen willst"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL der Website"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Passwort löschen"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Möchtest du dieses Passwort wirklich löschen?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Passwort bearbeiten"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Schließen"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Alle löschen"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Passwörter speichern und automatisch ausfüllen"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Keine Ergebnisse"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Passwörter werden sicher auf deinem Gerät gespeichert."; diff --git a/DuckDuckGo/de.lproj/Localizable.stringsdict b/DuckDuckGo/de.lproj/Localizable.stringsdict index cb2af7bb99..110ed364d5 100644 --- a/DuckDuckGo/de.lproj/Localizable.stringsdict +++ b/DuckDuckGo/de.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Ich habe sie blockiert! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d Passwort + other + %1$d Passwörter + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Möchtest du dieses %1$d Passwort wirklich löschen? + other + Möchtest du diese %1$d Passwörter wirklich löschen? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Dein Passwort wird von diesem Gerät gelöscht. Stelle sicher, dass du immer noch eine Möglichkeit hast, auf dein %2$#@accounts@ zuzugreifen. + other + Deine Passwörter werden von diesem Gerät gelöscht. Stelle sicher, dass du immer noch eine Möglichkeit hast, auf deine %2$#@accounts@ zuzugreifen. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Konto + other + Konten + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Dein Passwort wird von allen synchronisierten Geräten gelöscht. Vergewissere dich, dass du weiterhin eine Möglichkeit hast, auf deine %2$#@accounts@ zuzugreifen. + other + Deine Passwörter werden von allen synchronisierten Geräten gelöscht. Vergewissere dich, dass du weiterhin eine Möglichkeit hast, auf deine %2$#@accounts@ zuzugreifen. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Konto + other + Konten + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d Passwort gelöscht + other + %1$d Passwörter gelöscht + + diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 1b49494fe1..50ae42fd35 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Η διεύθυνση URL αντιγράφτηκε"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Διαγραφή"; /* Disable protection action */ "action.title.disable.protection" = "Απενεργοποίηση Προστασίας προσωπικών δεδομένων"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Όταν το DuckDuckGo εντοπίζει αναδυόμενα παράθυρα συναίνεσης για cookies σε ιστότοπους που επισκέπτεστε, μπορούμε να προσπαθήσουμε να ρυθμίσουμε αυτόματα τις προτιμήσεις σας για τα cookies ώστε να ελαχιστοποιήσετε τα cookies και να μεγιστοποιήσετε το απόρρητο. Έπειτα μπορείτε να κλείσετε τα αναδυόμενα παράθυρα. Ορισμένοι ιστότοποι δεν παρέχουν επιλογή για διαχείριση των προτιμήσεων cookies, οπότε μπορούμε μόνο να αποκρύψουμε αναδυόμενα παράθυρα όπως αυτά."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Ενεργοποίηση Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Το όνομα χρήστη αντιγράφηκε"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Έλεγχος ταυτότητας τώρα"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Έλεγχος ταυτότητας για διαγραφή όλων των κωδικών πρόσβασης"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Επαληθεύστε την ταυτότητά σας για να επιβεβαιώσετε ότι θέλετε να διαγράψετε όλους τους κωδικούς πρόσβασης"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Διεύθυνση URL ιστότοπου"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Διαγραφή κωδικού πρόσβασης"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Θέλετε σίγουρα να διαγράψετε αυτόν τον κωδικό πρόσβασης;"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Επεξεργασία κωδικού πρόσβασης"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Κλείσιμο"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Διαγραφή όλων"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Αποθήκευση και αυτόματη συμπλήρωση κωδικών πρόσβασης"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Κανένα αποτέλεσμα"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Οι κωδικοί πρόσβασης αποθηκεύονται με ασφάλεια στη συσκευή σας."; diff --git a/DuckDuckGo/el.lproj/Localizable.stringsdict b/DuckDuckGo/el.lproj/Localizable.stringsdict index 5483feb8f1..1bf4eaf174 100644 --- a/DuckDuckGo/el.lproj/Localizable.stringsdict +++ b/DuckDuckGo/el.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d κωδικός πρόσβασης + other + %1$d κωδικοί πρόσβασης + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Θέλετε σίγουρα να διαγράψετε %1$d κωδικούς πρόσβασης; + other + Θέλετε σίγουρα να διαγράψετε %1$d κωδικούς πρόσβασης; + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ο κωδικός πρόσβασής σας θα διαγραφεί από αυτήν τη συσκευή. Βεβαιωθείτε ότι εξακολουθείτε να έχετε έναν τρόπο πρόσβασης στο %2$#@accounts@. + other + Οι κωδικοί πρόσβασής σας θα διαγραφούν από αυτή τη συσκευή. Βεβαιωθείτε ότι εξακολουθείτε να έχετε έναν τρόπο πρόσβασης στο %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + λογαριασμό + other + λογαριασμούς + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ο κωδικός πρόσβασής σας θα διαγραφεί από όλες τις συγχρονισμένες συσκευές. Βεβαιωθείτε ότι έχετε ακόμα τρόπο πρόσβασης στους %2$#@accounts@ σας. + other + Οι κωδικοί πρόσβασής σας θα διαγραφούν από όλες τις συγχρονισμένες συσκευές. Βεβαιωθείτε ότι έχετε ακόμα τρόπο πρόσβασης στους %2$#@accounts@ σας. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + λογαριασμό + other + λογαριασμούς + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d κωδικός πρόσβασης διαγράφηκε + other + %1$d κωδικοί πρόσβασης διαγράφηκαν + + diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index cc817d9c34..4459952b4f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "When DuckDuckGo detects cookie consent pop-ups on sites you visit, we can try to automatically set your cookie preferences to minimize cookies and maximize privacy, then close the pop-ups. Some sites don't provide an option to manage cookie preferences, so we can only hide pop-ups like these."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Enable Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Username copied"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Authenticate Now"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Authenticate To Delete All Passwords"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Authenticate to confirm you want to delete all passwords"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Website URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Delete Password"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Are you sure you want to delete this password?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Edit Password"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Close"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Delete All"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Save and autofill passwords"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "No Results"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Passwords are stored securely on your device."; diff --git a/DuckDuckGo/en.lproj/Localizable.stringsdict b/DuckDuckGo/en.lproj/Localizable.stringsdict index 695e58187d..df96547b47 100644 --- a/DuckDuckGo/en.lproj/Localizable.stringsdict +++ b/DuckDuckGo/en.lproj/Localizable.stringsdict @@ -194,5 +194,107 @@ I blocked them! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d passwords + one + %d password + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Are you sure you want to delete %d passwords? + one + Are you sure you want to delete this password? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Your passwords will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. + one + Your password will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + accounts + one + account + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Your passwords will be deleted from all synced devices. Make sure you still have a way to access your %2$#@accounts@. + one + Your password will be deleted from all synced devices. Make sure you still have a way to access your %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + accounts + one + account + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d passwords deleted + one + %d password deleted + + diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 3d79ed55c8..bd06ea81a3 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiada"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Eliminar"; /* Disable protection action */ "action.title.disable.protection" = "Desactivar la protección de privacidad"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Cuando DuckDuckGo detecta ventanas emergentes de consentimiento de cookies en los sitios que visitas, podemos configurar automáticamente tus preferencias de cookies para minimizar las cookies y maximizar la privacidad y, a continuación, cerrar las ventanas emergentes. Algunos sitios no ofrecen la opción de administrar las preferencias de cookies, por lo que solo podemos ocultar ventanas emergentes de este tipo."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Activar Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nombre de usuario copiado"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autenticar ahora"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autenticar para borrar todas las contraseñas"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentícate para confirmar que deseas borrar todas las contraseñas"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL del sitio web"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Eliminar contraseña"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "¿Seguro de que quieres borrar esta contraseña?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Editar contraseña"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Cerrar"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Eliminar todo"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Guardar y autocompletar contraseñas"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Sin resultados"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Las contraseñas se almacenan de forma segura en tu dispositivo."; diff --git a/DuckDuckGo/es.lproj/Localizable.stringsdict b/DuckDuckGo/es.lproj/Localizable.stringsdict index 9f7c52c3d0..48951c37f0 100644 --- a/DuckDuckGo/es.lproj/Localizable.stringsdict +++ b/DuckDuckGo/es.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d contraseña + other + %1$d contraseñas + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + ¿Seguro de que quieres borrar %1$d contraseña? + other + ¿Seguro de que quieres borrar %1$d contraseñas? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tu contraseña se eliminará de este dispositivo. Asegúrate de tener otra forma de acceder a %2$#@accounts@. + other + Tus contraseñas se eliminarán de este dispositivo. Asegúrate de tener otra forma de acceder a %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + cuenta + other + cuentas + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tus contraseñas se eliminarán de todos los dispositivos sincronizados. Asegúrate de tener otra forma de acceder a %2$#@accounts@. + other + Tus contraseñas se eliminarán de todos los dispositivos sincronizados. Asegúrate de tener otra forma de acceder a%2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + cuenta + other + cuentas + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d contraseña borrada + other + %1$d contraseñas borradas + + diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index d0c8c88798..59cb7f715a 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL on kopeeritud"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Kustuta"; /* Disable protection action */ "action.title.disable.protection" = "Keela privaatsuse kaitse"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kui DuckDuckGo tuvastab külastatavatel veebisaitidel küpsiste nõusoleku hüpikaknad, saame proovida automaatselt seadistada sinu küpsise-eelistused selliselt, et minimeerida küpsised ja maksimeerida privaatsust, ning seejärel hüpikaknad sulgeda. Mõned saidid ei paku võimalust küpsise-eelistuste haldamiseks, seega saame sellised hüpikaknad ainult peita."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Luba Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Kasutajanimi on kopeeritud"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentige nüüd"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentige kõigi paroolide kustutamiseks"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autendi end, et kinnitada kõigi paroolide kustutamine"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Veebisaidi URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Kustuta parool"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Kas oled kindel, et soovid selle parooli kustutada?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Parooli muutmine"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Sulge"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Kustuta kõik"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Paroolide salvestamine ja automaatne sisestamine"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Tulemusi pole"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Paroolid salvestatakse turvaliselt sinu seadmesse."; diff --git a/DuckDuckGo/et.lproj/Localizable.stringsdict b/DuckDuckGo/et.lproj/Localizable.stringsdict index 16dd9cc09c..5e456c49c0 100644 --- a/DuckDuckGo/et.lproj/Localizable.stringsdict +++ b/DuckDuckGo/et.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Blokeerisin nad! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parool + other + %1$d parooli + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Kas soovid kindlasti %1$d parooli kustutada? + other + Kas soovid kindlasti %1$d parooli kustutada? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Teie parool kustutatakse sellest seadmest. Veenduge, et teil on endiselt võimalus oma %2$#@accounts@ juurde pääseda. + other + Teie paroolid kustutatakse sellest seadmest. Veenduge, et teil on endiselt võimalus pääseda ligi oma %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontod + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Teie parool kustutatakse kõikidest sünkroonitud seadmetest. Veenduge, et teil on endiselt võimalus oma %2$#@accounts@ juurde pääseda. + other + Teie paroolid kustutatakse kõikidest sünkroonitud seadmetest. Veenduge, et teil on endiselt võimalus oma %2$#@accounts@ juurde pääseda. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontod + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parool kustutatud + other + %1$d parooli kustutatud + + diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index d6410ae06a..ae08f1f9b8 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL kopioitu"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Poista"; /* Disable protection action */ "action.title.disable.protection" = "Poista yksityisyyden suoja käytöstä"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kun DuckDuckGo havaitsee evästeiden hallinnan ponnahdusikkunoita vierailemillasi sivustoilla, voimme yrittää määrittää evästeasetuksesi automaattisesti evästeiden minimoimiseksi ja yksityisyyden maksimoimiseksi ja sulkea sitten ponnahdusikkunat. Joillakin sivustoilla evästeiden hallinta ei ole mahdollista, joten voimme vain piilottaa tällaiset ponnahdusikkunat."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Ota Email Protection käyttöön"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Käyttäjätunnus kopioitu"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Todenna nyt"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Todenna poistaaksesi kaikki salasanat"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Vahvista, että haluat poistaa kaikki salasanat."; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Sivuston URL-osoite"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Poista salasana"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Haluatko varmasti poistaa tämän salasanan?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Muokkaa salasanaa"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Sulje"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Poista kaikki"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Tallenna ja täytä salasanat automaattisesti"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ei tuloksia"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Salasanat tallennetaan laitteellesi turvallisesti."; diff --git a/DuckDuckGo/fi.lproj/Localizable.stringsdict b/DuckDuckGo/fi.lproj/Localizable.stringsdict index ff02b2e383..44958a4bcf 100644 --- a/DuckDuckGo/fi.lproj/Localizable.stringsdict +++ b/DuckDuckGo/fi.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d salasana + other + %1$d salasanaa + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Haluatko varmasti poistaa %1$d salasanan? + other + Haluatko varmasti poistaa %1$d salasanaa? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Salasanasi poistetaan tästä laitteesta. Varmista, että pääset yhä käyttämään %2$#@accounts@. + other + Salasanasi poistetaan tästä laitteesta. Varmista, että pääset yhä käyttämään %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + tiliäsi + other + tilejäsi + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Salasanasi poistetaan kaikista synkronoiduista laitteista. Varmista, että pääset yhä käyttämään %2$#@accounts@. + other + Salasanasi poistetaan kaikista synkronoiduista laitteista. Varmista, että pääset yhä käyttämään %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + tiliäsi + other + tilejäsi + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d salasana poistettu + other + %1$d salasanaa poistettu + + diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 75303ca4ee..8a65d815c2 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiée"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Supprimer"; /* Disable protection action */ "action.title.disable.protection" = "Désactiver la protection de la confidentialité"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Lorsque DuckDuckGo détecte les fenêtres contextuelles de consentement aux cookies sur les sites que vous visitez, vos préférences peuvent, dans la mesure du possible, être configurées automatiquement de façon à ce que les cookies soient réduits au minimum et la confidentialité maximisée, puis les fenêtres contextuelles sont fermées. Certains sites ne proposent pas d'option pour gérer les préférences en matière de cookies. Nous ne pouvons donc masquer que des fenêtres contextuelles comme celles-ci."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Activez Email Protection"; @@ -458,7 +470,7 @@ "autofill.keep-enabled.alert.disable" = "Désactiver"; /* Confirm action for alert when asking the user if they want to keep using autofill */ -"autofill.keep-enabled.alert.keep-using" = "Encore des économies"; +"autofill.keep-enabled.alert.keep-using" = "Continuer à les sauvegarder"; /* Message for alert when asking the user if they want to keep using autofill */ "autofill.keep-enabled.alert.message" = "Cette fonction peut être désactivée à tout moment dans Paramètres."; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nom d'utilisateur copié"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Authentifiez-vous maintenant"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Authentifiez-vous pour supprimer tous les mots de passe"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Authentifiez-vous pour confirmer que vous souhaitez supprimer tous les mots de passe"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL du site Web"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Supprimer le mot de passe"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Voulez-vous vraiment supprimer ce mot de passe ?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Modifier le mot de passe"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Fermer"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Tout supprimer"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Enregistrer et saisir automatiquement les mots de passe"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Aucun résultat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Les mots de passe sont stockés en toute sécurité sur votre appareil."; diff --git a/DuckDuckGo/fr.lproj/Localizable.stringsdict b/DuckDuckGo/fr.lproj/Localizable.stringsdict index 07e2c102c8..bc855b5152 100644 --- a/DuckDuckGo/fr.lproj/Localizable.stringsdict +++ b/DuckDuckGo/fr.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Je les ai bloqués ! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d mot de passe + other + %1$d mots de passe + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Voulez-vous vraiment supprimer %1$d mot de passe ? + other + Voulez-vous vraiment supprimer %1$d mots de passe ? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Votre mot de passe sera supprimé de cet appareil. Assurez-vous d'avoir toujours un moyen d'accéder à votre %2$#@accounts@. + other + Vos mots de passe seront supprimés de cet appareil. Assurez-vous d'avoir toujours un moyen d'accéder à vos %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + compte + other + comptes + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Votre mot de passe sera supprimé de tous les appareils synchronisés. Assurez-vous d'avoir toujours un moyen d'accéder à votre %2$#@accounts@. + other + Vos mots de passe seront supprimés de tous les appareils synchronisés. Assurez-vous d'avoir toujours un moyen d'accéder à vos %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + compte + other + comptes + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d mot de passe supprimé + other + %1$d mots de passe supprimés + + diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index c85e257952..e60babbc56 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL je kopiran"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Izbriši"; /* Disable protection action */ "action.title.disable.protection" = "Onemogući zaštitu privatnosti"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kada DuckDuckGo otkrije skočne prozore pristanka na kolačiće na web lokacijama koje posjećuješ, mi možemo pokušati automatski odrediti tvoje postavke kolačića kako bismo minimizirali kolačiće i maksimizirali privatnost, a zatim zatvorili skočne prozore. Neke web lokacije ne nude opciju upravljanja postavkama kolačića, tako da možemo sakriti samo ovakve skočne prozore."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Omogući Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Korisničko ime je kopirano"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Potvrdi autentičnost"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Potvrdi autentičnost za brisanje svih lozinki"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Izvrši autentifikaciju i time potvrdi da želiš izbrisati sve lozinke"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL web lokacije"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Izbriši lozinku"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Sigurno želiš izbrisati ovu lozinku?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Uredi lozinku"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zatvori"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Izbriši sve"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Spremi i automatski popuni lozinke"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nema rezultata"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Lozinke su sigurno pohranjene na tvom uređaju."; diff --git a/DuckDuckGo/hr.lproj/Localizable.stringsdict b/DuckDuckGo/hr.lproj/Localizable.stringsdict index 88cd477d50..2d3c124795 100644 --- a/DuckDuckGo/hr.lproj/Localizable.stringsdict +++ b/DuckDuckGo/hr.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Ja sam ih blokirao! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d lozinka + few + %1$d lozinke + many + %1$d lozinki + other + %1$d lozinki + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Sigurno želiš izbrisati %1$d lozinku? + few + Sigurno želiš izbrisati %1$d lozinke? + many + Sigurno želiš izbrisati %1$d lozinki? + other + Sigurno želiš izbrisati %1$d lozinki? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoja lozinka bit će izbrisana s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + few + Tvoje lozinke bit će izbrisane s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + many + Tvoje lozinke bit će izbrisane s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + other + Tvoje lozinke bit će izbrisane s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + račun + few + računa + many + računa + other + računa + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoja lozinka bit će izbrisana sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa %2$#@accounts@ + few + Tvoje lozinke bit će izbrisane sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa svojim %2$#@accounts@ + many + Tvoje lozinke bit će izbrisane sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa svojim %2$#@accounts@ + other + Tvoje lozinke bit će izbrisane sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa svojim %2$#@accounts@ + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + računu + few + računima + many + računima + other + računima + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Izbrisana je %1$d lozinka + few + Izbrisane su %1$d lozinke + many + Izbrisano je %1$d lozinki + other + Izbrisano je %1$d lozinki + + diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 45a918ddae..e766dd8db6 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL lemásolva"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Törlés"; /* Disable protection action */ "action.title.disable.protection" = "Adatvédelem letiltása"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Amikor a DuckDuckGo a meglátogatott webhelyeken sütik elfogadására szolgáló felugró ablakokat észlel, a sütik minimalizálása és az adatvédelem maximalizálása érdekében automatikusan megpróbálhatjuk megadni a sütikre vonatkozó beállításaidat, és bezárhatjuk a felugró ablakokat. Egyes webhelyek nem adnak lehetőséget a sütikre vonatkozó beállítások kezelésére, így csak az ilyen felugró ablakokat tudjuk elrejteni."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "E-mail-védelem engedélyezése"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Felhasználónév másolva"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Hitelesítés"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Hitelesítés minden jelszó törléséhez"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Hitelesítéssel erősítsd meg, hogy minden jelszót törölni szeretnél"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Webhely URL-címe"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Jelszó törlése"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Biztosan törlöd ezt a jelszót?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Jelszó szerkesztése"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Bezárás"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Összes törlése"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Jelszavak mentése és automatikus kitöltése"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nincs találat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "A jelszavakat az eszközöd biztonságosan tárolja."; diff --git a/DuckDuckGo/hu.lproj/Localizable.stringsdict b/DuckDuckGo/hu.lproj/Localizable.stringsdict index 06695acfb1..0c212bd7ea 100644 --- a/DuckDuckGo/hu.lproj/Localizable.stringsdict +++ b/DuckDuckGo/hu.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Blokkoltam őket! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d jelszó + other + %1$d jelszó + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Biztosan törölni szeretnél %1$d jelszót? + other + Biztosan törölni szeretnél %1$d jelszót? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A jelszó törölve lesz erről az eszközről. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + other + A jelszavak törölve lesznek erről az eszközről. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + fiókodhoz + other + fiókjaidhoz + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A jelszó minden szinkronizált eszközről törölve lesz. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + other + A jelszavak minden szinkronizált eszközről törölve lesznek. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + fiókodhoz + other + fiókjaidhoz + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d jelszó törölve + other + %1$d jelszó törölve + + diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index bb2ac6be4c..9299050613 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiato"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Cancella"; /* Disable protection action */ "action.title.disable.protection" = "Disattiva la tutela della privacy"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Quando DuckDuckGo rileva la presenza di popup per il consenso ai cookie sui siti che visiti, può tentare di impostare automaticamente le tue preferenze in modo da ridurre al minimo i cookie e aumentare la privacy, chiudendo poi i popup. Alcuni siti non forniscono un'opzione di gestione delle preferenze sui cookie, quindi possiamo nascondere solo popup come questi."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Abilita Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nome utente copiato"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autenticati adesso"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Effettua l'autenticazione per eliminare tutte le password"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Esegui l'autenticazione per confermare l'eliminazione di tutte le password"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL del sito Web"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Elimina password"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Eliminare questa password?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Modifica password"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Chiudi"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Elimina tutto"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Salva e compila automaticamente le password"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nessun risultato"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Le password sono archiviate in modo sicuro sul tuo dispositivo."; diff --git a/DuckDuckGo/it.lproj/Localizable.stringsdict b/DuckDuckGo/it.lproj/Localizable.stringsdict index 5b242a0c34..34ecb1249d 100644 --- a/DuckDuckGo/it.lproj/Localizable.stringsdict +++ b/DuckDuckGo/it.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Li ho bloccati! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d password + other + %1$d password + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Eliminare %1$d password? + other + Eliminare %1$d password? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Le password verranno eliminate da questo dispositivo. Assicurati di avere ancora la possibilità di accedere al tuo %2$#@accounts@. + other + Le password verranno eliminate da questo dispositivo. Assicurati di avere ancora la possibilità di accedere ai tuoi %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + account + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Le password verranno eliminate da tutti i dispositivi sincronizzati. Assicurati di avere ancora la possibilità di accedere al tuo %2$#@accounts@. + other + Le password verranno eliminate da tutti i dispositivi sincronizzati. Assicurati di avere ancora la possibilità di accedere ai tuoi %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + account + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d password eliminata + other + %1$d password eliminate + + diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 0b243fbd45..16acb6bb3d 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL nukopijuotas"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Trinti"; /* Disable protection action */ "action.title.disable.protection" = "Išjungti privatumo apsaugą"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kai „DuckDuckGo“ jūsų lankomose svetainėse aptinka sutikimo su slapukais iškylančiuosius langus, galime automatiškai nustatyti jūsų slapukų nuostatas, kad sumažintume slapukų skaičių, sustiprintume privatumą ir uždarytume iškylančiuosius langus. Kai kuriose svetainėse nėra galimybės tvarkyti slapukų nuostatas, todėl galime tik paslėpti tokius iškylančiuosius langus."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Įjungti „Email Protection“"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Naudotojo vardas nukopijuotas"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Patvirtinkite autentiškumą dabar"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Patvirtinkite autentiškumą, kad ištrintumėte visus slaptažodžius"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Patvirtinkite tapatybę, kad patvirtintumėte, jog norite ištrinti visus slaptažodžius"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Svetainės URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Ištrinti slaptažodį"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Ar tikrai norite ištrinti šį slaptažodį?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Redaguoti slaptažodį"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Uždaryti"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Ištrinti viską"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Išsaugokite ir automatiškai užpildykite slaptažodžius"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Rezultatų nerasta"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Slaptažodžiai saugiai saugomi jūsų įrenginyje."; diff --git a/DuckDuckGo/lt.lproj/Localizable.stringsdict b/DuckDuckGo/lt.lproj/Localizable.stringsdict index b0614a1ab2..feddc77cf3 100644 --- a/DuckDuckGo/lt.lproj/Localizable.stringsdict +++ b/DuckDuckGo/lt.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Užblokavau juos! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d slaptažodžis + few + %1$d slaptažodžiai + many + %1$d slaptažodžio + other + %1$d slaptažodžių + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ar tikrai norite ištrinti %1$d slaptažodį? + few + Ar tikrai norite ištrinti %1$d slaptažodžius? + many + Ar tikrai norite ištrinti %1$d slaptažodžio? + other + Ar tikrai norite ištrinti %1$d slaptažodžių? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + few + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + many + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + other + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + sąskaita + few + sąskaitos + many + sąskaitos + other + sąskaitų + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyrą. + few + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyras. + many + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyros. + other + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyrų. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + sąskaita + few + sąskaitos + many + sąskaitos + other + sąskaitų + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d slaptažodis ištrintas + few + %1$d slaptažodžiai ištrinti + many + %1$d slaptažodžio ištrinta + other + %1$d slaptažodžių ištrinti + + diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index c163f07853..211afd152a 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL ir nokopēts"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Dzēst"; /* Disable protection action */ "action.title.disable.protection" = "Atspējot privātuma aizsardzību"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Ja DuckDuckGo tevis apmeklētajās vietnēs atklāj sīkfailu piekrišanas uznirstošos logus, mēs varam automātiski iestatīt tavas sīkfailu preferences, lai samazinātu sīkfailus un stiprinātu privātumu, un pēc tam aizvērt šos uznirstošos logus. Dažās vietnēs nav iespējas pārvaldīt sīkfailu preferences, tāpēc mēs varam tikai paslēpt šādus uznirstošos logus."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Iespējo e-pasta aizsardzību"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Lietotājvārds nokopēts"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentificēt tagad"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentificē, lai dzēstu visas paroles"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentificējies, lai apstiprinātu, ka vēlies dzēst visas paroles"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Tīmekļa vietnes URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Dzēst paroli"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Vai tiešām vēlies dzēst šo paroli?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Paroles rediģēšana"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Aizvērt"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Dzēst visus"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Saglabāt un automātiski aizpildīt paroles"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nav rezultātu"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Paroles tiek droši glabātas tavā ierīcē."; diff --git a/DuckDuckGo/lv.lproj/Localizable.stringsdict b/DuckDuckGo/lv.lproj/Localizable.stringsdict index a4d519bc52..9e4a7ba870 100644 --- a/DuckDuckGo/lv.lproj/Localizable.stringsdict +++ b/DuckDuckGo/lv.lproj/Localizable.stringsdict @@ -172,5 +172,121 @@ Es tos nobloķēju! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + %1$d paroles + one + %1$d parole + other + %1$d paroles + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Vai tiešām vēlies dzēst %1$d paroles? + one + Vai tiešām vēlies dzēst %1$d paroli? + other + Vai tiešām vēlies dzēst %1$d paroles? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Tavas paroles tiks dzēstas no šīs ierīces. Pārliecinies, ka joprojām vari piekļūt saviem %2$#@accounts@ kontiem. + one + Tavas paroles tiks dzēstas no šīs ierīces. Pārliecinies, ka joprojām vari piekļūt savam %2$#@accounts@ kontam. + other + Tavas paroles tiks dzēstas no šīs ierīces. Pārliecinies, ka joprojām vari piekļūt saviem %2$#@accounts@ kontiem. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + konti + one + konts + other + konti + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Tavas paroles tiks dzēstas no visām sinhronizētajām ierīcēm. Pārliecinies, vai joprojām varēsi piekļūt saviem %2$#@accounts@ kontiem. + one + Tavas paroles tiks dzēstas no visām sinhronizētajām ierīcēm. Pārliecinies, vai joprojām varēsi piekļūt savam %2$#@accounts@ kontam. + other + Tavas paroles tiks dzēstas no visām sinhronizētajām ierīcēm. Pārliecinies, vai joprojām varēsi piekļūt saviem %2$#@accounts@ kontiem. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + konti + one + konts + other + konti + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + %1$d paroles izdzēstas + one + %1$d parole izdzēsta + other + %1$d paroles izdzēstas + + diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index f0880a9b16..d7ab1b2eed 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Nettadresse kopiert"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Slett"; /* Disable protection action */ "action.title.disable.protection" = "Deaktiver personvernbeskyttelse"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Når DuckDuckGo oppdager popup-vinduer om samtykke til informasjonskapsler på nettsteder du besøker, kan vi automatisk angi innstillingene for å minimere informasjonskapslene og maksimere personvernet, og deretter lukke popup-vinduene. Noen nettsteder tilbyr ikke noe alternativ for å administrere preferanser for informasjonskapsler, så slike popup-vinduer kan vi bare skjule."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Aktiver e-postbeskyttelse"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Brukernavnet er kopiert"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Godkjenn nå"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Godkjenn for å slette alle passord"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Godkjenn for å bekrefte at du vil slette alle passord"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL-adresse til nettsted"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Slett passord"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Er du sikker på at du vil slette dette passordet?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Rediger passordet"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Lukk"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Slett alt"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Lagre og fyll ut passord automatisk"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ingen resultater"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Passord lagres på enheten din på en sikker måte."; diff --git a/DuckDuckGo/nb.lproj/Localizable.stringsdict b/DuckDuckGo/nb.lproj/Localizable.stringsdict index 6a12a317f3..63a57f94f9 100644 --- a/DuckDuckGo/nb.lproj/Localizable.stringsdict +++ b/DuckDuckGo/nb.lproj/Localizable.stringsdict @@ -192,6 +192,109 @@ Jeg har blokkert dem! %d sporer funnet other %d sporere funnet + + + + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d passord + other + %1$d passord + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Er du sikker på at du vil slette %1$d passord? + other + Er du sikker på at du vil slette %1$d passord? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Passordet ditt blir slettet fra denne enheten. Sørg for at du fortsatt har tilgang til din %2$#@accounts@. + other + Passordene dine blir slettet fra denne enheten. Sørg for at du fortsatt har tilgang til dine %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontoer + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Passordet ditt blir slettet fra alle synkroniserte enheter. Sørg for at du fortsatt har tilgang til din %2$#@accounts@. + other + Passordene dine blir slettet fra alle synkroniserte enheter. Sørg for at du fortsatt har tilgang til dine %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontoer + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d passord er slettet + other + %1$d passord er slettet diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 11d462af04..a618d838f7 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL gekopieerd"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Verwijderen"; /* Disable protection action */ "action.title.disable.protection" = "Privacybescherming uitschakelen"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Wanneer DuckDuckGo pop-ups voor cookietoestemming op bezochte websites detecteert, kunnen we proberen automatisch je cookievoorkeuren in te stellen om het aantal cookies tot een minimum te beperken en je privacy te maximaliseren. De pop-ups worden vervolgens gesloten. Sommige sites bieden geen optie om cookievoorkeuren te beheren, zodat we pop-ups zoals deze alleen kunnen verbergen."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "E-mailbeveiliging inschakelen"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Gebruikersnaam gekopieerd"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Nu verifiëren"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Verifiëren om alle wachtwoorden te verwijderen"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Verifieer om te bevestigen dat je alle wachtwoorden wilt verwijderen"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL van de website"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Wachtwoord verwijderen"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Weet je zeker dat je dit wachtwoord wilt verwijderen?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Wachtwoord bewerken"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Sluiten"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Alles verwijderen"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Wachtwoorden opslaan en automatisch invullen"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Geen resultaten"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Wachtwoorden worden veilig opgeslagen op je apparaat."; diff --git a/DuckDuckGo/nl.lproj/Localizable.stringsdict b/DuckDuckGo/nl.lproj/Localizable.stringsdict index 0c1b4fc3a1..a8c322780f 100644 --- a/DuckDuckGo/nl.lproj/Localizable.stringsdict +++ b/DuckDuckGo/nl.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Ik heb ze geblokkeerd! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d wachtwoord + other + %1$d wachtwoorden + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + one + Weet je zeker dat je %1$d wachtwoord wilt verwijderen? + other + Weet je zeker dat je %1$d wachtwoorden wilt verwijderen? + NSStringFormatValueTypeKey + d + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Je wachtwoord wordt van dit apparaat verwijderd. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + other + Je wachtwoorden worden van dit apparaat verwijderd. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + accounts + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Je wachtwoord wordt verwijderd van alle gesynchroniseerde apparaten. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + other + Je wachtwoorden worden verwijderd van alle gesynchroniseerde apparaten. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + accounts + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d wachtwoord verwijderd + other + %1$d wachtwoorden verwijderd + + diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 3667ccf72c..f314c24c9a 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Skopiowano adres URL"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Usuń"; /* Disable protection action */ "action.title.disable.protection" = "Wyłącz ochronę prywatności"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kiedy DuckDuckGo wykrywa wyskakujące okienka z prośbą o zgodę na używanie plików cookie na odwiedzanych przez Ciebie stronach, może spróbować automatycznie ustawić Twoje preferencje dotyczące plików cookie w taki sposób, aby zminimalizować liczbę plików cookie i zmaksymalizować prywatność, a następnie zamknąć wyskakujące okienka. Niektóre witryny nie umożliwiają zarządzania preferencjami dotyczącymi plików cookie, dlatego możemy jedynie ukryć takie wyskakujące okienka."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Włącz ochronę poczty Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Skopiowano nazwę użytkownika"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Uwierzytelnij teraz"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Uwierzytelnij, aby usunąć wszystkie hasła"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Uwierzytelnij, aby potwierdzić, że chcesz usunąć wszystkie hasła"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Adres URL witryny"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Usuń hasło"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Czy na pewno chcesz usunąć to hasło?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Edytuj hasło"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zamknij"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Usuń wszystko"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Zapisuj i automatycznie uzupełniaj hasła"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Brak wyników"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Hasła są bezpiecznie przechowywane na Twoim urządzeniu."; diff --git a/DuckDuckGo/pl.lproj/Localizable.stringsdict b/DuckDuckGo/pl.lproj/Localizable.stringsdict index 0f112e9b01..77d5f52835 100644 --- a/DuckDuckGo/pl.lproj/Localizable.stringsdict +++ b/DuckDuckGo/pl.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Zablokowałem ich! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d hasło + few + %1$d hasła + many + %1$d haseł + other + %1$d haseł + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Czy na pewno chcesz usunąć %1$d hasło? + few + Czy na pewno chcesz usunąć %1$d hasła? + many + Czy na pewno chcesz usunąć %1$d haseł? + other + Czy na pewno chcesz usunąć %1$d hasła? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swojego %2$#@accounts@. + few + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swoich %2$#@accounts@. + many + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swoich %2$#@accounts@. + other + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swoich %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + few + konta + many + kont + other + kont + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swojego %2$#@accounts@. + few + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swoich %2$#@accounts@. + many + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swoich %2$#@accounts@. + other + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swoich %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + few + konta + many + kont + other + kont + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Usunięto %1$d hasło + few + Usunięto %1$d hasła + many + Usunięto %1$d haseł + other + Usunięto %1$d hasła + + diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 4324f1a86d..19ebbbb4d9 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiada"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Eliminar"; /* Disable protection action */ "action.title.disable.protection" = "Desativar Proteção de Privacidade"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Quando o DuckDuckGo deteta pop-ups de consentimento de cookies nos sites que visitas, podemos tentar definir automaticamente as tuas preferências de cookies para minimizar os cookies e maximizar a privacidade e, em seguida, fechar os pop-ups. Alguns sites não te dão a opção de gerir preferências de cookies, por isso só podemos ocultar pop-ups como estes."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Ativa a Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nome de utilizador copiado"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autenticar agora"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autenticar para eliminar todas as palavras-passe"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentica para confirmar que pretendes eliminar todas as palavras-passe"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL do site"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Eliminar palavra-passe"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Tens a certeza de que pretendes eliminar esta palavra-passe?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Editar palavra-passe"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Fechar"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Eliminar tudo"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Guardar e preencher palavras-passe automaticamente"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Sem resultados"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "As palavras-passe são armazenadas com segurança no teu dispositivo."; diff --git a/DuckDuckGo/pt.lproj/Localizable.stringsdict b/DuckDuckGo/pt.lproj/Localizable.stringsdict index 7c28acc96b..b59d6898ad 100644 --- a/DuckDuckGo/pt.lproj/Localizable.stringsdict +++ b/DuckDuckGo/pt.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Bloqueei-os! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d palavra-passe + other + %1$d palavras-passe + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tens a certeza de que pretendes eliminar %1$d palavra-passe? + other + Tens a certeza de que pretendes eliminar %1$d palavras-passe? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A tua palavra-passe será eliminada deste dispositivo. Confirma que ainda tens uma forma de aceder à %2$#@accounts@. + other + As tuas palavras-passe serão eliminadas deste dispositivo. Confirma que ainda tens uma forma de aceder às %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + contas + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A tua palavra-passe será eliminada de todos os dispositivos sincronizados. Confirma que ainda tens uma forma de aceder à %2$#@accounts@. + other + As tuas palavras-passe serão eliminadas de todos os dispositivos sincronizados. Confirma que ainda tens uma forma de aceder às %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + contas + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d palavra-passe eliminada + other + %1$d palavras-passe eliminadas + + diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 370c94d16f..6b91fde611 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiat"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Ștergere"; /* Disable protection action */ "action.title.disable.protection" = "Dezactivează protecția confidențialității"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Când DuckDuckGo detectează ferestre pop-up în care ți se solicită consimțământul cu privire la modulele cookie pe site-urile pe care le vizitezi, putem încerca să îți setăm automat preferințele privind modulele cookie pentru a minimiza modulele cookie și a maximiza confidențialitatea și, apoi, pentru a închide ferestrele pop-up. Unele site-uri nu oferă o opțiune de gestionare a preferințelor privind modulele cookie, prin urmare putem doar să ascundem ferestrele pop-up precum acestea."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Activează Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Numele de utilizator a fost copiat"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentifică-te acum"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentifică-te pentru a șterge toate parolele"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentifică-te pentru a confirma că dorești să ștergi toate parolele"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL-ul site-ului"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Șterge parola"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Sigur dorești să ștergi această parolă?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Editează parola"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Închidere"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Șterge toate"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Salvează și completează automat parolele"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Niciun rezultat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Parolele sunt stocate în siguranță pe dispozitivul tău."; diff --git a/DuckDuckGo/ro.lproj/Localizable.stringsdict b/DuckDuckGo/ro.lproj/Localizable.stringsdict index 15eedec91f..22a82ec393 100644 --- a/DuckDuckGo/ro.lproj/Localizable.stringsdict +++ b/DuckDuckGo/ro.lproj/Localizable.stringsdict @@ -172,5 +172,121 @@ I-am blocat! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parolă + few + %1$d parole + other + %1$d de parole + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Sigur dorești să ștergi %1$d parolă? + few + Sigur dorești să ștergi %1$d parole? + other + Sigur dorești să ștergi această %1$d de parole? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Parola ta va fi ștearsă de pe acest dispozitiv. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + few + Parolele tale vor fi șterse de pe acest dispozitiv. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + other + Parolele tale vor fi șterse de pe acest dispozitiv. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + few + conturi + other + de conturi + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Parola ta va fi ștearsă de pe toate dispozitivele sincronizate. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + few + Parolele tale vor fi șterse de pe toate dispozitivele sincronizate. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + other + Parolele dvs. vor fi șterse de pe toate dispozitivele sincronizate. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + few + conturi + other + de conturi + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parolă ștearsă + few + %1$d parole șterse + other + %1$d de parole șterse + + diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index d63d876074..abab11583c 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Адрес скопирован"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Удалить"; /* Disable protection action */ "action.title.disable.protection" = "Отключить защиту конфиденциальности"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Если DuckDuckGo обнаружит всплывающее окно выбора куки-файлов на каком-либо сайте, мы попытаемся автоматически выбрать минимум куки, максимально защитив ваши данные, а затем закроем окно. Некоторые сайты не позволяют контролировать настройки куки-файлов. В этом случае мы можем их только скрыть."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Включите защиту электронной почты"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Имя пользователя скопировано!"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Авторизоваться"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Авторизация для удаления паролей"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Чтобы подтвердить удаление, выполните авторизацию"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Адрес сайта"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Удалить пароль"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Вы точно хотите удалить этот пароль?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Редактирование пароля"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Закрыть"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Удалить все"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Хранение и автозаполнение паролей"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Нет результатов"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Пароли надежно хранятся на вашем устройстве."; diff --git a/DuckDuckGo/ru.lproj/Localizable.stringsdict b/DuckDuckGo/ru.lproj/Localizable.stringsdict index 33e60942d5..7b4f8bf3ba 100644 --- a/DuckDuckGo/ru.lproj/Localizable.stringsdict +++ b/DuckDuckGo/ru.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d пароль + few + %1$d пароля + many + %1$d паролей + other + %1$d пароля + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Вы точно хотите удалить %1$d пароль? + few + Вы точно хотите удалить %1$d пароля? + many + Вы точно хотите удалить %1$d паролей? + other + Вы точно хотите удалить пароли (%1$d)? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свою %2$#@accounts@. + few + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + many + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + other + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + учетную запись + few + учетные записи + many + учетные записи + other + учетные записи + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свою %2$#@accounts@. + few + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + many + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + other + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + учетную запись + few + учетные записи + many + учетные записи + other + учетные записи + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Удален %1$d пароль + few + Удалено %1$d пароля + many + Удалено %1$d паролей + other + Удалено паролей: %1$d + + diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 09f96b80ca..6826707e50 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Adresa URL bola skopírovaná"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Vymazať"; /* Disable protection action */ "action.title.disable.protection" = "Vypnúť ochranu súkromia"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Keď DuckDuckGo zistí na vami navštívených lokalitách kontextové okná týkajúce sa súhlasu so súbormi cookie, môžeme sa pokúsiť automaticky nastaviť vaše možnosti súborov cookie tak, aby sa využívanie súborov cookie minimalizovali a aby sa maximalizovala ochrana súkromia, a potom môžeme kontextové automaticky okná zavrieť. Niektoré lokality neponúkajú možnosť spravovať preferencie súborov cookie, takže takéto kontextové okná môžeme iba skryť."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Povoliť ochranu e-mailu"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Meno používateľa bolo skopírované"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Overiť teraz"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Overenie na odstránenie všetkých hesiel."; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Overením potvrďte, že chcete odstrániť všetky heslá."; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Adresa URL webových stránok"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Odstrániť heslo"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Naozaj chcete odstrániť toto heslo?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Upraviť heslo"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zatvoriť"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Odstrániť všetko"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Ukladať a automaticky dopĺňať heslá"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Žiadne výsledky"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Heslá sú bezpečne uložené vo vašom zariadení."; diff --git a/DuckDuckGo/sk.lproj/Localizable.stringsdict b/DuckDuckGo/sk.lproj/Localizable.stringsdict index afe4bb849c..4361597c30 100644 --- a/DuckDuckGo/sk.lproj/Localizable.stringsdict +++ b/DuckDuckGo/sk.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Boli zablokovaní! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d heslo + few + %1$d heslá + many + %1$d hesla + other + %1$d hesiel + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Naozaj chcete odstrániť %1$d heslo? + few + Naozaj chcete odstrániť %1$d heslá? + many + Naozaj chcete odstrániť %1$d hesla? + other + Naozaj chcete odstrániť %1$d hesiel? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Vaše heslo bude odstránené z tohto zariadenia. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + few + Vaše heslá budú odstránené z tohto zariadenia. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + many + Vaše heslá budú odstránené z tohto zariadenia. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + other + Vaše heslá budú z tohto zariadenia vymazané. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účet + few + účty + many + účtu + other + účtov + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Vaše heslo bude odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + few + Vaše heslá budú odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + many + Vaše heslá budú odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + other + Vaše heslá budú odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účet + few + účty + many + účtu + other + účtov + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Bolo odstránené %1$d heslo + few + Boli odstránené %1$d heslá + many + Bolo odstránených %1$d hesiel + other + Bol odstránených %1$d hesiel + + diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 8b11995d70..8fb6b6af7f 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL naslov kopiran"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Izbriši"; /* Disable protection action */ "action.title.disable.protection" = "Onemogoči zaščito zasebnosti"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "DuckDuckGo na spletnih mestih, ki jih obiščete, zazna pojavna okna s soglasjem za piškotke, zato lahko samodejno nastavimo vaše nastavitve piškotkov, da zmanjšamo uporabo piškotkov in povečamo zasebnost, nato pa pojavna okna zapremo. Nekatera spletna mesta pa ne omogočajo upravljanja nastavitev piškotkov, zato lahko takšna pojavna okna le skrijemo."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Omogočite zaščito e-pošte Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Uporabniško ime je bilo kopirano"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Preveri pristnost zdaj"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Preverite pristnost za brisanje vseh gesel"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Potrdite, da želite izbrisati vsa gesla"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL spletnega mesta"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Izbriši geslo"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Ali ste prepričani, da želite izbrisati to geslo?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Uredite geslo"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zapri"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Izbriši vse"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Shranite in samodejno izpolnite gesla"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ni rezultatov"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Gesla so varno shranjena v vaši napravi."; diff --git a/DuckDuckGo/sl.lproj/Localizable.stringsdict b/DuckDuckGo/sl.lproj/Localizable.stringsdict index 988805afa3..a42f7c1e50 100644 --- a/DuckDuckGo/sl.lproj/Localizable.stringsdict +++ b/DuckDuckGo/sl.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Blokiral sem jih! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d geslo + two + %1$d gesli + few + %1$d gesla + other + %1$d gesel + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ali ste prepričani, da želite izbrisati %1$d geslo? + two + Ali ste prepričani, da želite izbrisati %1$d gesli? + few + Ali ste prepričani, da želite izbrisati %1$d gesla? + other + Ali ste prepričani, da želite izbrisati %1$d gesel? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojega %2$#@accounts@. + two + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojih %2$#@accounts@. + few + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojih %2$#@accounts@. + other + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojih %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + račun + two + računa + few + računi + other + računov + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojega %2$#@accounts@. + two + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojih %2$#@accounts@. + few + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojih %2$#@accounts@. + other + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojih %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + račun + two + računa + few + računi + other + računov + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d geslo je bilo izbrisano + two + %1$d gesli sta bili izbrisani + few + %1$d gesla so bila izbrisana + other + %1$d gesel je bilo izbrisanih + + diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 626ec73894..b03ac806df 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Webbadress kopierad"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Ta bort"; /* Disable protection action */ "action.title.disable.protection" = "Inaktivera integritetsskydd"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "När DuckDuckGo upptäcker popup-fönster för godkännande av cookies på webbplatser som du besöker kan vi försöka att automatiskt ställa in dina cookieinställningar på att minimera cookies och maximera din integritet, och därefter stänga popup-fönstren. På vissa webbplatser finns ingen möjlighet att justera cookieinställningarna, så vi kan enbart dölja popup-fönster som dessa."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Aktivera Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Användarnamn kopierat"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentisera nu"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentisera för att ta bort alla lösenord"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentisera för att bekräfta att du vill radera alla lösenord"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Webbplats-URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Radera lösenord"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Är du säker på att du vill radera detta lösenord?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Redigera lösenord"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Stäng"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Radera allt"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Spara och fyll i lösenord automatiskt"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Inga resultat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Lösenord lagras säkert på din enhet."; diff --git a/DuckDuckGo/sv.lproj/Localizable.stringsdict b/DuckDuckGo/sv.lproj/Localizable.stringsdict index 87d22e38fc..2b260781eb 100644 --- a/DuckDuckGo/sv.lproj/Localizable.stringsdict +++ b/DuckDuckGo/sv.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Jag blockerade dem! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d lösenord + other + %1$d lösenord + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Är du säker på att du vill radera %1$d lösenord? + other + Är du säker på att du vill radera %1$d lösenord? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ditt lösenord kommer att raderas från denna enhet. Se till att du har kvar ett sätt att komma åt ditt %2$#@accounts@. + other + Dina lösenord kommer att raderas från denna enhet. Se till att du har kvar ett sätt att komma åt dina %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konton + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ditt lösenord kommer att raderas från alla synkroniserade enheter. Se till att du har kvar ett sätt att komma åt ditt %2$#@accounts@. + other + Dina lösenord kommer att raderas från alla synkroniserade enheter. Se till att du har kvar ett sätt att komma åt dina %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konton + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d lösenord raderat + other + %1$d lösenord raderade + + diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 2e53912f04..6d58c61947 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL kopyalandı"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Sil"; /* Disable protection action */ "action.title.disable.protection" = "Gizlilik Korumasını Devre Dışı Bırak"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "DuckDuckGo ziyaret ettiğiniz sitelerde çerez onayı açılır pencereleri tespit ederse çerez tercihlerinizi çerezleri en aza indirecek ve gizliliği en üst düzeye çıkaracak şekilde otomatik olarak ayarlayabilir ve ardından açılır pencereleri kapatabiliriz. Bazı siteler çerez tercihlerini yönetmek için bir seçenek sunmaz. Bu nedenle yalnızca bu gibi açılır pencereleri gizleyebiliriz."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Email Protection'ı Etkinleştir"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Kullanıcı adı kopyalandı"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Kimlik Doğrulaması Yapın"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Tüm Şifreleri Silmek İçin Kimliğinizi Doğrulayın"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Tüm şifreleri silmek istediğinizi onaylamak için kimlik doğrulaması yapın"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Web sitesi URL'si"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Şifreyi Sil"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Bu şifreyi silmek istediğinizden emin misiniz?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Şifreyi Düzenle"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Kapat"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Tümünü Sil"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Şifreleri kaydedin ve otomatik doldurun"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Sonuç yok"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Şifreler cihazınızda güvenli bir şekilde saklanır."; diff --git a/DuckDuckGo/tr.lproj/Localizable.stringsdict b/DuckDuckGo/tr.lproj/Localizable.stringsdict index e535be2fb2..65c7351751 100644 --- a/DuckDuckGo/tr.lproj/Localizable.stringsdict +++ b/DuckDuckGo/tr.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Onları engelledim! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d şifre + other + %1$d şifre + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Bu %1$d şifreyi silmek istediğinizden emin misiniz? + other + Bu %1$d şifreyi silmek istediğinizden emin misiniz? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Şifreniz bu cihazdan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + other + Şifreniz bu cihazdan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + hesap + other + hesap + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Şifreleriniz senkronize edilen tüm cihazlardan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + other + Şifreleriniz senkronize edilen tüm cihazlardan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + hesap + other + hesap + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d şifre silindi + other + %1$d şifre silindi + + diff --git a/DuckDuckGoTests/AutofillLoginListViewModelTests.swift b/DuckDuckGoTests/AutofillLoginListViewModelTests.swift index bf6d6eace3..457445462a 100644 --- a/DuckDuckGoTests/AutofillLoginListViewModelTests.swift +++ b/DuckDuckGoTests/AutofillLoginListViewModelTests.swift @@ -20,18 +20,20 @@ import Foundation import XCTest +import Combine @testable import DuckDuckGo @testable import Core @testable import BrowserServicesKit @testable import Common -// swiftlint:disable line_length +// swiftlint:disable line_length file_length class AutofillLoginListViewModelTests: XCTestCase { private let tld = TLD() private let appSettings = AppUserDefaults() private let vault = (try? MockSecureVaultFactory.makeVault(errorReporter: nil))! private var manager: AutofillNeverPromptWebsitesManager! + private var cancellables: Set = [] override func setUpWithError() throws { try super.setUpWithError() @@ -40,6 +42,7 @@ class AutofillLoginListViewModelTests: XCTestCase { override func tearDownWithError() throws { manager = nil + cancellables.removeAll() try super.tearDownWithError() } @@ -113,6 +116,191 @@ class AutofillLoginListViewModelTests: XCTestCase { XCTAssertEqual(tableContentsToDelete.rowsToDelete.count, 2) } + func testWhenMultipleAccountsSavedAndClearAllThenNoAccountsAreShown() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + } + + func testWhenMultipleAccountsSavedAndClearAllThenUndoThenAccountsAreShownAgain() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + + model.undoClearAllAccounts() + + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + } + + func testWhenOneAccountSavedAndClearAllThenUndoThenAccountIsShownAgain() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 1) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + + model.undoClearAllAccounts() + + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 1) + } + + func testWhenMultipleAccountsSavedAndOneSuggestionAndClearAllThenUndoThenAccountsAndSuggestionsAreShown() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + let testDomain = "testsite.com" + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, currentTabUrl: URL(string: "https://\(testDomain)"), autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 3) + XCTAssertEqual(model.rowsInSection(1), 1) + XCTAssertEqual(model.rowsInSection(2), 3) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + + model.undoClearAllAccounts() + + XCTAssertEqual(model.sections.count, 3) + XCTAssertEqual(model.rowsInSection(1), 1) + XCTAssertEqual(model.rowsInSection(2), 3) + } + + func testWhenInEditModeThenEnableAutofillSectionIsNotDisplayed() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + + XCTAssertEqual(model.sections.count, 2) + + model.isEditing = true + + XCTAssertEqual(model.sections.count, 1) + } + + func testWhenSearchingThenEnableAutofillSectionIsNotDisplayed() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + + XCTAssertEqual(model.sections.count, 2) + + model.isSearching = true + model.filterData(with: "z") + + XCTAssertEqual(model.sections.count, 0) + + model.filterData(with: "t") + + XCTAssertEqual(model.sections.count, 1) + } + + func testWhenOneAccountDeletedInEditModeThenAccountsCountPublisherUpdatesCorrectly() { + let expectation = XCTestExpectation(description: "accountsCountPublisher emits an updated count") + + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + + model.isEditing = true + model.accountsCountPublisher.sink { count in + XCTAssertEqual(count, model.accountsCount, "The published count should match the number accounts count") + expectation.fulfill() + } + .store(in: &cancellables) + + _ = model.delete(at: IndexPath(row: 1, section: 0)) + wait(for: [expectation], timeout: 1.0) + } + + func testWhenOneAccountSavedAndDeleteAllThenNoAccountsAreShownAndVaultIsEmpty() throws { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + for account in vault.storedAccounts { + _ = try vault.storeWebsiteCredentials(SecureVaultModels.WebsiteCredentials(account: account, password: nil)) + } + + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(0), 1) + XCTAssertEqual(model.rowsInSection(1), 1) + XCTAssertEqual(vault.storedAccounts.count, 1) + + model.isEditing = true + let result = model.deleteAllCredentials() + if result { + model.updateData() + } + + XCTAssertEqual(model.sections.count, 0) + XCTAssertEqual(vault.storedAccounts.count, 0) + } + + func testWhenMultipleAccountsSavedAndDeleteAllThenNoAccountsAreShownAndVaultIsEmpty() throws { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + for account in vault.storedAccounts { + _ = try vault.storeWebsiteCredentials(SecureVaultModels.WebsiteCredentials(account: account, password: nil)) + } + + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + XCTAssertEqual(vault.storedAccounts.count, 3) + + model.isEditing = true + let result = model.deleteAllCredentials() + if result { + model.updateData() + } + + XCTAssertEqual(model.sections.count, 0) + XCTAssertEqual(vault.storedAccounts.count, 0) + } + func testWhenNoNeverPromptWebsitesSavedThenNeverPromptSectionIsNotShown() { XCTAssertTrue(manager.deleteAllNeverPromptWebsites()) let model = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index 9dfc62644f..8545c641fa 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -100,6 +100,11 @@ final class MockSecureVault: AutofillSecureVault { storedCredentials[accountId] = nil } + func deleteAllWebsiteCredentials() throws { + storedCredentials = [:] + storedAccounts = [] + } + func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] { return storedNeverPromptWebsites } @@ -269,6 +274,11 @@ class MockDatabaseProvider: AutofillDatabaseProvider { self._accounts = self._accounts.filter { $0.id != String(accountId) } } + func deleteAllWebsiteCredentials() throws { + self._credentialsDict = [:] + self._accounts = [] + } + func accounts() throws -> [SecureVaultModels.WebsiteAccount] { return _accounts } From 34decedf5e28a0e4aca696474abbfd9e1e3e377d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 5 Mar 2024 19:05:32 +0100 Subject: [PATCH 05/20] 16. Subscription: Display "Activation in progress" message (#2535) Description: Display an "Activation in progress" message if there's an active subscription but no available entitlements. --- DuckDuckGo/SettingsSubscriptionView.swift | 92 +++++++++++++++---- DuckDuckGo/SettingsViewModel.swift | 23 ++++- .../info-16.imageset/Contents.json | 12 +++ .../info-16.imageset/info-16.svg | 11 +++ DuckDuckGo/UserText.swift | 6 +- DuckDuckGo/en.lproj/Localizable.strings | 6 ++ 6 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index c758793012..8dc36fc915 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -31,11 +31,19 @@ struct SettingsSubscriptionView: View { @State var isShowingDBP = false @State var isShowingITP = false + enum Constants { + static let purchaseDescriptionPadding = 5.0 + static let topCellPadding = 3.0 + static let noEntitlementsIconWidth = 20.0 + static let navigationDelay = 0.3 + static let infoIcon = "info-16" + } + private var subscriptionDescriptionView: some View { VStack(alignment: .leading) { Text(UserText.settingsPProSubscribe).daxBodyRegular() Group { - Text(UserText.settingsPProDescription).daxFootnoteRegular().padding(.bottom, 5) + Text(UserText.settingsPProDescription).daxFootnoteRegular().padding(.bottom, Constants.purchaseDescriptionPadding) Text(UserText.settingsPProFeatures).daxFootnoteRegular() }.foregroundColor(Color(designSystemColor: .textSecondary)) } @@ -53,20 +61,38 @@ struct SettingsSubscriptionView: View { .foregroundColor(Color.init(designSystemColor: .accent)) } + @ViewBuilder + private var restorePurchaseView: some View { + let text = !viewModel.isRestoringSubscription ? UserText.subscriptionActivateAppleIDButton : UserText.subscriptionRestoringTitle + SettingsCustomCell(content: { + Text(text) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent)) }, + action: { + Task { await viewModel.restoreAccountPurchase() } + }, + isButton: !viewModel.isRestoringSubscription ) + .alert(isPresented: $viewModel.shouldDisplayRestoreSubscriptionError) { + Alert( + title: Text(UserText.subscriptionAppStoreErrorTitle), + message: Text(UserText.subscriptionAppStoreErrorMessage), + dismissButton: .default(Text(UserText.actionOK)) {} + ) + } + } + private var manageSubscriptionView: some View { Text(UserText.settingsPProManageSubscription) .daxBodyRegular() } - + + @ViewBuilder private var purchaseSubscriptionView: some View { - return Group { + Group { SettingsCustomCell(content: { subscriptionDescriptionView }) SettingsCustomCell(content: { learnMoreView }, action: { isShowingsubScriptionFlow = true }, isButton: true ) - .sheet(isPresented: $isShowingsubScriptionFlow) { - SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() - } SettingsCustomCell(content: { iHaveASubscriptionView }, action: { @@ -78,8 +104,28 @@ struct SettingsSubscriptionView: View { } } + @ViewBuilder + private var noEntitlementsAvailableView: some View { + Group { + SettingsCustomCell(content: { + HStack(alignment: .top) { + Image(Constants.infoIcon) + .frame(width: Constants.noEntitlementsIconWidth) + .padding(.top, Constants.topCellPadding) + VStack(alignment: .leading) { + Text(UserText.settingsPProActivationPendingTitle).daxBodyRegular() + Text(UserText.settingsPProActivationPendingDescription).daxFootnoteRegular() + .padding(.bottom, Constants.purchaseDescriptionPadding) + }.foregroundColor(Color(designSystemColor: .textSecondary)) + } + }) + restorePurchaseView + } + } + + @ViewBuilder private var subscriptionDetailsView: some View { - return Group { + Group { if viewModel.shouldShowNetP { SettingsCellView(label: UserText.settingsPProVPNTitle, subtitle: viewModel.state.networkProtection.status != "" ? viewModel.state.networkProtection.status : nil, @@ -105,13 +151,11 @@ struct SettingsSubscriptionView: View { SubscriptionITPView() } } - - if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { - NavigationLink(destination: SubscriptionSettingsView()) { - SettingsCustomCell(content: { manageSubscriptionView }) - } + + NavigationLink(destination: SubscriptionSettingsView()) { + SettingsCustomCell(content: { manageSubscriptionView }) } - + } } @@ -119,12 +163,28 @@ struct SettingsSubscriptionView: View { if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { if viewModel.state.subscription.hasActiveSubscription { - subscriptionDetailsView + + // Allow managing the subscription if we have some entitlements + if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { + subscriptionDetailsView + + // If no entitlements it should mean the backend is still out of sync + } else { + noEntitlementsAvailableView + } + } else { purchaseSubscriptionView + } } + // Subscription Restore + .sheet(isPresented: $isShowingsubScriptionFlow) { + SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() + } + + // Refresh subscription when dismissing the Subscription Flow .onChange(of: isShowingsubScriptionFlow, perform: { value in if !value { @@ -135,7 +195,7 @@ struct SettingsSubscriptionView: View { .onChange(of: viewModel.shouldNavigateToDBP, perform: { value in if value { // Allow the sheet to dismiss before presenting a new one - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.navigationDelay) { isShowingDBP = true } } @@ -144,7 +204,7 @@ struct SettingsSubscriptionView: View { .onChange(of: viewModel.shouldNavigateToITP, perform: { value in if value { // Allow the sheet to dismiss before presenting a new one - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.navigationDelay) { isShowingITP = true } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index e2aa84904e..a4e386c80b 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -50,6 +50,8 @@ final class SettingsViewModel: ObservableObject { #if SUBSCRIPTION private var accountManager: AccountManager private var signOutObserver: Any? + @Published var isRestoringSubscription: Bool = false + @Published var shouldDisplayRestoreSubscriptionError: Bool = false #endif @@ -405,7 +407,26 @@ extension SettingsViewModel { } } - #endif + @available(iOS 15.0, *) + func restoreAccountPurchase() async { + DispatchQueue.main.async { self.isRestoringSubscription = true } + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + switch result { + case .success: + DispatchQueue.main.async { + self.isRestoringSubscription = false + } + await self.setupSubscriptionEnvironment() + + case .failure: + DispatchQueue.main.async { + self.isRestoringSubscription = false + self.shouldDisplayRestoreSubscriptionError = true + } + } + } + +#endif // SUBSCRIPTION #if NETWORK_PROTECTION private func updateNetPStatus(connectionStatus: ConnectionStatus) { diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json new file mode 100644 index 0000000000..6e2e5f1256 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "info-16.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg new file mode 100644 index 0000000000..b9390be066 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 8a769e5c59..bf53406e72 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -999,7 +999,11 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsPProDBPSubTitle = NSLocalizedString("settings.subscription.DBP.subtitle", value: "Remove your info from sites that sell it", comment: "Data Broker protection cell subtitle for privacy pro") public static let settingsPProITRTitle = NSLocalizedString("settings.subscription.ITR.title", value: "Identity Theft Restoration", comment: "Identity theft restoration cell title for privacy pro") public static let settingsPProITRSubTitle = NSLocalizedString("settings.subscription.ITR.subtitle", value: "If your identity is stolen, we'll help restore it", comment: "Identity theft restoration cell subtitle for privacy pro") - + + public static let settingsPProActivationPendingTitle = NSLocalizedString("settings.subscription.activation.pending.title", value: "Your Subscription is Being Activated", comment: "Subscription activation pending title") + public static let settingsPProActivationPendingDescription = NSLocalizedString("settings.subscription.activation.pending.description", value: "This is taking longer than usual, please check back later.", comment: "Subscription activation pending description") + + // Customize Section public static let settingsCustomizeSection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 4459952b4f..7025259c86 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1855,6 +1855,12 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings title for the privacy section */ "settings.privacy" = "Privacy"; +/* Subscription activation pending description */ +"settings.subscription.activation.pending.description" = "This is taking longer than usual, please check back later."; + +/* Subscription activation pending title */ +"settings.subscription.activation.pending.title" = "Your Subscription is Being Activated"; + /* Data Broker protection cell subtitle for privacy pro */ "settings.subscription.DBP.subtitle" = "Remove your info from sites that sell it"; From d9e49af1b429677f43357564d893d32b9a6f2fc3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 6 Mar 2024 11:57:04 +0100 Subject: [PATCH 06/20] Makes dbSaveBloomFilterError daily and count (#2526) Task/Issue URL: https://app.asana.com/0/1199230911884351/1205962557569232/f macOS PR: https://github.com/duckduckgo/macos-browser/pull/2299 ## Description We're making the `dbSaveBloomFilterError` pixel fire daily and continuously to be able to better understand impact (ie: number of users affected). Also fixes an issue in the debug menu that was causing the privacy config to be refreshed an ever-increasing number of times. --- Core/PrivacyFeatures.swift | 10 +++++++--- DuckDuckGo/ConfigurationURLDebugViewController.swift | 12 +++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Core/PrivacyFeatures.swift b/Core/PrivacyFeatures.swift index b3ca70c839..977a5d94f9 100644 --- a/Core/PrivacyFeatures.swift +++ b/Core/PrivacyFeatures.swift @@ -33,17 +33,21 @@ public final class PrivacyFeatures { } private static let httpsUpgradeDebugEvents = EventMapping { event, error, parameters, onComplete in let domainEvent: Pixel.Event + let dailyAndCount: Bool + switch event { case .dbSaveBloomFilterError: domainEvent = .dbSaveBloomFilterError + dailyAndCount = true case .dbSaveExcludedHTTPSDomainsError: domainEvent = .dbSaveExcludedHTTPSDomainsError + dailyAndCount = false } - if let error { - Pixel.fire(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + if dailyAndCount { + DailyPixel.fireDailyAndCount(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onCountComplete: onComplete) } else { - Pixel.fire(pixel: domainEvent, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + Pixel.fire(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) } } private static var httpsUpgradeStore: AppHTTPSUpgradeStore { diff --git a/DuckDuckGo/ConfigurationURLDebugViewController.swift b/DuckDuckGo/ConfigurationURLDebugViewController.swift index f508a40393..7a6e970cc4 100644 --- a/DuckDuckGo/ConfigurationURLDebugViewController.swift +++ b/DuckDuckGo/ConfigurationURLDebugViewController.swift @@ -127,7 +127,7 @@ final class ConfigurationURLDebugViewController: UITableViewController { cell.subtitle.text = url(for: row) cell.subtitle.textColor = customURL(for: row) != nil ? UIColor(designSystemColor: .accent) : .black cell.ternary.text = lastConfigurationUpdateDate != nil ? dateFormatter.string(from: lastConfigurationUpdateDate!) : "-" - cell.refresh.addAction(makeAction(for: row), for: .allEvents) + cell.refresh.addAction(refreshAction, for: .primaryActionTriggered) return cell } @@ -136,12 +136,10 @@ final class ConfigurationURLDebugViewController: UITableViewController { presentCustomURLAlert(for: row) } - private func makeAction(for row: CustomURLsRows) -> UIAction { - UIAction { [weak self] _ in - self?.lastConfigurationRefreshDate = Date.distantPast - self?.fetchAssets() - self?.tableView.reloadData() - } + private lazy var refreshAction = UIAction { [weak self] _ in + self?.lastConfigurationRefreshDate = Date.distantPast + self?.fetchAssets() + self?.tableView.reloadData() } private func presentCustomURLAlert(for row: CustomURLsRows) { From d6b7c9791701a1a3c4535d4ffe0e93d94732ae65 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 6 Mar 2024 16:27:51 +0100 Subject: [PATCH 07/20] Integrate confirm entitlements endpoint (#2541) --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +---- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/SettingsViewModel.swift | 4 ++-- ...IdentityTheftRestorationPagesFeature.swift | 3 ++- .../UserScripts/Subscription.swift | 24 ------------------- ...scriptionPagesUseSubscriptionFeature.swift | 18 +++++++++----- .../SubscriptionSettingsViewModel.swift | 18 +++++++------- .../SubscriptionDebugViewController.swift | 2 +- 8 files changed, 28 insertions(+), 51 deletions(-) delete mode 100644 DuckDuckGo/Subscription/UserScripts/Subscription.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 074ad901eb..2a8d4acacc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -799,7 +799,6 @@ D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */; }; D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; }; D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */; }; - D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92C2B696945008E2FF2 /* Subscription.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; @@ -2464,7 +2463,6 @@ D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionITPViewModel.swift; sourceTree = ""; }; D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesFeature.swift; sourceTree = ""; }; - D668D92C2B696945008E2FF2 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; @@ -4641,7 +4639,6 @@ D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D668D92C2B696945008E2FF2 /* Subscription.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, @@ -6939,7 +6936,6 @@ EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, - D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */, AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, @@ -10001,7 +9997,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.3.0; + version = 115.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5f6b2bab9d..d879edc585 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "1fa06fb43fb0f26fc1a74b710fb1402573264bf5", - "version" : "114.3.0" + "revision" : "06ba37e23cd4c8d848d9d153c6e5be07956d3adc", + "version" : "115.0.0" } }, { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index a4e386c80b..b94ef3a23a 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -348,9 +348,9 @@ extension SettingsViewModel { } // Fetch available subscriptions from the backend (or sign out) - switch await SubscriptionService.getSubscriptionDetails(token: token) { + switch await SubscriptionService.getSubscription(accessToken: token) { - case .success(let response) where response.isSubscriptionActive: + case .success(let subscription) where subscription.isActive: // Cache Subscription state cacheSubscriptionState(active: true) diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 055e3bc1cf..276ef14926 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -32,6 +32,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { struct Constants { static let featureName = "useIdentityTheftRestoration" static let os = "ios" + static let token = "token" } struct OriginDomains { @@ -68,7 +69,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { let authToken = AccountManager().authToken ?? "" - return Subscription(token: authToken) + return [Constants.token: authToken] } deinit { diff --git a/DuckDuckGo/Subscription/UserScripts/Subscription.swift b/DuckDuckGo/Subscription/UserScripts/Subscription.swift deleted file mode 100644 index 9306a531c5..0000000000 --- a/DuckDuckGo/Subscription/UserScripts/Subscription.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Subscription.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 - -struct Subscription: Encodable { - let token: String -} diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 0af6a2d15f..9cd2090506 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -30,6 +30,8 @@ enum SubscriptionTransactionStatus { case idle, purchasing, restoring, polling } +// swiftlint:disable type_body_length + @available(iOS 15.0, *) final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObject { @@ -37,6 +39,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec static let featureName = "useSubscription" static let os = "ios" static let empty = "" + static let token = "token" } struct OriginDomains { @@ -156,7 +159,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // MARK: Broker Methods (Called from WebView via UserScripts) func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { let authToken = AccountManager().authToken ?? Constants.empty - return Subscription(token: authToken) + return [Constants.token: authToken] } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { @@ -202,10 +205,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } let emailAccessToken = try? EmailManager().getToken() + let purchaseTransactionJWS: String switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { - case .success: - break + case .success(let transactionJWS): + purchaseTransactionJWS = transactionJWS case .failure(let error): switch error { @@ -220,7 +224,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } setTransactionStatus(.polling) - switch await AppStorePurchaseFlow.completeSubscriptionPurchase() { + switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { case .success(let purchaseUpdate): await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure: @@ -256,10 +260,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let accountManager = AccountManager() if let accessToken = accountManager.accessToken, case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - switch await SubscriptionService.getSubscriptionDetails(token: accessToken) { + switch await SubscriptionService.getSubscription(accessToken: accessToken) { // If the account is not active, display an error and logout - case .success(let response) where !response.isSubscriptionActive: + case .success(let subscription) where !subscription.isActive: setTransactionError(.failedToRestoreFromEmailSubscriptionInactive) accountManager.signOut() return nil @@ -353,4 +357,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } +// swiftlint:enable type_body_length + #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 6ad599d49f..f52e6f75c4 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -27,8 +27,6 @@ import Subscription final class SubscriptionSettingsViewModel: ObservableObject { enum Constants { - static let autoRenewable = "Auto-Renewable" - static let notAutoRenewable = "Not Auto-Renewable" static let monthlyProductID = "ios.subscription.1month" static let yearlyProductID = "ios.subscription.1year" static let updateFrequency: Float = 10 @@ -61,19 +59,19 @@ final class SubscriptionSettingsViewModel: ObservableObject { Task { guard let token = accountManager.accessToken else { return } - if let cachedDate = SubscriptionService.cachedSubscriptionDetailsResponse?.expiresOrRenewsAt, - let cachedStatus = SubscriptionService.cachedSubscriptionDetailsResponse?.status, - let productID = SubscriptionService.cachedSubscriptionDetailsResponse?.productId { + if let cachedDate = SubscriptionService.cachedGetSubscriptionResponse?.expiresOrRenewsAt, + let cachedStatus = SubscriptionService.cachedGetSubscriptionResponse?.status, + let productID = SubscriptionService.cachedGetSubscriptionResponse?.productId { updateSubscriptionDetails(status: cachedStatus, date: cachedDate, product: productID) } - if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { - if !response.isSubscriptionActive { + if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token) { + if !subscription.isActive { AccountManager().signOut() shouldDismissView = true return } else { - updateSubscriptionDetails(status: response.status, date: response.expiresOrRenewsAt, product: response.productId) + updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId) } } } @@ -97,8 +95,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { } - private func updateSubscriptionDetails(status: String, date: Date, product: String) { - let statusString = (status == Self.Constants.autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires + private func updateSubscriptionDetails(status: Subscription.Status, date: Date, product: String) { + let statusString = (status == .autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires self.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) self.subscriptionType = product == Constants.monthlyProductID ? UserText.subscriptionMonthly : UserText.subscriptionAnnual } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index feb61f2310..27c4b67c96 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -222,7 +222,7 @@ final class SubscriptionDebugViewController: UITableViewController { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } - switch await SubscriptionService.getSubscriptionDetails(token: token) { + switch await SubscriptionService.getSubscription(accessToken: token) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): From 52011471417f75d52d024f5a8c92ada880665dbf Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Wed, 6 Mar 2024 15:47:08 +0000 Subject: [PATCH 08/20] fix opening tabs with transitional (#2542) Task/Issue URL: https://app.asana.com/0/414235014887631/1206769708570992/f Tech Design URL: CC: Description: Pages that open new tabs in a certain way will get stuck on about:blank Steps to test this PR: Visit https://finance.yahoo.com/news/ocean-biomedical-inc-announces-publication-131500736.html?guccounter=1 and get to the article content Click on a link in the article. A new tab should open and navigate to the content Visit https://chrisbrind.rocks and click on one of the Connect buttons (instagram, tiktok, mastodon) - should all open in a new tab and load successfully (I know this website is awful sorry) --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/MainViewController.swift | 3 ++- DuckDuckGo/TabManager.swift | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d879edc585..dffa9b0106 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index d125eaccaf..41df7f48c2 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1833,7 +1833,8 @@ extension MainViewController: TabDelegate { showBars() currentTab?.dismiss() - let newTab = tabManager.addURLRequest(navigationAction.request, + // Don't use a request or else the page gets stuck on "about:blank" + let newTab = tabManager.addURLRequest(nil, with: configuration, inheritedAttribution: inheritingAttribution) newTab.openedByPage = true diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 39e2629895..68a8fa473a 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -120,7 +120,7 @@ class TabManager { return current(createIfNeeded: true)! } - func addURLRequest(_ request: URLRequest, + func addURLRequest(_ request: URLRequest?, with configuration: WKWebViewConfiguration, inheritedAttribution: AdClickAttributionLogic.State?) -> TabViewController { @@ -128,7 +128,12 @@ class TabManager { fatalError("Failed to copy configuration") } - let tab = Tab(link: request.url == nil ? nil : Link(title: nil, url: request.url!)) + let tab: Tab + if let request { + tab = Tab(link: request.url == nil ? nil : Link(title: nil, url: request.url!)) + } else { + tab = Tab() + } model.insert(tab: tab, at: model.currentIndex + 1) model.select(tabAt: model.currentIndex + 1) From 21ce049b4f1efe0cc1aaa7f39d9f3e00f1a7c31d Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:15:28 -0500 Subject: [PATCH 09/20] Add preliminary support for subscription keychain sharing (#2538) Co-authored-by: Graeme Arthur --- Configuration/Configuration-Alpha.xcconfig | 3 ++ Configuration/DuckDuckGoDeveloper.xcconfig | 3 ++ Core/AccountManagerExtension.swift | 31 ++++++++++++++ Core/BundleExtension.swift | 40 +++++++++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 22 +++++++++- .../xcshareddata/swiftpm/Package.resolved | 6 +-- DuckDuckGo/DuckDuckGo.entitlements | 9 ++++- DuckDuckGo/DuckDuckGoAlpha.entitlements | 5 +++ DuckDuckGo/Info.plist | 2 + DuckDuckGo/SettingsViewModel.swift | 2 +- ...scriptionPagesUseSubscriptionFeature.swift | 11 +++-- PacketTunnelProvider/Info.plist | 2 + ...etworkProtectionPacketTunnelProvider.swift | 6 ++- .../PacketTunnelProvider.entitlements | 5 +++ .../PacketTunnelProviderAlpha.entitlements | 5 +++ 15 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 Core/AccountManagerExtension.swift create mode 100644 Core/BundleExtension.swift diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig index 4c15890703..940e9dfdcf 100644 --- a/Configuration/Configuration-Alpha.xcconfig +++ b/Configuration/Configuration-Alpha.xcconfig @@ -26,3 +26,6 @@ APP_ID = com.duckduckgo.mobile.ios.alpha // A prefix for group ids. Must start with "group.". GROUP_ID_PREFIX = group.com.duckduckgo.alpha + +// The keychain access group for subscriptions +SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions.alpha diff --git a/Configuration/DuckDuckGoDeveloper.xcconfig b/Configuration/DuckDuckGoDeveloper.xcconfig index d23a9aaa9c..37195c89c5 100644 --- a/Configuration/DuckDuckGoDeveloper.xcconfig +++ b/Configuration/DuckDuckGoDeveloper.xcconfig @@ -27,3 +27,6 @@ CODE_SIGN_STYLE = Manual // The manually specified provisioning profile PROVISIONING_PROFILE_SPECIFIER = Development - App + +// The keychain access group for subscriptions +SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions diff --git a/Core/AccountManagerExtension.swift b/Core/AccountManagerExtension.swift new file mode 100644 index 0000000000..5dd738fa3d --- /dev/null +++ b/Core/AccountManagerExtension.swift @@ -0,0 +1,31 @@ +// +// AccountManagerExtension.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. +// + +#if SUBSCRIPTION + +import Foundation +import Subscription + +public extension AccountManager { + convenience init() { + self.init(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + } +} + +#endif diff --git a/Core/BundleExtension.swift b/Core/BundleExtension.swift new file mode 100644 index 0000000000..97b82eaae5 --- /dev/null +++ b/Core/BundleExtension.swift @@ -0,0 +1,40 @@ +// +// BundleExtension.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 + +extension Bundle { + public func appGroup(bundle: BundleGroup) -> String { + var appGroupName: String + + switch bundle { + case .subs: + appGroupName = "SUBSCRIPTION_APP_GROUP" + } + + guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { + fatalError("Info.plist is missing \(appGroupName)") + } + return appGroup + } +} + +public enum BundleGroup { + case subs +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2a8d4acacc..03e362adc5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -695,11 +695,15 @@ B6BA95C528894A28004ABA20 /* BrowsingMenuViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */; }; B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; }; B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */; }; + BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; }; BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; }; BD862E052B30DB250073E2EE /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */; }; BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */; }; BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */; }; BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */; }; + BDA583872B98B6C700732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; + BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; + BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; @@ -2344,11 +2348,13 @@ B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BrowsingMenuViewController.storyboard; sourceTree = ""; }; B6BA95E728924730004ABA20 /* JSAlertController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = JSAlertController.storyboard; sourceTree = ""; }; B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64DownloadSession.swift; sourceTree = ""; }; + BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = ""; }; BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackCategory.swift; sourceTree = ""; }; BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackSender.swift; sourceTree = ""; }; BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNMetadataCollector.swift; sourceTree = ""; }; BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = ""; }; + BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerExtension.swift; sourceTree = ""; }; BDC234F62B27F51100D3C798 /* UniquePixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquePixel.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 = ""; }; @@ -4412,6 +4418,15 @@ path = Feedback; sourceTree = ""; }; + BDA583852B98B69C00732FDC /* Subscription */ = { + isa = PBXGroup; + children = ( + BD15DB842B959CFD00821457 /* BundleExtension.swift */, + BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */, + ); + name = Subscription; + sourceTree = ""; + }; C14882D627F2010700D59F0C /* ImportExport */ = { isa = PBXGroup; children = ( @@ -5104,6 +5119,7 @@ F143C2E51E4A4CD400CFDE3A /* Core */ = { isa = PBXGroup; children = ( + BDA583852B98B69C00732FDC /* Subscription */, 858479C72B8792C900D156C1 /* History */, EE7A92852AC6DE2500832A36 /* NetworkProtection */, 4B470ED4299C484B0086EBDC /* AppTrackingProtection */, @@ -6465,6 +6481,7 @@ 02025AD42988229800E694E7 /* ProxySocket.swift in Sources */, 02025AD62988229800E694E7 /* SocketProtocol.swift in Sources */, 02025AD82988229800E694E7 /* Tunnel.swift in Sources */, + BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */, 02025ADA2988229800E694E7 /* Port.swift in Sources */, 02025ADB2988229800E694E7 /* HTTPStreamScanner.swift in Sources */, 02025ADC2988229800E694E7 /* UInt128.swift in Sources */, @@ -6895,6 +6912,7 @@ 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, + BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */, C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, @@ -7251,6 +7269,7 @@ 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */, F1134EB01F40AC6300B73467 /* AtbParser.swift in Sources */, EE50052E29C369D300AE0773 /* FeatureFlag.swift in Sources */, + BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */, 37DF000F29F9D635002B7D3E /* SyncBookmarksAdapter.swift in Sources */, B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */, 4BE2756827304F57006B20B0 /* URLRequestExtension.swift in Sources */, @@ -7291,6 +7310,7 @@ B652DF0D287C2A6300C12A9C /* PrivacyFeatures.swift in Sources */, F10E522D1E946F8800CE1253 /* NSAttributedStringExtension.swift in Sources */, 9887DC252354D2AA005C85F5 /* Database.swift in Sources */, + BDA583872B98B6C700732FDC /* AccountManagerExtension.swift in Sources */, F143C3171E4A99D200CFDE3A /* AppURLs.swift in Sources */, C1963863283794A000298D4D /* BookmarksCachingSearch.swift in Sources */, ); @@ -9997,7 +10017,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 115.0.0; + version = 116.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dffa9b0106..3abe889c31 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "06ba37e23cd4c8d848d9d153c6e5be07956d3adc", - "version" : "115.0.0" + "revision" : "5a6c7b62f84e7c2a4ffa7803392284a055229aef", + "version" : "116.0.0" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/DuckDuckGo.entitlements b/DuckDuckGo/DuckDuckGo.entitlements index 82bd4ed6cd..0133866cab 100644 --- a/DuckDuckGo/DuckDuckGo.entitlements +++ b/DuckDuckGo/DuckDuckGo.entitlements @@ -2,12 +2,12 @@ - com.apple.developer.web-browser - com.apple.developer.networking.networkextension packet-tunnel-provider + com.apple.developer.web-browser + com.apple.security.application-groups $(GROUP_ID_PREFIX).bookmarks @@ -17,5 +17,10 @@ $(GROUP_ID_PREFIX).apptp $(GROUP_ID_PREFIX).netp + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + diff --git a/DuckDuckGo/DuckDuckGoAlpha.entitlements b/DuckDuckGo/DuckDuckGoAlpha.entitlements index 0a73901384..c9b90b996b 100644 --- a/DuckDuckGo/DuckDuckGoAlpha.entitlements +++ b/DuckDuckGo/DuckDuckGoAlpha.entitlements @@ -17,5 +17,10 @@ group.com.duckduckgo.alpha.netp group.com.duckduckgo.alpha.statistics + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index b61e407988..e5d5328d08 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -216,6 +216,8 @@ UIStatusBarHidden + SUBSCRIPTION_APP_GROUP + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) UIStatusBarStyle UIStatusBarStyleDefault UISupportedInterfaceOrientations~ipad diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index b94ef3a23a..30f48b19de 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -410,7 +410,7 @@ extension SettingsViewModel { @available(iOS 15.0, *) func restoreAccountPurchase() async { DispatchQueue.main.async { self.isRestoringSubscription = true } - let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: DispatchQueue.main.async { diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 9cd2090506..98fd019c13 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -207,9 +207,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, + emailAccessToken: emailAccessToken, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { case .success(let transactionJWS): purchaseTransactionJWS = transactionJWS + case .failure(let error): switch error { @@ -224,7 +227,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } setTransactionStatus(.polling) - switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { + switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { case .success(let purchaseUpdate): await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure: @@ -323,7 +327,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func restoreAccountFromAppStorePurchase() async throws { setTransactionStatus(.restoring) - let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: setTransactionStatus(.idle) @@ -356,7 +360,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } } - // swiftlint:enable type_body_length #endif diff --git a/PacketTunnelProvider/Info.plist b/PacketTunnelProvider/Info.plist index b9e2818ff1..8d86253eb9 100644 --- a/PacketTunnelProvider/Info.plist +++ b/PacketTunnelProvider/Info.plist @@ -4,6 +4,8 @@ DuckDuckGoGroupIdentifierPrefix $(GROUP_ID_PREFIX) + SUBSCRIPTION_APP_GROUP + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) NSExtension NSExtensionPointIdentifier diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index f9e7fa92f7..408e9c6015 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -277,7 +277,11 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } private static func entitlementCheck() async -> Result { - let result = await AccountManager().hasEntitlement(for: .networkProtection) +#if ALPHA + SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging +#endif + + let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).hasEntitlement(for: .networkProtection) switch result { case .success(let hasEntitlement): return .success(hasEntitlement) diff --git a/PacketTunnelProvider/PacketTunnelProvider.entitlements b/PacketTunnelProvider/PacketTunnelProvider.entitlements index 5e171bb76b..d4e6617d26 100644 --- a/PacketTunnelProvider/PacketTunnelProvider.entitlements +++ b/PacketTunnelProvider/PacketTunnelProvider.entitlements @@ -11,5 +11,10 @@ $(GROUP_ID_PREFIX).apptp $(GROUP_ID_PREFIX).netp + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + diff --git a/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements b/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements index f814f005e7..a346d69b16 100644 --- a/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements +++ b/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements @@ -11,5 +11,10 @@ group.com.duckduckgo.alpha.apptp group.com.duckduckgo.alpha.netp + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + From 28c56c6d874b20250a84038fbbf8fbbd001a2a86 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 6 Mar 2024 12:58:46 -0800 Subject: [PATCH 10/20] Remove CGNAT range (#2524) Task/Issue URL: https://app.asana.com/0/414235014887631/1206718079780271/f Tech Design URL: CC: Description: Client PR for duckduckgo/BrowserServicesKit#691 --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 03e362adc5..0f50b88fa0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10017,7 +10017,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.0.0; + version = 116.0.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3abe889c31..f454fcbcfa 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "5a6c7b62f84e7c2a4ffa7803392284a055229aef", - "version" : "116.0.0" + "revision" : "12b960a8cc18cf080c41422ac7913e1c0cd9d874", + "version" : "116.0.1" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" From 5405554cdf723b21ddae9abb08c639568e9fbfdf Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Wed, 6 Mar 2024 22:31:21 +0100 Subject: [PATCH 11/20] Validate correct environment (#2546) Task/Issue URL: https://app.asana.com/0/0/1206645876801807/f Tech Design URL: CC: Description: Fire Unique pixel in case we are on dev environment but we most likely should not be. --- Core/PixelEvent.swift | 3 +++ DuckDuckGo/AppDelegate.swift | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 527ce9c495..be65c36f6a 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -499,6 +499,7 @@ extension Pixel { case syncRemoveDeviceError case syncDeleteAccountError case syncLoginExistingAccountError + case syncWrongEnvironment case swipeTabsUsed case swipeTabsUsedDaily @@ -1004,6 +1005,8 @@ extension Pixel.Event { case .syncDeleteAccountError: return "m_d_sync_delete_account_error" case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error" + case .syncWrongEnvironment: return "m_d_sync_wrong_environment_u" + case .swipeTabsUsed: return "m_swipe-tabs-used" case .swipeTabsUsedDaily: return "m_swipe-tabs-used-daily" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 3bd12868ed..72323da1e2 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -399,6 +399,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard !testing else { return } syncService.initializeIfNeeded() + if syncService.authState == .active && + (InternalUserStore().isInternalUser == false && syncService.serverEnvironment == .development) { + UniquePixel.fire(pixel: .syncWrongEnvironment) + } syncDataProviders.setUpDatabaseCleanersIfNeeded(syncService: syncService) if !(overlayWindow?.rootViewController is AuthenticationViewController) { From 68151f84886e7dbb0a156655bb47619d04028734 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:08:40 -0500 Subject: [PATCH 12/20] Remove isSubscriptionEnabled check when attempting to delete NetP token (#2548) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0f50b88fa0..6600d13023 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10017,7 +10017,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.0.1; + version = 116.0.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f454fcbcfa..141daf2b93 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "12b960a8cc18cf080c41422ac7913e1c0cd9d874", - "version" : "116.0.1" + "revision" : "5b354c6c91dc6059afd9b6f2b8f2b212d5b50046", + "version" : "116.0.2" } }, { From da0450e9052f2c868ebd942655402d8520d382d5 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:18:00 -0500 Subject: [PATCH 13/20] Handle expired entitlement in NetP (#2525) Task/Issue URL: https://app.asana.com/0/0/1206409081785857/f Description: This PR adds proper entitlement expiration handling to NetP. Steps to test this PR: Go through the subscription flow With the VPN on, wait till the subscription expires. You should encounter the messaging & tunnel shutdown Resubscribe. Keep the VPN off. Once the subscription expires, try to reconnect. Again you should see the messaging. --- Core/BundleExtensions.swift | 40 +++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 32 ++++++++--- DuckDuckGo/CriticalAlerts.swift | 9 +-- DuckDuckGo/MainViewController+Segues.swift | 8 +++ DuckDuckGo/MainViewController.swift | 56 +++++++++++++++++-- ...NetworkProtectionDebugViewController.swift | 10 ++++ DuckDuckGo/SettingsSubscriptionView.swift | 8 ++- DuckDuckGo/SettingsViewModel.swift | 7 ++- DuckDuckGo/UserText.swift | 14 +++++ DuckDuckGo/en.lproj/Localizable.strings | 18 ++++++ ...etworkProtectionPacketTunnelProvider.swift | 2 + ...orkProtectionUNNotificationPresenter.swift | 6 +- 14 files changed, 192 insertions(+), 28 deletions(-) create mode 100644 Core/BundleExtensions.swift diff --git a/Core/BundleExtensions.swift b/Core/BundleExtensions.swift new file mode 100644 index 0000000000..2ba420193f --- /dev/null +++ b/Core/BundleExtensions.swift @@ -0,0 +1,40 @@ +// +// BundleExtensions.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 + +extension Bundle { + public func appGroup(bundle: BundleGroup) -> String { + var appGroupName: String + + switch bundle { + case .subs: + appGroupName = "SUBSCRIPTION_APP_GROUP" + } + + guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { + fatalError("Info.plist is missing \(appGroupName)") + } + return appGroup + } +} + +public enum BundleGroup { + case subs +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6600d13023..ff266ce02e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -705,6 +705,7 @@ BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; }; + BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */; }; @@ -4421,7 +4422,6 @@ BDA583852B98B69C00732FDC /* Subscription */ = { isa = PBXGroup; children = ( - BD15DB842B959CFD00821457 /* BundleExtension.swift */, BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */, ); name = Subscription; @@ -4822,6 +4822,7 @@ children = ( EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */, EE9D68DD2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift */, + BD15DB842B959CFD00821457 /* BundleExtension.swift */, ); name = NetworkProtection; sourceTree = ""; @@ -6754,6 +6755,7 @@ D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */, + BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, @@ -10017,7 +10019,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.0.2; + version = 116.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 141daf2b93..d017bc495e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "5b354c6c91dc6059afd9b6f2b8f2b212d5b50046", - "version" : "116.0.2" + "revision" : "9bafa0271688bae67ab9c1ba97d5e69f80fe71df", + "version" : "116.1.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 72323da1e2..b0b88edc4a 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -74,6 +74,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() + private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults #endif private var autoClear: AutoClear? @@ -301,12 +302,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() #endif -#if NETWORK_PROTECTION && SUBSCRIPTION - if VPNSettings(defaults: .networkProtectionGroupDefaults).showEntitlementAlert { - presentExpiredEntitlementAlert() - } -#endif - RemoteMessaging.registerBackgroundRefreshTaskHandler( bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: AppDependencyProvider.shared.appSettings.favoritesDisplayMode @@ -354,13 +349,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window?.rootViewController?.present(alertController, animated: true, completion: nil) } +#if NETWORK_PROTECTION private func presentExpiredEntitlementAlert() { - let alertController = CriticalAlerts.makeExpiredEntitlementAlert() - window?.rootViewController?.present(alertController, animated: true) { - VPNSettings(defaults: .networkProtectionGroupDefaults).apply(change: .setShowEntitlementAlert(false)) + let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in + self?.mainViewController?.segueToPrivacyPro() + } + window?.rootViewController?.present(alertController, animated: true) { [weak self] in + self?.tunnelDefaults.showEntitlementAlert = false } } + private func presentExpiredEntitlementNotification() { + let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( + settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + defaults: .networkProtectionGroupDefaults, + wrappee: NetworkProtectionUNNotificationPresenter() + ) + presenter.showEntitlementNotification() + } +#endif + private func cleanUpMacPromoExperiment2() { UserDefaults.standard.removeObject(forKey: "com.duckduckgo.ios.macPromoMay23.exp2.cohort") } @@ -445,6 +453,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION widgetRefreshModel.refreshVPNWidget() + + if tunnelDefaults.showEntitlementAlert { + presentExpiredEntitlementAlert() + } + + presentExpiredEntitlementNotification() #endif } diff --git a/DuckDuckGo/CriticalAlerts.swift b/DuckDuckGo/CriticalAlerts.swift index c62183fe0c..0d3f25796f 100644 --- a/DuckDuckGo/CriticalAlerts.swift +++ b/DuckDuckGo/CriticalAlerts.swift @@ -70,19 +70,20 @@ struct CriticalAlerts { return alertController } - static func makeExpiredEntitlementAlert() -> UIAlertController { + static func makeExpiredEntitlementAlert(completion: @escaping () -> Void) -> UIAlertController { let alertController = UIAlertController(title: UserText.vpnAccessRevokedAlertTitle, message: UserText.vpnAccessRevokedAlertMessage, preferredStyle: .alert) alertController.overrideUserInterfaceStyle() let closeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionCancel, style: .cancel) - let signInButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in - UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) + let subscribeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in + completion() } alertController.addAction(closeButton) - alertController.addAction(signInButton) + alertController.addAction(subscribeButton) + alertController.preferredAction = subscribeButton return alertController } diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index c1e8a9560f..c352df8fb2 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -203,6 +203,14 @@ extension MainViewController { launchSettings() } + func segueToPrivacyPro() { + os_log(#function, log: .generalLog, type: .debug) + hideAllHighlightsIfNeeded() + launchSettings { + $0.shouldNavigateToSubscriptionFlow = true + } + } + func segueToDebugSettings() { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 41df7f48c2..160c7ee248 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -108,7 +108,8 @@ class MainViewController: UIViewController { private var emailCancellables = Set() #if NETWORK_PROTECTION - private var netpCancellables = Set() + private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults + private var vpnCancellables = Set() #endif private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger @@ -1333,9 +1334,56 @@ class MainViewController: UIViewController { .sink { [weak self] notification in self?.onNetworkProtectionAccountSignIn(notification) } - .store(in: &netpCancellables) + .store(in: &vpnCancellables) + + NotificationCenter.default.publisher(for: .vpnEntitlementMessagingDidChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.onNetworkProtectionEntitlementMessagingChange() + } + .store(in: &vpnCancellables) + + let notificationCallback: CFNotificationCallback = { _, _, name, _, _ in + if let name { + NotificationCenter.default.post(name: Notification.Name(name.rawValue as String), + object: nil) + } + } + + CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), + UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()), + notificationCallback, + Notification.Name.vpnEntitlementMessagingDidChange.rawValue as CFString, + nil, .deliverImmediately) } - + + private func onNetworkProtectionEntitlementMessagingChange() { + if tunnelDefaults.showEntitlementAlert { + presentExpiredEntitlementAlert() + } + + presentExpiredEntitlementNotification() + } + + private func presentExpiredEntitlementAlert() { + let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in + self?.segueToPrivacyPro() + } + dismiss(animated: true) { + self.present(alertController, animated: true, completion: nil) + self.tunnelDefaults.showEntitlementAlert = false + } + } + + private func presentExpiredEntitlementNotification() { + let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( + settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + defaults: .networkProtectionGroupDefaults, + wrappee: NetworkProtectionUNNotificationPresenter() + ) + presenter.showEntitlementNotification() + } + @objc private func onNetworkProtectionAccountSignIn(_ notification: Notification) { guard let token = AccountManager().accessToken else { @@ -1343,7 +1391,7 @@ class MainViewController: UIViewController { return } - VPNSettings(defaults: .networkProtectionGroupDefaults).resetEntitlementMessaging() + tunnelDefaults.resetEntitlementMessaging() print("[NetP Subscription] Reset expired entitlement messaging") Task { diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 73f2453a99..f3f98803ec 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -87,6 +87,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { enum ExtensionDebugCommandRows: Int, CaseIterable { case triggerTestNotification case shutDown + case showEntitlementMessaging + case resetEntitlementMessaging } enum NetworkPathRows: Int, CaseIterable { @@ -364,6 +366,10 @@ final class NetworkProtectionDebugViewController: UITableViewController { cell.textLabel?.text = "Test Notification" case .shutDown: cell.textLabel?.text = "Disable VPN From Extension" + case .showEntitlementMessaging: + cell.textLabel?.text = "Show Entitlement Messaging" + case .resetEntitlementMessaging: + cell.textLabel?.text = "Reset Entitlement Messaging" case .none: break } @@ -379,6 +385,10 @@ final class NetworkProtectionDebugViewController: UITableViewController { Task { await NetworkProtectionDebugUtilities().disableConnectOnDemandAndShutDown() } + case .showEntitlementMessaging: + UserDefaults.networkProtectionGroupDefaults.enableEntitlementMessaging() + case .resetEntitlementMessaging: + UserDefaults.networkProtectionGroupDefaults.resetEntitlementMessaging() case .none: break } diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 8dc36fc915..4b2c71b8e5 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -209,7 +209,13 @@ struct SettingsSubscriptionView: View { } } }) - + + .onChange(of: viewModel.shouldNavigateToSubscriptionFlow, perform: { value in + if value { + isShowingsubScriptionFlow = true + } + }) + .onReceive(subscriptionFlowViewModel.$selectedFeature) { value in guard let value else { return } viewModel.onAppearNavigationTarget = value diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 30f48b19de..1e31387cd3 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -77,7 +77,8 @@ final class SettingsViewModel: ObservableObject { // Add more views as needed here... @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false - + @Published var shouldNavigateToSubscriptionFlow = false + @Published var shouldShowNetP = false @Published var shouldShowDBP = false @Published var shouldShowITP = false @@ -102,7 +103,7 @@ final class SettingsViewModel: ObservableObject { // Used to automatically navigate on Appear to a specific section enum SettingsSection: String { - case none, netP, dbp, itr + case none, netP, dbp, itr, subscriptionFlow } @Published var onAppearNavigationTarget: SettingsSection @@ -508,6 +509,8 @@ extension SettingsViewModel { self.shouldNavigateToDBP = true case .itr: self.shouldNavigateToITP = true + case .subscriptionFlow: + self.shouldNavigateToSubscriptionFlow = true default: break } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index bf53406e72..134db22317 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -950,6 +950,20 @@ But if you *do* want a peek under the hood, you can find more information about static let networkProtectionNotificationPromptTitle = NSLocalizedString("network-protection.waitlist.notification-prompt-title", value: "Know the instant you're invited", comment: "Title for the alert to confirm enabling notifications") static let networkProtectionNotificationPromptDescription = NSLocalizedString("network-protection.waitlist.notification-prompt-description", value: "Get a notification when your copy of Network Protection early access is ready.", comment: "Subtitle for the alert to confirm enabling notifications") + static let networkProtectionNotificationsTitle = NSLocalizedString("network.protection.notification.title", value: "DuckDuckGo", comment: "The title of the notifications shown from Network Protection") + static let networkProtectionConnectionSuccessNotificationBody = NSLocalizedString("network.protection.success.notification.body", value: "Network Protection is On. Your location and online activity are protected.", comment: "The body of the notification shown when Network Protection reconnects successfully") + static func networkProtectionConnectionSuccessNotificationBody(serverLocation: String) -> String { + let localized = NSLocalizedString( + "network.protection.success.notification.subtitle.including.serverLocation", + value: "Routing device traffic through %@.", + comment: "The body of the notification shown when Network Protection connects successfully with the city + state/country as formatted parameter" + ) + return String(format: localized, serverLocation) + } + static let networkProtectionConnectionInterruptedNotificationBody = NSLocalizedString("network.protection.interrupted.notification.body", value: "Network Protection was interrupted. Attempting to reconnect now...", comment: "The body of the notification shown when Network Protection's connection is interrupted") + static let networkProtectionConnectionFailureNotificationBody = NSLocalizedString("network.protection.failure.notification.body", value: "Network Protection failed to connect. Please try again later.", comment: "The body of the notification shown when Network Protection fails to reconnect") + static let networkProtectionEntitlementExpiredNotificationBody = NSLocalizedString("network.protection.entitlement.expired.notification.body", value: "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.", comment: "The body of the notification when Privacy Pro subscription expired") + // MARK: Settings Screeen public static let settingsTitle = NSLocalizedString("settings.title", value: "Settings", comment: "Title for the Settings View") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 7025259c86..5654129084 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1528,6 +1528,15 @@ https://duckduckgo.com/mac"; /* Subtitle text for the Network Protection settings row */ "network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Join the private waitlist"; +/* The body of the notification when Privacy Pro subscription expired */ +"network.protection.entitlement.expired.notification.body" = "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN."; + +/* The body of the notification shown when Network Protection fails to reconnect */ +"network.protection.failure.notification.body" = "Network Protection failed to connect. Please try again later."; + +/* The body of the notification shown when Network Protection's connection is interrupted */ +"network.protection.interrupted.notification.body" = "Network Protection was interrupted. Attempting to reconnect now..."; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; @@ -1543,6 +1552,9 @@ https://duckduckgo.com/mac"; /* Title for the network protection invite success view */ "network.protection.invite.success.title" = "Success! You’re in."; +/* The title of the notifications shown from Network Protection */ +"network.protection.notification.title" = "DuckDuckGo"; + /* Title text for an iOS quick action that opens VPN settings */ "network.protection.quick-action.open-vpn" = "Open VPN"; @@ -1591,6 +1603,12 @@ https://duckduckgo.com/mac"; /* Title label text for the status view when netP is disconnected */ "network.protection.status.view.title" = "Network Protection"; +/* The body of the notification shown when Network Protection reconnects successfully */ +"network.protection.success.notification.body" = "Network Protection is On. Your location and online activity are protected."; + +/* The body of the notification shown when Network Protection connects successfully with the city + state/country as formatted parameter */ +"network.protection.success.notification.subtitle.including.serverLocation" = "Routing device traffic through %@."; + /* Title for the button to link to the iOS app settings and enable notifications app-wide. */ "network.protection.turn.on.notifications.button.title" = "Turn On Notifications"; diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 408e9c6015..373694a2c8 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -217,6 +217,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) let notificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( settings: settings, + defaults: .networkProtectionGroupDefaults, wrappee: notificationsPresenter ) notificationsPresenter.requestAuthorization() @@ -228,6 +229,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents, settings: settings, + defaults: .networkProtectionGroupDefaults, isSubscriptionEnabled: isSubscriptionEnabled, entitlementCheck: Self.entitlementCheck) startMonitoringMemoryPressureEvents() diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift index 0afc858191..7e06bdbf1e 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift @@ -105,7 +105,7 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio func showSupersededNotification() { } - func showEntitlementNotification(completion: @escaping (Error?) -> Void) { + func showEntitlementNotification() { let identifier = NetworkProtectionNotificationIdentifier.entitlement.rawValue let content = notificationContent(body: UserText.networkProtectionEntitlementExpiredNotificationBody) let request = UNNotificationRequest(identifier: identifier, content: content, trigger: .none) @@ -113,9 +113,7 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio requestAlertAuthorization { authorized in guard authorized else { return } self.userNotificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier]) - self.userNotificationCenter.add(request) { error in - completion(error) - } + self.userNotificationCenter.add(request) } } From 5a4d936fdd2ce488c53cb8be1f68e5b64caed83c Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 7 Mar 2024 12:39:43 +0600 Subject: [PATCH 14/20] Add #URL macro (#2540) Task/Issue URL: https://app.asana.com/0/42792087274227/1206542455948401/f BSK PR: duckduckgo/BrowserServicesKit#657 toolbox PR: duckduckgo/apple-toolbox#2 --- .github/workflows/codeql.yml | 1 + .github/workflows/end-to-end.yml | 1 + .github/workflows/nightly.yml | 2 + .github/workflows/pr.yml | 2 + .github/workflows/sync-end-to-end.yml | 1 + Core/AppURLs.swift | 5 +- Core/BookmarksImporter.swift | 7 +- Core/DataStoreWarmup.swift | 3 +- Core/UserAgentManager.swift | 7 +- DuckDuckGo.xcodeproj/project.pbxproj | 70 ++++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 17 +++-- .../AdAttribution/AdAttributionFetcher.swift | 3 +- .../AutofillLoginDetailsViewModel.swift | 9 +-- DuckDuckGo/DesktopDownloadViewModel.swift | 3 +- DuckDuckGo/FirewallManager.swift | 7 +- DuckDuckGo/RemoteMessageRequest.swift | 10 +-- .../AddressDisplayHelperTests.swift | 33 +++++---- DuckDuckGoTests/AppURLsTests.swift | 44 ++++++------ .../BookmarksCachingSearchTests.swift | 17 ++--- DuckDuckGoTests/BookmarksImporterTests.swift | 8 ++- DuckDuckGoTests/DaxDialogTests.swift | 24 ++++--- DuckDuckGoTests/DownloadMocks.swift | 3 +- DuckDuckGoTests/DownloadTestsHelper.swift | 4 +- .../FaviconRequestModifierTests.swift | 6 +- DuckDuckGoTests/FaviconsTests.swift | 12 ++-- .../FireproofFaviconUpdaterTests.swift | 12 ++-- DuckDuckGoTests/HTTPSUpgradeTests.swift | 15 ++-- .../MenuBookmarksViewModelTests.swift | 9 +-- DuckDuckGoTests/MockSecureVault.swift | 13 ++-- DuckDuckGoTests/MockUserAgent.swift | 5 +- .../NotFoundCachingDownloaderTests.swift | 8 ++- DuckDuckGoTests/PrivacyIconLogicTests.swift | 18 ++--- DuckDuckGoTests/TabTests.swift | 10 +-- DuckDuckGoTests/TabsModelTests.swift | 12 ++-- .../TrackerAnimationLogicTests.swift | 10 +-- DuckDuckGoTests/UserAgentTests.swift | 14 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 7 +- .../Network/ProductWaitlistRequest.swift | 5 +- .../Sources/WaitlistMocks/TestWaitlist.swift | 3 +- .../WaitlistViewModelTests.swift | 6 +- Widgets/WidgetViews.swift | 5 +- fastlane/Fastfile | 6 +- 44 files changed, 299 insertions(+), 162 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8a6d1ba63b..c1b68f040c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,6 +44,7 @@ jobs: -scheme "DuckDuckGo" \ -destination "platform=iOS Simulator,name=iPhone 14,OS=16.4" -skipPackagePluginValidation \ + -skipMacroValidation \ - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index 5cef39c0a7..68abd594b3 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -44,6 +44,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ ONLY_ACTIVE_ARCH=NO \ | tee xcodebuild.log diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ae31bc6122..a53560b741 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -47,6 +47,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | tee xcodebuild.log \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml @@ -89,6 +90,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml - name: Publish unit tests report diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bdbdb037cc..462a3fc004 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -82,6 +82,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ DDG_SLOW_COMPILE_CHECK_THRESHOLD=250 \ | tee xcodebuild.log \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml @@ -188,6 +189,7 @@ jobs: -derivedDataPath "DerivedData" \ -configuration "Release" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | xcbeautify create-asana-task: diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index 78d7e2ad1e..af560b1dd5 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -44,6 +44,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ ONLY_ACTIVE_ARCH=NO \ | tee xcodebuild.log diff --git a/Core/AppURLs.swift b/Core/AppURLs.swift index e2250c3744..eff05031d3 100644 --- a/Core/AppURLs.swift +++ b/Core/AppURLs.swift @@ -17,8 +17,9 @@ // limitations under the License. // -import Foundation import BrowserServicesKit +import Foundation +import Macros public extension URL { @@ -48,7 +49,7 @@ public extension URL { static let exti = URL(string: "\(base)/exti/\(devMode)")! static let feedback = URL(string: "\(base)/feedback.js?type=app-feedback")! - static let appStore = URL(string: "https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361")! + static let appStore = #URL("https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361") static let mac = URL(string: "\(base)/mac")! static let windows = URL(string: "\(base)/windows")! diff --git a/Core/BookmarksImporter.swift b/Core/BookmarksImporter.swift index 177dcf2955..4615e47689 100644 --- a/Core/BookmarksImporter.swift +++ b/Core/BookmarksImporter.swift @@ -17,11 +17,12 @@ // limitations under the License. // +import Bookmarks import Common import Foundation -import SwiftSoup -import Bookmarks +import Macros import Persistence +import SwiftSoup public enum BookmarksImportError: Error { case invalidHtmlNoDLTag @@ -217,7 +218,7 @@ final public class BookmarksImporter { static let FavoritesFolder = "DuckDuckGo Favorites" static let BookmarksFolder = "DuckDuckGo Bookmarks" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = URL(string: "https://duckduckgo.com")! + static let bookmarkURL = #URL("https://duckduckgo.com") static let favoriteAttribute = "duckduckgo:favorite" static let isFavorite = "true" static let idAttribute = "id" diff --git a/Core/DataStoreWarmup.swift b/Core/DataStoreWarmup.swift index 79087b9fc2..6554f0ca8c 100644 --- a/Core/DataStoreWarmup.swift +++ b/Core/DataStoreWarmup.swift @@ -18,6 +18,7 @@ // import Combine +import Macros import WebKit /// WKWebsiteDataStore is basically non-functional until a web view has been instanciated and a page is successfully loaded. @@ -27,7 +28,7 @@ public class DataStoreWarmup { @MainActor public func ensureReady() async { - await BlockingNavigationDelegate().loadInBackgroundWebView(url: URL(string: "about:blank")!) + await BlockingNavigationDelegate().loadInBackgroundWebView(url: #URL("about:blank")) } } diff --git a/Core/UserAgentManager.swift b/Core/UserAgentManager.swift index 80db1e64a0..32f90094d2 100644 --- a/Core/UserAgentManager.swift +++ b/Core/UserAgentManager.swift @@ -19,10 +19,11 @@ // swiftlint:disable file_length -import Foundation -import WebKit import BrowserServicesKit import Common +import Foundation +import Macros +import WebKit public protocol UserAgentManager { @@ -46,7 +47,7 @@ public class DefaultUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) + webview.load(URLRequest.developerInitiated(#URL("about:blank"))) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ff266ce02e..2295ddf9f0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -687,6 +687,14 @@ B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9847BFFD27A2DDB400DB07AA /* ContentBlocking.swift */; }; B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DF11287C336E00C12A9C /* ContentBlockingUpdating.swift */; }; B652DF13287C373A00C12A9C /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DEFE287BF1FE00C12A9C /* ScriptSourceProviding.swift */; }; + B6A26C042B98358B00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C032B98358B00DF9EAD /* Macros */; }; + B6A26C062B98359A00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C052B98359A00DF9EAD /* Macros */; }; + B6A26C082B9835A000DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C072B9835A000DF9EAD /* Macros */; }; + B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C092B9835A800DF9EAD /* Macros */; }; + B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0B2B9835AD00DF9EAD /* Macros */; }; + B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0D2B9835B100DF9EAD /* Macros */; }; + B6A26C102B9835B400DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0F2B9835B400DF9EAD /* Macros */; }; + B6A26C122B9835B800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C112B9835B800DF9EAD /* Macros */; }; B6AD9E3628D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3428D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift */; }; B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3528D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift */; }; B6AD9E3828D4512E0019CDE9 /* EmbeddedTrackerDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9801F08927E4B21100191874 /* EmbeddedTrackerDataTests.swift */; }; @@ -2697,6 +2705,7 @@ 0202569029881ECA00E694E7 /* CocoaAsyncSocket in Frameworks */, 02025664298818B200E694E7 /* NetworkExtension.framework in Frameworks */, 4B470EE4299C6DFB0086EBDC /* Core.framework in Frameworks */, + B6A26C062B98359A00DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2704,6 +2713,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2722,6 +2732,7 @@ 853273B624FFE0BB00E3C778 /* WidgetKit.framework in Frameworks */, 0238E44F29C0FAA100615E30 /* FindInPageIOSJSSupport in Frameworks */, 3760DFED299315EF0045A446 /* Waitlist in Frameworks */, + B6A26C042B98358B00DF9EAD /* Macros in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */, 31E69A63280F4CB600478327 /* DuckUI in Frameworks */, @@ -2737,6 +2748,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */, F486D3362506A037002D07D7 /* OHHTTPStubs in Frameworks */, F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */, F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, @@ -2767,6 +2779,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C102B9835B400DF9EAD /* Macros in Frameworks */, 1E1D8B632995143200C96994 /* OHHTTPStubs in Frameworks */, 1E1D8B652995143200C96994 /* OHHTTPStubsSwift in Frameworks */, ); @@ -2776,6 +2789,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */, F486D31D2506980E002D07D7 /* Swifter in Frameworks */, 85F21DC021123B03002631A6 /* Core.framework in Frameworks */, ); @@ -2786,6 +2800,7 @@ buildActionMask = 2147483647; files = ( 98D4B7DF2944DDBD0068814D /* Core.framework in Frameworks */, + B6A26C122B9835B800DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2794,6 +2809,7 @@ buildActionMask = 2147483647; files = ( 4B948E2629DCCDB9002531FA /* Persistence in Frameworks */, + B6A26C082B9835A000DF9EAD /* Macros in Frameworks */, 98A50962294B48A400D10880 /* Bookmarks in Frameworks */, 1E60989B290009C700A508F9 /* Common in Frameworks */, 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */, @@ -5692,6 +5708,7 @@ name = PacketTunnelProvider; packageProductDependencies = ( 0202568F29881ECA00E694E7 /* CocoaAsyncSocket */, + B6A26C052B98359A00DF9EAD /* Macros */, ); productName = PacketTunnelProvider; productReference = 02025662298818B100E694E7 /* PacketTunnelProvider.appex */; @@ -5712,6 +5729,9 @@ 025CCFE82582601C001CD5BB /* PBXTargetDependency */, ); name = FingerprintingUITests; + packageProductDependencies = ( + B6A26C0D2B9835B100DF9EAD /* Macros */, + ); productName = FingerprintingUITests; productReference = 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; @@ -5771,6 +5791,7 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, + B6A26C032B98358B00DF9EAD /* Macros */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -5796,6 +5817,7 @@ F486D3372506A225002D07D7 /* OHHTTPStubsSwift */, EEFAB4662A73C230008A38E4 /* NetworkProtectionTestUtils */, F115ED9B2B4EFC8E001A0453 /* TestUtils */, + B6A26C092B9835A800DF9EAD /* Macros */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -5859,6 +5881,7 @@ packageProductDependencies = ( 1E1D8B622995143200C96994 /* OHHTTPStubs */, 1E1D8B642995143200C96994 /* OHHTTPStubsSwift */, + B6A26C0F2B9835B400DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 85D33FCB25C97B6E002B91A6 /* IntegrationTests.xctest */; @@ -5881,6 +5904,7 @@ name = AtbUITests; packageProductDependencies = ( F486D31C2506980E002D07D7 /* Swifter */, + B6A26C0B2B9835AD00DF9EAD /* Macros */, ); productName = AtbIntegrationTests; productReference = 85F21DAD210F5E32002631A6 /* AtbUITests.xctest */; @@ -5902,6 +5926,7 @@ ); name = PerformanceTests; packageProductDependencies = ( + B6A26C112B9835B800DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */; @@ -5954,6 +5979,7 @@ EE8E56892A56BCE400F11DCA /* NetworkProtection */, D61CDA152B7CF77300A0FBB9 /* Subscription */, D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, + B6A26C072B9835A000DF9EAD /* Macros */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -10019,7 +10045,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.1.0; + version = 117.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10027,7 +10053,7 @@ repositoryURL = "https://github.com/duckduckgo/apple-toolbox.git"; requirement = { kind = exactVersion; - version = 1.0.0; + version = 2.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { @@ -10178,6 +10204,46 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; + B6A26C032B98358B00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C052B98359A00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C072B9835A000DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C092B9835A800DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0B2B9835AD00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0D2B9835B100DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0F2B9835B400DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C112B9835B800DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; B6F997CB2B8F380A00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d017bc495e..a17cb5bc7a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/apple-toolbox.git", "state" : { - "revision" : "e3dc4faf70ca09718a2d20d5c47b449389e8c153", - "version" : "1.0.0" + "revision" : "d51beaf1736013b530576ace13a16d6d1a63742c", + "version" : "2.0.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "9bafa0271688bae67ab9c1ba97d5e69f80fe71df", - "version" : "116.1.0" + "revision" : "dbe75fa0ee9e3b740d520d5be7967e2c5239dfb5", + "version" : "117.0.0" } }, { @@ -135,6 +135,15 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", + "version" : "509.1.1" + } + }, { "identity" : "swifter", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift index a9d3d31a80..6193491b4f 100644 --- a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift +++ b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift @@ -19,6 +19,7 @@ import AdServices import Common +import Macros protocol AdAttributionFetcher { func fetch() async -> AdServicesAttributionResponse? @@ -116,7 +117,7 @@ struct DefaultAdAttributionFetcher: AdAttributionFetcher { } private struct Constant { - static let attributionServiceURL = URL(string: "https://api-adservices.apple.com/api/v1/")! + static let attributionServiceURL = #URL("https://api-adservices.apple.com/api/v1/") static let maxRetries = 3 } } diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 2c7ec10da8..5aacbeea91 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -19,14 +19,15 @@ // swiftlint:disable file_length -import Foundation import BrowserServicesKit import Common -import SwiftUI import Core +import DDGSync import DesignResourcesKit +import Foundation +import Macros import SecureStorage -import DDGSync +import SwiftUI protocol AutofillLoginDetailsViewModelDelegate: AnyObject { func autofillLoginDetailsViewModelDidSave() @@ -58,7 +59,7 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } enum Constants { - static let privateEmailURL = URL(string: "https://duckduckgo.com/email")! + static let privateEmailURL = #URL("https://duckduckgo.com/email") } weak var delegate: AutofillLoginDetailsViewModelDelegate? diff --git a/DuckDuckGo/DesktopDownloadViewModel.swift b/DuckDuckGo/DesktopDownloadViewModel.swift index 6d52883345..9a14e3b360 100644 --- a/DuckDuckGo/DesktopDownloadViewModel.swift +++ b/DuckDuckGo/DesktopDownloadViewModel.swift @@ -18,11 +18,12 @@ // import Foundation +import Macros import UIKit final class DesktopDownloadViewModel: ObservableObject { - static let defaultURL = URL(string: "https://duckduckgo.com/")! + static let defaultURL = #URL("https://duckduckgo.com/") static let prefix = "https://" private var platform: DesktopDownloadPlatform diff --git a/DuckDuckGo/FirewallManager.swift b/DuckDuckGo/FirewallManager.swift index 6fb8e0a029..0035b6161c 100644 --- a/DuckDuckGo/FirewallManager.swift +++ b/DuckDuckGo/FirewallManager.swift @@ -23,6 +23,7 @@ import Foundation import NetworkExtension import BrowserServicesKit import Common +import Macros public protocol FirewallDelegate: AnyObject { func statusDidChange(newStatus: NEVPNStatus) @@ -80,10 +81,10 @@ public class FirewallManager: FirewallManaging { config.requestCachePolicy = .reloadIgnoringLocalCacheData config.urlCache = nil let session = URLSession(configuration: config) - let url = URL(string: "https://bad_url") - + let url = #URL("https://bad_url") + os_log("[INFO] Calling dummy URL to force VPN", log: FirewallManager.apptpLog, type: .debug) - _ = try? await session.data(from: url!) + _ = try? await session.data(from: url) os_log("[INFO] Response from dummy URL while activating VPN", log: FirewallManager.apptpLog, type: .debug) } diff --git a/DuckDuckGo/RemoteMessageRequest.swift b/DuckDuckGo/RemoteMessageRequest.swift index 2bfcd698fa..2aa0192467 100644 --- a/DuckDuckGo/RemoteMessageRequest.swift +++ b/DuckDuckGo/RemoteMessageRequest.swift @@ -17,20 +17,20 @@ // limitations under the License. // - -import Foundation import BrowserServicesKit -import RemoteMessaging import Core +import Foundation +import Macros import Networking +import RemoteMessaging public struct RemoteMessageRequest { private var endpoint: URL { #if DEBUG - return URL(string: "https://raw.githubusercontent.com/duckduckgo/remote-messaging-config/main/samples/ios/sample1.json")! + return #URL("https://raw.githubusercontent.com/duckduckgo/remote-messaging-config/main/samples/ios/sample1.json") #else - return URL(string: "https://staticcdn.duckduckgo.com/remotemessaging/config/v1/ios-config.json")! + return #URL("https://staticcdn.duckduckgo.com/remotemessaging/config/v1/ios-config.json") #endif } diff --git a/DuckDuckGoTests/AddressDisplayHelperTests.swift b/DuckDuckGoTests/AddressDisplayHelperTests.swift index c4122c4f20..e9fae0c49e 100644 --- a/DuckDuckGoTests/AddressDisplayHelperTests.swift +++ b/DuckDuckGoTests/AddressDisplayHelperTests.swift @@ -16,7 +16,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // + +import Macros import XCTest + @testable import DuckDuckGo class AddressDisplayHelperTests: XCTestCase { @@ -25,36 +28,36 @@ class AddressDisplayHelperTests: XCTestCase { func testShortURL() { - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com")!), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com/some/path")!), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.subdomain.duckduckgo.com/some/path")!), "subdomain.duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://m.duckduckgo.com/some/path")!), "m.duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://some-other.sub.domain.duck.eu/with/path")!), "some-other.sub.domain.duck.eu") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://duckduckgo.com:1234")!), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://192.168.0.1:1234")!), "192.168.0.1") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.duckduckgo.com")), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.duckduckgo.com/some/path")), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.subdomain.duckduckgo.com/some/path")), "subdomain.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://m.duckduckgo.com/some/path")), "m.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("http://some-other.sub.domain.duck.eu/with/path")), "some-other.sub.domain.duck.eu") + XCTAssertEqual(AddressHelper.shortURLString(#URL("http://duckduckgo.com:1234")), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://192.168.0.1:1234")), "192.168.0.1") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.com")!), "com") // This is an exception we are ok with) + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.com")), "com") // This is an exception we are ok with) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "file:///some/path")!)) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "somescheme:///some/path")!)) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "blob:https://www.my.com/111-222-333-444")!)) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "data:text/plain;charset=UTF-8;page=21,the%20data:12345")!)) + XCTAssertNil(AddressHelper.shortURLString(#URL("file:///some/path"))) + XCTAssertNil(AddressHelper.shortURLString(#URL("somescheme:///some/path"))) + XCTAssertNil(AddressHelper.shortURLString(#URL("blob:https://www.my.com/111-222-333-444"))) + XCTAssertNil(AddressHelper.shortURLString(#URL("data:text/plain;charset=UTF-8;page=21,the%20data:12345"))) } func testShortensURLWhenShortVersionExpected() { - let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: false) + let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("http://some.domain.eu/with/path"), showsFullURL: false) XCTAssertEqual(addressForDisplay, "some.domain.eu") } func testDoesNotShortenURLWhenFullVersionExpected() { - let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: true) + let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("http://some.domain.eu/with/path"), showsFullURL: true) XCTAssertEqual(addressForDisplay, "http://some.domain.eu/with/path") } func testFallsBackToLongURLWhenCannotProduceShortURL() { - let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "file:///some/path")!, showsFullURL: false) + let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("file:///some/path"), showsFullURL: false) XCTAssertEqual(addressForDisplay, "file:///some/path") } diff --git a/DuckDuckGoTests/AppURLsTests.swift b/DuckDuckGoTests/AppURLsTests.swift index 280e855dcc..830fa92b30 100644 --- a/DuckDuckGoTests/AppURLsTests.swift +++ b/DuckDuckGoTests/AppURLsTests.swift @@ -17,7 +17,9 @@ // limitations under the License. // +import Macros import XCTest + @testable import BrowserServicesKit @testable import Core @@ -60,7 +62,7 @@ final class AppURLsTests: XCTestCase { } func testWhenRemoveInternalSearchParametersFromNonSearchUrlThenUrlIsUnchanged() { - let example = URL(string: "https://duckduckgo.com?atb=x&t=y&ko=z")! + let example = #URL("https://duckduckgo.com?atb=x&t=y&ko=z") let result = example.removingInternalSearchParameters() XCTAssertEqual(example.absoluteString, result.absoluteString) } @@ -76,13 +78,13 @@ final class AppURLsTests: XCTestCase { } func testBaseUrlDoesNotHaveSubDomain() { - XCTAssertEqual(URL.ddg, URL(string: "https://duckduckgo.com")) + XCTAssertEqual(URL.ddg, #URL("https://duckduckgo.com")) } func testWhenMobileStatsParamsAreAppliedThenTheyReturnAnUpdatedUrl() throws { mockStatisticsStore.atb = "x" let actual = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .applyingStatsParams(to: URL(string: "http://duckduckgo.com?atb=wrong&t=wrong")!) + .applyingStatsParams(to: #URL("http://duckduckgo.com?atb=wrong&t=wrong")) XCTAssertEqual(actual.getParameter(named: "atb"), "x") XCTAssertEqual(actual.getParameter(named: "t"), "ddg_ios") } @@ -90,70 +92,70 @@ final class AppURLsTests: XCTestCase { func testWhenAtbMatchesThenHasMobileStatsParamsIsTrue() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_ios")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_ios")) XCTAssertTrue(result) } func testWhenAtbIsMismatchedThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "y" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_ios")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_ios")) XCTAssertFalse(result) } func testWhenAtbIsMissingThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?t=ddg_ios")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?t=ddg_ios")) XCTAssertFalse(result) } func testWhenSourceIsMismatchedThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_desktop")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_desktop")) XCTAssertFalse(result) } func testWhenSourceIsMissingThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=y")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=y")) XCTAssertFalse(result) } func testWhenUrlIsDdgWithASearchParamThenIsSearchIsTrue() { - let result = URL(string: "http://duckduckgo.com?q=hello")!.isDuckDuckGoSearch + let result = #URL("http://duckduckgo.com?q=hello").isDuckDuckGoSearch XCTAssertTrue(result) } func testWhenUrlHasNoSearchParamsThenIsSearchIsFalse() { - let result = URL(string: "http://duckduckgo.com?test=hello")!.isDuckDuckGoSearch + let result = #URL("http://duckduckgo.com?test=hello").isDuckDuckGoSearch XCTAssertFalse(result) } func testWhenUrlIsNonDdgThenIsSearchIsFalse() { - let result = URL(string: "http://www.example.com?q=hello")!.isDuckDuckGoSearch + let result = #URL("http://www.example.com?q=hello").isDuckDuckGoSearch XCTAssertFalse(result) } func testWhenNonDdgUrlHasDdgParamThenIsDdgIsFalse() { - let result = URL(string: "http://www.example.com?x=duckduckgo.com")!.isDuckDuckGo + let result = #URL("http://www.example.com?x=duckduckgo.com").isDuckDuckGo XCTAssertFalse(result) } func testWhenDdgUrlIsHttpThenIsDddgIsTrue() { - let result = URL(string: "http://duckduckgo.com")!.isDuckDuckGo + let result = #URL("http://duckduckgo.com").isDuckDuckGo XCTAssertTrue(result) } func testWhenDdgUrlIsHttpsThenIsDddgIsTrue() { - let result = URL(string: "https://duckduckgo.com")!.isDuckDuckGo + let result = #URL("https://duckduckgo.com").isDuckDuckGo XCTAssertTrue(result) } func testWhenDdgUrlHasSubdomainThenIsDddgIsTrue() { - let result = URL(string: "http://www.duckduckgo.com")!.isDuckDuckGo + let result = #URL("http://www.duckduckgo.com").isDuckDuckGo XCTAssertTrue(result) } @@ -233,7 +235,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryUsesVerticalThenItIsAppliedToNewOne() throws { - let contextURL = URL(string: "https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images")! + let contextURL = #URL("https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images") let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -242,7 +244,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryUsesVerticalWithMapsThenTheseAreIgnored() throws { - let contextURL = URL(string: "https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images&iaxm=maps")! + let contextURL = #URL("https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images&iaxm=maps") let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -253,7 +255,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryHasNoVerticalThenItIsAbsentInNewOne() throws { - let contextURL = URL(string: "https://example.com")! + let contextURL = #URL("https://example.com") let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -273,20 +275,20 @@ final class AppURLsTests: XCTestCase { } func testWhenDdgUrlWithSearchParamThenSearchQueryReturned() { - let url = URL(string: "https://www.duckduckgo.com/?ko=-1&kl=wt-wt&q=some%20search")! + let url = #URL("https://www.duckduckgo.com/?ko=-1&kl=wt-wt&q=some%20search") let expected = "some search" let actual = url.searchQuery XCTAssertEqual(actual, expected) } func testWhenNoSearchParamInDdgUrlThenSearchQueryReturnsNil() { - let url = URL(string: "https://www.duckduckgo.com/?ko=-1&kl=wt-wt")! + let url = #URL("https://www.duckduckgo.com/?ko=-1&kl=wt-wt") let result = url.searchQuery XCTAssertNil(result) } func testWhenNotDdgUrlThenSearchQueryReturnsNil() { - let url = URL(string: "https://www.test.com/?ko=-1&kl=wt-wt&q=some%20search")! + let url = #URL("https://www.test.com/?ko=-1&kl=wt-wt&q=some%20search") let result = url.searchQuery XCTAssertNil(result) } diff --git a/DuckDuckGoTests/BookmarksCachingSearchTests.swift b/DuckDuckGoTests/BookmarksCachingSearchTests.swift index 7885ce8e8e..0efb242a11 100644 --- a/DuckDuckGoTests/BookmarksCachingSearchTests.swift +++ b/DuckDuckGoTests/BookmarksCachingSearchTests.swift @@ -17,10 +17,11 @@ // limitations under the License. // -import XCTest -import CoreData -import Combine import Bookmarks +import Combine +import CoreData +import Macros +import XCTest @testable import Core @@ -42,7 +43,7 @@ public class MockBookmarksSearchStore: BookmarksSearchStore { class BookmarksCachingSearchTests: XCTestCase { - let url = URL(string: "http://duckduckgo.com")! + let url = #URL("http://duckduckgo.com") let simpleStore = MockBookmarksSearchStore() let urlStore = MockBookmarksSearchStore() @@ -85,9 +86,9 @@ class BookmarksCachingSearchTests: XCTestCase { BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.f12a.rawValue, url: url, isFavorite: true)] urlStore.dataSet = [ - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample1.rawValue, url: URL(string: "https://example.com")!, isFavorite: true), - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample2.rawValue, url: URL(string: "https://example.com")!, isFavorite: true), - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlNasa.rawValue, url: URL(string: "https://www.nasa.gov")!, isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample1.rawValue, url: #URL("https://example.com"), isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample2.rawValue, url: #URL("https://example.com"), isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlNasa.rawValue, url: #URL("https://www.nasa.gov"), isFavorite: true), BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlDDG.rawValue, url: url, isFavorite: true)] } @@ -238,6 +239,6 @@ class BookmarksCachingSearchTests: XCTestCase { private extension BookmarksCachingSearchTests { enum Constants { static let bookmarkTitle = "my bookmark" - static let bookmarkURL = URL(string: "https://www.apple.com")! + static let bookmarkURL = #URL("https://www.apple.com") } } diff --git a/DuckDuckGoTests/BookmarksImporterTests.swift b/DuckDuckGoTests/BookmarksImporterTests.swift index e8a4dc44ee..94d2375de5 100644 --- a/DuckDuckGoTests/BookmarksImporterTests.swift +++ b/DuckDuckGoTests/BookmarksImporterTests.swift @@ -17,10 +17,12 @@ // limitations under the License. // -import XCTest +import Bookmarks +import Macros import SwiftSoup +import XCTest + @testable import Core -import Bookmarks @MainActor class BookmarksImporterTests: XCTestCase { @@ -195,6 +197,6 @@ private extension BookmarksImporterTests { enum Constants { static let bookmarkTitle = "my bookmark" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = URL(string: "https://duckduckgo.com")! + static let bookmarkURL = #URL("https://duckduckgo.com") } } diff --git a/DuckDuckGoTests/DaxDialogTests.swift b/DuckDuckGoTests/DaxDialogTests.swift index 727f21a99d..1d0072f0b5 100644 --- a/DuckDuckGoTests/DaxDialogTests.swift +++ b/DuckDuckGoTests/DaxDialogTests.swift @@ -17,13 +17,15 @@ // limitations under the License. // -import XCTest -@testable import DuckDuckGo -@testable import Core import BrowserServicesKit -import TrackerRadarKit import ContentBlocking +import Macros import PrivacyDashboard +import TrackerRadarKit +import XCTest + +@testable import Core +@testable import DuckDuckGo private struct MockEntityProvider: EntityProviding { @@ -46,13 +48,13 @@ final class DaxDialog: XCTestCase { struct URLs { - static let example = URL(string: "https://www.example.com")! - static let ddg = URL(string: "https://duckduckgo.com?q=test")! - static let facebook = URL(string: "https://www.facebook.com")! - static let google = URL(string: "https://www.google.com")! - static let ownedByFacebook = URL(string: "https://www.instagram.com")! - static let amazon = URL(string: "https://www.amazon.com")! - static let tracker = URL(string: "https://www.1dmp.io")! + static let example = #URL("https://www.example.com") + static let ddg = #URL("https://duckduckgo.com?q=test") + static let facebook = #URL("https://www.facebook.com") + static let google = #URL("https://www.google.com") + static let ownedByFacebook = #URL("https://www.instagram.com") + static let amazon = #URL("https://www.amazon.com") + static let tracker = #URL("https://www.1dmp.io") } diff --git a/DuckDuckGoTests/DownloadMocks.swift b/DuckDuckGoTests/DownloadMocks.swift index 1601392430..4ff1c9bd4a 100644 --- a/DuckDuckGoTests/DownloadMocks.swift +++ b/DuckDuckGoTests/DownloadMocks.swift @@ -18,6 +18,7 @@ // import Foundation +import Macros import WebKit @testable import DuckDuckGo @@ -55,7 +56,7 @@ class MockNavigationResponse: WKNavigationResponse { var mimeType: String? override var response: URLResponse { - let response = MockURLResponse(url: URL(string: "https://www.duck.com")!, + let response = MockURLResponse(url: #URL("https://www.duck.com"), mimeType: mimeType!, expectedContentLength: 1234, textEncodingName: "") diff --git a/DuckDuckGoTests/DownloadTestsHelper.swift b/DuckDuckGoTests/DownloadTestsHelper.swift index e9810b6c21..073e463402 100644 --- a/DuckDuckGoTests/DownloadTestsHelper.swift +++ b/DuckDuckGoTests/DownloadTestsHelper.swift @@ -18,10 +18,12 @@ // import Foundation +import Macros + @testable import DuckDuckGo struct DownloadTestsHelper { - let mockURL = URL(string: "https://duck.com")! + let mockURL = #URL("https://duck.com") let tmpDirectory = FileManager.default.temporaryDirectory let downloadsDirectory: URL diff --git a/DuckDuckGoTests/FaviconRequestModifierTests.swift b/DuckDuckGoTests/FaviconRequestModifierTests.swift index 0058e7a5c9..b3431cd777 100644 --- a/DuckDuckGoTests/FaviconRequestModifierTests.swift +++ b/DuckDuckGoTests/FaviconRequestModifierTests.swift @@ -17,8 +17,10 @@ // limitations under the License. // -import XCTest import BrowserServicesKit +import Macros +import XCTest + @testable import Core class MockEmbeddedDataProvider: EmbeddedDataProvider { @@ -74,7 +76,7 @@ class FaviconRequestModifierTests: XCTestCase { } func test() { - let request = URLRequest(url: URL(string: "https://www.example.com")!) + let request = URLRequest(url: #URL("https://www.example.com")) let result = FaviconRequestModifier(userAgentManager: userAgentManager).modified(for: request) XCTAssertTrue(result?.allHTTPHeaderFields?["User-Agent"]?.contains("DuckDuckGo") ?? false) } diff --git a/DuckDuckGoTests/FaviconsTests.swift b/DuckDuckGoTests/FaviconsTests.swift index efe00fe4d4..12d4bb494a 100644 --- a/DuckDuckGoTests/FaviconsTests.swift +++ b/DuckDuckGoTests/FaviconsTests.swift @@ -17,11 +17,13 @@ // limitations under the License. // +import Bookmarks +import CoreData +import Kingfisher +import Macros import XCTest + @testable import Core -import Kingfisher -import CoreData -import Bookmarks class FaviconsTests: XCTestCase { @@ -97,8 +99,8 @@ class FaviconsTests: XCTestCase { switch options?[4] { case .alternativeSources(let sources): XCTAssertEqual(2, sources.count) - XCTAssertEqual(sources[0].url, URL(string: "https://example.com/favicon.ico")) - XCTAssertEqual(sources[1].url, URL(string: "http://example.com/favicon.ico")) + XCTAssertEqual(sources[0].url, #URL("https://example.com/favicon.ico")) + XCTAssertEqual(sources[1].url, #URL("http://example.com/favicon.ico")) default: XCTFail("Unexpected option") diff --git a/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift b/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift index f8713a7307..2dfacdc021 100644 --- a/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift +++ b/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift @@ -17,12 +17,14 @@ // limitations under the License. // +import Bookmarks +import Core import Foundation +import Macros +import Persistence import XCTest + @testable import DuckDuckGo -import Persistence -import Core -import Bookmarks class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { @@ -85,7 +87,7 @@ class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { try createBookmark() image = UIImage() - let url = URL(string: "https://example.com/favicon.ico")! + let url = #URL("https://example.com/favicon.ico") let updater = FireproofFaviconUpdater(bookmarksDatabase: db, tab: self, favicons: self) updater.faviconUserScript(FaviconUserScript(), didRequestUpdateFaviconForHost: "example.com", withUrl: url) @@ -102,7 +104,7 @@ class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { try createBookmark() image = UIImage() - let url = URL(string: "https://example.com/favicon.ico")! + let url = #URL("https://example.com/favicon.ico") let updater = FireproofFaviconUpdater(bookmarksDatabase: db, tab: self, favicons: self) updater.faviconUserScript(FaviconUserScript(), didRequestUpdateFaviconForHost: "www.example.com", withUrl: url) diff --git a/DuckDuckGoTests/HTTPSUpgradeTests.swift b/DuckDuckGoTests/HTTPSUpgradeTests.swift index cc649cb09b..c32252598a 100644 --- a/DuckDuckGoTests/HTTPSUpgradeTests.swift +++ b/DuckDuckGoTests/HTTPSUpgradeTests.swift @@ -17,10 +17,11 @@ // limitations under the License. // -import XCTest +import BrowserServicesKit +import Macros import OHHTTPStubs import OHHTTPStubsSwift -import BrowserServicesKit +import XCTest @testable import Core @@ -34,7 +35,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpsThenShouldUpgradeResultIsFalse() { let expect = expectation(description: "Https url should not be upgraded") - let url = URL(string: "https://upgradable.url")! + let url = #URL("https://upgradable.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() @@ -49,7 +50,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsExcludedThenShouldUpgradeResultIsFalse() { let expect = expectation(description: "Excluded http:// urls should not be upgraded") - let url = URL(string: "http://excluded.url")! + let url = #URL("http://excluded.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() @@ -63,7 +64,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndCanBeUpgradedThenShouldUpgradeIsTrue() { let expect = expectation(description: "Http url in list and should be upgraded") - let url = URL(string: "http://upgradable.url")! + let url = #URL("http://upgradable.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter()), privacyConfig: WebKitTestHelper.preparePrivacyConfig( @@ -84,7 +85,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndHttpsUpgradesDisabledThenShouldUpgradeIsFalse() { let expect = expectation(description: "Http url in list and should not be upgraded") - let url = URL(string: "http://upgradable.url")! + let url = #URL("http://upgradable.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter()), privacyConfig: WebKitTestHelper.preparePrivacyConfig( @@ -105,7 +106,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndCannotBeUpgradedThenShouldUpgradeIsFalse() { let expect = expectation(description: "Http url not in list should not be upgraded") - let url = URL(string: "http://unknown.url")! + let url = #URL("http://unknown.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() diff --git a/DuckDuckGoTests/MenuBookmarksViewModelTests.swift b/DuckDuckGoTests/MenuBookmarksViewModelTests.swift index d2bce8cb21..e4f310fbb4 100644 --- a/DuckDuckGoTests/MenuBookmarksViewModelTests.swift +++ b/DuckDuckGoTests/MenuBookmarksViewModelTests.swift @@ -17,11 +17,12 @@ // limitations under the License. // -import Foundation -import XCTest import Bookmarks -import Persistence import DuckDuckGo +import Foundation +import Macros +import Persistence +import XCTest private extension MenuBookmarksViewModel { @@ -35,7 +36,7 @@ private extension MenuBookmarksViewModel { class MenuBookmarksViewModelTests: XCTestCase { - let url = URL(string: "https://test.com")! + let url = #URL("https://test.com") var db: CoreDataDatabase! override func setUpWithError() throws { diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index 8545c641fa..ca0aea4c51 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -17,10 +17,11 @@ // limitations under the License. // -import Foundation import BrowserServicesKit -import SecureStorage +import Foundation import GRDB +import Macros +import SecureStorage // swiftlint:disable file_length typealias MockVaultFactory = SecureVaultFactory> @@ -222,6 +223,10 @@ final class MockSecureVault: AutofillSecureVault { // MARK: - Mock Providers +private extension URL { + static let duckduckgo = #URL("https://duckduckgo.com/") +} + class MockDatabaseProvider: AutofillDatabaseProvider { // swiftlint:disable identifier_name @@ -237,13 +242,13 @@ class MockDatabaseProvider: AutofillDatabaseProvider { var db: GRDB.DatabaseWriter // swiftlint:enable identifier_name - required init(file: URL = URL(string: "https://duckduckgo.com/")!, key: Data = Data()) throws { + required init(file: URL = .duckduckgo, key: Data = Data()) throws { db = (try? DatabaseQueue(named: "Test"))! } static func recreateDatabase(withKey key: Data) throws -> Self { // swiftlint:disable:next force_cast - return try MockDatabaseProvider(file: URL(string: "https://duck.com")!, key: Data()) as! Self + return try MockDatabaseProvider(file: #URL("https://duck.com"), key: Data()) as! Self } func storeWebsiteCredentials(_ credentials: SecureVaultModels.WebsiteCredentials) throws -> Int64 { diff --git a/DuckDuckGoTests/MockUserAgent.swift b/DuckDuckGoTests/MockUserAgent.swift index d9bb55ff33..49c9897345 100644 --- a/DuckDuckGoTests/MockUserAgent.swift +++ b/DuckDuckGoTests/MockUserAgent.swift @@ -17,9 +17,10 @@ // limitations under the License. // +import BrowserServicesKit import Foundation +import Macros import WebKit -import BrowserServicesKit class MockUserAgentManager: UserAgentManager { @@ -33,7 +34,7 @@ class MockUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) + webview.load(URLRequest.developerInitiated(#URL("about:blank"))) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned diff --git a/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift b/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift index 3bb8ed65c4..bb8f666020 100644 --- a/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift +++ b/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift @@ -17,7 +17,9 @@ // limitations under the License. // +import Macros import XCTest + @testable import Core class NotFoundCachingDownloaderTests: XCTestCase { @@ -57,7 +59,7 @@ class NotFoundCachingDownloaderTests: XCTestCase { downloader.noFaviconsFound(forDomain: "example.com") let moreThanAWeekFromNow = Date().addingTimeInterval(60 * 60 * 24 * 8) - XCTAssertTrue(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!, referenceDate: moreThanAWeekFromNow)) + XCTAssertTrue(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"), referenceDate: moreThanAWeekFromNow)) guard let domains: [String: TimeInterval] = UserDefaults.app.object(forKey: UserDefaultsWrapper.Key.notFoundCache.rawValue) as? [String: TimeInterval] else { @@ -70,11 +72,11 @@ class NotFoundCachingDownloaderTests: XCTestCase { func testWhenMarkingDomainAsNotFoundThenShouldNotDownload() { downloader.noFaviconsFound(forDomain: "example.com") - XCTAssertFalse(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!)) + XCTAssertFalse(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"))) } func testWhenDomainNotMarkedAsNotFoundThenShouldNotDownload() { - XCTAssertTrue(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!)) + XCTAssertTrue(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"))) } } diff --git a/DuckDuckGoTests/PrivacyIconLogicTests.swift b/DuckDuckGoTests/PrivacyIconLogicTests.swift index a14220bdc8..7ff6ad326f 100644 --- a/DuckDuckGoTests/PrivacyIconLogicTests.swift +++ b/DuckDuckGoTests/PrivacyIconLogicTests.swift @@ -17,21 +17,23 @@ // limitations under the License. // -import Foundation -import XCTest -import TrackerRadarKit import BrowserServicesKit +import Foundation +import Macros import PrivacyDashboard +import TrackerRadarKit +import XCTest + @testable import Core @testable import DuckDuckGo class PrivacyIconLogicTests: XCTestCase { - static let pageURL = URL(string: "https://example.com")! - static let insecurePageURL = URL(string: "http://example.com")! - static let ddgSearchURL = URL(string: "https://duckduckgo.com/?q=catfood&t=h_&ia=web")! - static let ddgMainURL = URL(string: "https://duckduckgo.com")! - static let ddgSupportURL = URL(string: "https://duckduckgo.com/email/settings/support")! + static let pageURL = #URL("https://example.com") + static let insecurePageURL = #URL("http://example.com") + static let ddgSearchURL = #URL("https://duckduckgo.com/?q=catfood&t=h_&ia=web") + static let ddgMainURL = #URL("https://duckduckgo.com") + static let ddgSupportURL = #URL("https://duckduckgo.com/email/settings/support") func testPrivacyIconIsShieldForPageURL() { let url = PrivacyIconLogicTests.insecurePageURL diff --git a/DuckDuckGoTests/TabTests.swift b/DuckDuckGoTests/TabTests.swift index c5092a92d8..e8eaa73ba4 100644 --- a/DuckDuckGoTests/TabTests.swift +++ b/DuckDuckGoTests/TabTests.swift @@ -17,16 +17,18 @@ // limitations under the License. // +import Macros import XCTest -@testable import DuckDuckGo + @testable import Core +@testable import DuckDuckGo class TabTests: XCTestCase { struct Constants { static let title = "A title" - static let url = URL(string: "https://example.com")! - static let differentUrl = URL(string: "https://aDifferentUrl.com")! + static let url = #URL("https://example.com") + static let differentUrl = #URL("https://aDifferentUrl.com") } func testWhenDesktopPropertyChangesThenObserversNotified() { @@ -145,7 +147,7 @@ class TabTests: XCTestCase { } private func link() -> Link { - return Link(title: "title", url: URL(string: "http://example.com")!) + return Link(title: "title", url: #URL("http://example.com")) } } diff --git a/DuckDuckGoTests/TabsModelTests.swift b/DuckDuckGoTests/TabsModelTests.swift index f81545afa2..405e4922ea 100644 --- a/DuckDuckGoTests/TabsModelTests.swift +++ b/DuckDuckGoTests/TabsModelTests.swift @@ -17,13 +17,15 @@ // limitations under the License. // +import Macros import XCTest + @testable import DuckDuckGo @testable import Core class TabsModelTests: XCTestCase { - private let exampleLink = Link(title: nil, url: URL(string: "https://example.com")!) + private let exampleLink = Link(title: nil, url: #URL("https://example.com")) private var emptyModel: TabsModel { return TabsModel(desktop: false) @@ -38,9 +40,9 @@ class TabsModelTests: XCTestCase { private var filledModel: TabsModel { let model = TabsModel(tabs: [ - Tab(link: Link(title: "url1", url: URL(string: "https://ur1l.com")!)), - Tab(link: Link(title: "url2", url: URL(string: "https://ur12.com")!)), - Tab(link: Link(title: "url3", url: URL(string: "https://ur13.com")!)) + Tab(link: Link(title: "url1", url: #URL("https://ur1l.com"))), + Tab(link: Link(title: "url2", url: #URL("https://ur12.com"))), + Tab(link: Link(title: "url3", url: #URL("https://ur13.com"))) ], desktop: false) return model } @@ -102,7 +104,7 @@ class TabsModelTests: XCTestCase { } func testWhenTabExistsThenIndexReturned() { - let tab = Tab(link: Link(title: nil, url: URL(string: "https://www.example.com")!)) + let tab = Tab(link: Link(title: nil, url: #URL("https://www.example.com"))) let testee = filledModel testee.add(tab: tab) XCTAssertEqual(testee.indexOf(tab: tab), 3) diff --git a/DuckDuckGoTests/TrackerAnimationLogicTests.swift b/DuckDuckGoTests/TrackerAnimationLogicTests.swift index 07302fc11f..b3088d01d7 100644 --- a/DuckDuckGoTests/TrackerAnimationLogicTests.swift +++ b/DuckDuckGoTests/TrackerAnimationLogicTests.swift @@ -17,18 +17,20 @@ // limitations under the License. // -import Foundation -import XCTest -import TrackerRadarKit import BrowserServicesKit import ContentBlocking +import Foundation +import Macros import PrivacyDashboard +import TrackerRadarKit +import XCTest + @testable import Core @testable import DuckDuckGo class TrackerAnimationLogicTests: XCTestCase { - static let pageURL = URL(string: "https://example.com")! + static let pageURL = #URL("https://example.com") func testAnimationLogicToAnimateTrackersIfAnyBlocked() { let trackerInfo = makeBlockedTrackerInfo(pageURL: Self.pageURL) diff --git a/DuckDuckGoTests/UserAgentTests.swift b/DuckDuckGoTests/UserAgentTests.swift index 7055db968e..22b6e6e509 100644 --- a/DuckDuckGoTests/UserAgentTests.swift +++ b/DuckDuckGoTests/UserAgentTests.swift @@ -17,9 +17,11 @@ // limitations under the License. // +import BrowserServicesKit +import Macros import WebKit import XCTest -import BrowserServicesKit + @testable import Core // swiftlint:disable file_length type_body_length @@ -64,11 +66,11 @@ final class UserAgentTests: XCTestCase { } private struct Constants { - static let url = URL(string: "http://example.com/index.html") - static let noAppUrl = URL(string: "http://cvs.com/index.html") - static let noAppSubdomainUrl = URL(string: "http://subdomain.cvs.com/index.html") - static let ddgFixedUrl = URL(string: "http://test2.com/index.html") - static let ddgDefaultUrl = URL(string: "http://test3.com/index.html") + static let url = #URL("http://example.com/index.html") + static let noAppUrl = #URL("http://cvs.com/index.html") + static let noAppSubdomainUrl = #URL("http://subdomain.cvs.com/index.html") + static let ddgFixedUrl = #URL("http://test2.com/index.html") + static let ddgDefaultUrl = #URL("http://test3.com/index.html") } let testConfig = """ diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 79df959eeb..693478114c 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "2.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 51c9b26b91..ebbe099b43 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -34,7 +34,7 @@ let package = Package( dependencies: [ .package(path: "../DuckUI"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0"), - .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "2.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index f3a3951ba5..b89a136609 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -16,12 +16,15 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0"), - .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "2.0.0"), ], targets: [ .target( name: "Waitlist", - dependencies: ["DesignResourcesKit"], + dependencies: [ + "DesignResourcesKit", + .product(name: "Macros", package: "apple-toolbox"), + ], swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) ], diff --git a/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift b/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift index 63dab2041f..1058a46a3f 100644 --- a/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift +++ b/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift @@ -18,6 +18,7 @@ // import Foundation +import Macros public typealias ProductWaitlistMakeHTTPRequest = (URL, _ method: String, _ body: Data?, @escaping ProductWaitlistHTTPRequestCompletion) -> Void public typealias ProductWaitlistHTTPRequestCompletion = (Data?, Error?) -> Void @@ -133,9 +134,9 @@ public class ProductWaitlistRequest: WaitlistRequest { private var endpoint: URL { #if DEBUG - return URL(string: "https://quack.duckduckgo.com/api/auth/waitlist/")! + return #URL("https://quack.duckduckgo.com/api/auth/waitlist/") #else - return URL(string: "https://quack.duckduckgo.com/api/auth/waitlist/")! + return #URL("https://quack.duckduckgo.com/api/auth/waitlist/") #endif } diff --git a/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift b/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift index 51236cdf43..0dd29425fd 100644 --- a/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift +++ b/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift @@ -18,6 +18,7 @@ // import Foundation +import Macros import Waitlist public struct TestWaitlist: Waitlist { @@ -38,7 +39,7 @@ public struct TestWaitlist: Waitlist { public static var identifier: String = "mockIdentifier" public static var apiProductName: String = "mockApiProductName" - public static var downloadURL: URL = URL(string: "https://duckduckgo.com")! + public static var downloadURL: URL = #URL("https://duckduckgo.com") public static var backgroundTaskName: String = "BG Task" public static var backgroundRefreshTaskIdentifier: String = "bgtask" diff --git a/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift b/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift index fffb48ee09..3fc237560b 100644 --- a/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift +++ b/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift @@ -17,10 +17,12 @@ // limitations under the License. // -import XCTest import Combine +import Macros import UserNotifications import WaitlistMocks +import XCTest + @testable import Waitlist class WaitlistViewModelTests: XCTestCase { @@ -261,7 +263,7 @@ extension WaitlistViewModel { waitlistRequest: waitlistRequest, waitlistStorage: waitlistStorage, notificationService: notificationService, - downloadURL: URL(string: "https://duckduckgo.com")! + downloadURL: #URL("https://duckduckgo.com") ) } } diff --git a/Widgets/WidgetViews.swift b/Widgets/WidgetViews.swift index 60e6902090..81f71f056e 100644 --- a/Widgets/WidgetViews.swift +++ b/Widgets/WidgetViews.swift @@ -17,8 +17,9 @@ // limitations under the License. // -import WidgetKit +import Macros import SwiftUI +import WidgetKit struct FavoriteView: View { @@ -306,7 +307,7 @@ extension Image { struct WidgetViews_Previews: PreviewProvider { static let mockFavorites: [Favorite] = { - let duckDuckGoFavorite = Favorite(url: URL(string: "https://duckduckgo.com/")!, + let duckDuckGoFavorite = Favorite(url: #URL("https://duckduckgo.com/"), domain: "duckduckgo.com", title: "title", favicon: nil) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 751b5b0df9..0b02e8896e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -78,7 +78,7 @@ lane :adhoc do |options| scheme: "DuckDuckGo", export_options: "adhocExportOptions.plist", derived_data_path: "DerivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) if is_ci @@ -184,7 +184,7 @@ private_lane :build_release do |options| scheme: "DuckDuckGo", export_options: "appStoreExportOptions.plist", derived_data_path: "DerivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) end @@ -197,7 +197,7 @@ private_lane :build_alpha do |options| scheme: "DuckDuckGo-Alpha", export_options: "alphaExportOptions.plist", derived_data_path: "DerivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) end From 5dcdabd90a759eeb5a6567a0bdc2cc917e149d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Thu, 7 Mar 2024 10:12:50 +0100 Subject: [PATCH 15/20] Cleanup after rolling out autoconsent enabled by default (#2537) --- Core/FeatureFlag.swift | 3 --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/AppUserDefaults.swift | 26 ++----------------- DuckDuckGo/Debug.storyboard | 25 ++++++------------ DuckDuckGo/RootDebugViewController.swift | 7 ----- DuckDuckGoTests/AppUserDefaultsTests.swift | 24 +---------------- 7 files changed, 14 insertions(+), 77 deletions(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 1246732321..0715f027d1 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -36,7 +36,6 @@ public enum FeatureFlag: String { case networkProtectionWaitlistActive case subscription case swipeTabs - case autoconsentOnByDefault case history } @@ -67,8 +66,6 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault)) case .incontextSignup: return .remoteReleasable(.feature(.incontextSignup)) - case .autoconsentOnByDefault: - return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault)) case .history: return .remoteReleasable(.feature(.history)) } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2295ddf9f0..34030af0a5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10045,7 +10045,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 117.0.0; + version = 118.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a17cb5bc7a..e3e64dc11f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "dbe75fa0ee9e3b740d520d5be7967e2c5239dfb5", - "version" : "117.0.0" + "revision" : "2181cdcd27be961f10d988fbb202431a6ec6d56d", + "version" : "118.0.0" } }, { diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 446bd75fc3..7be4a03d4e 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -22,7 +22,6 @@ import Bookmarks import Core import WidgetKit -// swiftlint:disable file_length public class AppUserDefaults: AppSettings { public struct Notifications { @@ -291,29 +290,8 @@ public class AppUserDefaults: AppSettings { } } - var autoconsentEnabled: Bool { - get { - // Use settings value if present - if let isEnabled = autoconsentEnabledSetting { - return isEnabled - } - - // Use onByDefault rollout otherwise - return featureFlagger.isFeatureOn(.autoconsentOnByDefault) - } - - set { - autoconsentEnabledSetting = newValue - } - } - - // Only for testing and `DebugViewController` purposes - func clearAutoconsentUserSetting() { - autoconsentEnabledSetting = nil - } - - @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: false) - private var autoconsentEnabledSetting: Bool? + @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: true) + var autoconsentEnabled: Bool var inspectableWebViewEnabled: Bool { get { diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 7443344405..fdab02c1d8 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -233,17 +233,8 @@ - - - - - - - - - - + @@ -252,7 +243,7 @@ - + @@ -261,7 +252,7 @@ - + @@ -270,7 +261,7 @@ - + @@ -846,17 +837,17 @@ - + - + - + - +