From 1c1ab1879532a3b192ba64ae62c218b86d33cd7c Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 22 Aug 2023 06:27:47 -0700 Subject: [PATCH 01/17] Run checks on all PRs (#1929) Task/Issue URL: https://app.asana.com/0/0/1205320678425970/f Tech Design URL: CC: @ayoy Description: This PR updates the PR workflow to run checks on all PRs, not just those targeting specific branches. This is to allow us to more effectively leverage stacked PRs in our workflow, as they currently do not have any checks run against them. --- .github/workflows/pr.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index cd057e39fb..5d94f31c9f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -4,8 +4,6 @@ on: push: branches: [ develop, "release/**" ] pull_request: - branches: [ develop, "release/**" ] - jobs: swiftlint: From 4313e815fc11d91d434dc088da8d30ae7af6a959 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 23 Aug 2023 15:14:53 +0200 Subject: [PATCH 02/17] Add Commit hook install script (#1845) Task URL: https://app.asana.com/0/1200194497630846/1205110674679790/f Descrption: Adds a script that allows us to automatically install a pre-commit hook that runs swiftlint --fix The original installation script lives in BSK for easier maintenance. App Scripts download it and install it locally. (Remote URL will be updated once we merge BSK. This script can be extended as necesary to install/manage additional pre-commit hooks --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- lint.sh | 44 ++++++++++++++++++++++++++++ scripts/pre-commit.sh | 4 +++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100755 lint.sh create mode 100755 scripts/pre-commit.sh diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ffb9e436fe..91c53f453a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -5781,7 +5781,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [[ -n \"$CI\" ]] || [[ -n \"$BITRISE_IO\" ]]; then\n echo \"Skipping SwiftLint run in CI\"\n exit 0\nfi\n\nif test -d \"/opt/homebrew/bin/\"; then\n PATH=\"/opt/homebrew/bin/:${PATH}\"\nfi\n\nif test -d \"$HOME/.mint/bin/\"; then\n PATH=\"$HOME/.mint/bin/:${PATH}\"\nfi\n\nexport PATH\n\nif which swiftlint >/dev/null; then\n if [ \"$CONFIGURATION\" = \"Release\" ]; then\n swiftlint lint --strict\n if [ $? -ne 0 ]; then\n echo \"error: SwiftLint validation failed.\"\n exit 1\n fi\n else\n swiftlint lint\n fi\nelse\n echo \"error: SwiftLint not installed. Install using \\`brew install swiftlint\\`\"\n exit 1\nfi\n"; + shellScript = "./lint.sh\n"; }; 98B0CE69251C937D003FB601 /* Update Localizable.strings */ = { isa = PBXShellScriptBuildPhase; diff --git a/lint.sh b/lint.sh new file mode 100755 index 0000000000..92decc2803 --- /dev/null +++ b/lint.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +FIX=false + +if [[ "$1" == "--fix" ]]; then + FIX=true +fi + +if [[ -n "$CI" ]] || [[ -n "$BITRISE_IO" ]]; then + echo "Skipping SwiftLint run in CI" + exit 0 +fi + +# Add brew into PATH +if [[ -f /opt/homebrew/bin/brew ]]; then + eval $(/opt/homebrew/bin/brew shellenv) +fi + +if test -d "$HOME/.mint/bin/"; then + PATH="$HOME/.mint/bin/:${PATH}" +fi + +export PATH + + +SWIFTLINT_COMMAND="swiftlint lint" +if $FIX; then + SWIFTLINT_COMMAND="swiftlint lint --fix" +fi + +if which swiftlint >/dev/null; then + if [ "$CONFIGURATION" = "Release" ]; then + $SWIFTLINT_COMMAND --strict + if [ $? -ne 0 ]; then + echo "error: SwiftLint validation failed." + exit 1 + fi + else + $SWIFTLINT_COMMAND + fi +else + echo "error: SwiftLint not installed. Install using \`brew install swiftlint\`" + exit 1 +fi diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh new file mode 100755 index 0000000000..413f8ce0a5 --- /dev/null +++ b/scripts/pre-commit.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +SCRIPT_URL="https://raw.githubusercontent.com/duckduckgo/BrowserServicesKit/main/scripts/pre-commit.sh" +curl -s "${SCRIPT_URL}" | bash -s -- "$@" \ No newline at end of file From 25d7a99c4cd24c6ddb8ea26752dbc97d2aae6e30 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 23 Aug 2023 17:06:23 +0200 Subject: [PATCH 03/17] Replaces useSystemKeychain with keychain type (#1928) Task/Issue URL: https://app.asana.com/0/0/1205319558799772/f BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/469 macOS PR: https://github.com/duckduckgo/macos-browser/pull/1516 ## Description Changes the code so that we can specify which keychain to use. There should be no logical changes whatsoever. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../xcshareddata/xcschemes/DuckDuckGo.xcscheme | 5 +++++ DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift | 2 +- DuckDuckGo/NetworkProtectionTunnelController.swift | 2 +- .../NetworkProtectionPacketTunnelProvider.swift | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 91c53f453a..9cc5c1e161 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8810,7 +8810,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 75.0.2; + version = 75.0.3; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 42e1c53d24..db4c3b897d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "8d4c3d884762d954183a9496961b87d254b43539", - "version": "75.0.2" + "revision": "9bf7b16196ac4a78bf9189841d9823047b5c141b", + "version": "75.0.3" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme index 7cfeb57d56..5383c46f83 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme @@ -58,6 +58,11 @@ BlueprintName = "UnitTests" ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + + + diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index f2f386d2e1..af653f4f43 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -47,7 +47,7 @@ extension ConnectionServerInfoObserverThroughSession { extension NetworkProtectionKeychainTokenStore { convenience init() { // Error events to be added as part of https://app.asana.com/0/1203137811378537/1205112639044115/f - self.init(useSystemKeychain: false, errorEvents: nil) + self.init(keychainType: .dataProtection(.unspecified), errorEvents: nil) } } diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index b7dc7e9eaf..b4ec36c5cc 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -28,7 +28,7 @@ import NetworkProtection final class NetworkProtectionTunnelController: TunnelController { static var simulationOptions = NetworkProtectionSimulationOptions() - private let tokenStore = NetworkProtectionKeychainTokenStore(useSystemKeychain: false, errorEvents: nil) + private let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), errorEvents: nil) private let errorStore = NetworkProtectionTunnelErrorStore() // MARK: - Starting & Stopping the VPN diff --git a/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift index 58292201ee..6622785120 100644 --- a/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift @@ -31,7 +31,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { super.init(notificationsPresenter: DefaultNotificationPresenter(), tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: NetworkProtectionTunnelErrorStore(), - useSystemKeychain: false, + keychainType: .dataProtection(.unspecified), debugEvents: nil, providerEvents: Self.packetTunnelProviderEvents) } From 71763ffe4b3bc4c349b228411f8222bec2b199d6 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Wed, 23 Aug 2023 17:33:34 +0200 Subject: [PATCH 04/17] Add alpha specific app groups (#1934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1205332058642473/f Description: This is needed to fix a potential problem when running the prod + Alpha builds side by side. If we don’t separate the app groups, we would end up sharing state between the two which could introduce problems when we change schema between releases. --- DuckDuckGo.xcodeproj/project.pbxproj | 12 ++++++++--- DuckDuckGo/DuckDuckGoAlpha.entitlements | 21 +++++++++++++++++++ .../PacketTunnelProviderAlpha.entitlements | 15 +++++++++++++ WidgetsExtensionAlpha.entitlements | 11 ++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 DuckDuckGo/DuckDuckGoAlpha.entitlements create mode 100644 PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements create mode 100644 WidgetsExtensionAlpha.entitlements diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9cc5c1e161..e506715375 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2301,6 +2301,9 @@ EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteView.swift; sourceTree = ""; }; EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootViewController.swift; sourceTree = ""; }; EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockInternalUserStoring.swift; sourceTree = ""; }; + EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoAlpha.entitlements; sourceTree = ""; }; + EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtensionAlpha.entitlements; sourceTree = ""; }; + EE3B98EC2A963538002F63A0 /* PacketTunnelProviderAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PacketTunnelProviderAlpha.entitlements; sourceTree = ""; }; EE41BD182A729E9C00546C57 /* NetworkProtectionInviteViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteViewModelTests.swift; sourceTree = ""; }; EE4BE0082A740BED00CD6AA8 /* ClearTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearTextField.swift; sourceTree = ""; }; EE4FB1852A28CE7200E5CBA7 /* NetworkProtectionStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionStatusView.swift; sourceTree = ""; }; @@ -2565,6 +2568,7 @@ 02025665298818B200E694E7 /* PacketTunnelProvider */ = { isa = PBXGroup; children = ( + EE3B98EC2A963538002F63A0 /* PacketTunnelProviderAlpha.entitlements */, 02025670298818CB00E694E7 /* ProxyServer */, 02025666298818B200E694E7 /* AppTrackingProtectionPacketTunnelProvider.swift */, 02025B1429884EA500E694E7 /* DDGObserverFactory.swift */, @@ -3457,6 +3461,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, 84E341941E2F7EFB00BDBA6F /* DuckDuckGo */, F143C2E51E4A4CD400CFDE3A /* Core */, @@ -3501,6 +3506,7 @@ 84E341941E2F7EFB00BDBA6F /* DuckDuckGo */ = { isa = PBXGroup; children = ( + EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */, CB258D1129A4F1BB00DEBA24 /* Configuration */, 1E908BED29827C480008C8F3 /* Autoconsent */, 3157B43627F4C8380042D3D7 /* Favicons */, @@ -8241,7 +8247,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; - CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; + CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 0; @@ -8335,7 +8341,7 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; + CODE_SIGN_ENTITLEMENTS = WidgetsExtensionAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; @@ -8371,7 +8377,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; + CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; diff --git a/DuckDuckGo/DuckDuckGoAlpha.entitlements b/DuckDuckGo/DuckDuckGoAlpha.entitlements new file mode 100644 index 0000000000..0a73901384 --- /dev/null +++ b/DuckDuckGo/DuckDuckGoAlpha.entitlements @@ -0,0 +1,21 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.developer.web-browser + + com.apple.security.application-groups + + group.com.duckduckgo.alpha.apptp + group.com.duckduckgo.alpha.bookmarks + group.com.duckduckgo.alpha.contentblocker + group.com.duckduckgo.alpha.database + group.com.duckduckgo.alpha.netp + group.com.duckduckgo.alpha.statistics + + + diff --git a/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements b/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements new file mode 100644 index 0000000000..f814f005e7 --- /dev/null +++ b/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements @@ -0,0 +1,15 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.application-groups + + group.com.duckduckgo.alpha.apptp + group.com.duckduckgo.alpha.netp + + + diff --git a/WidgetsExtensionAlpha.entitlements b/WidgetsExtensionAlpha.entitlements new file mode 100644 index 0000000000..d5324a15bd --- /dev/null +++ b/WidgetsExtensionAlpha.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.security.application-groups + + group.com.duckduckgo.alpha.database + group.com.duckduckgo.alpha.bookmarks + + + From b97241f5280c803c258fc77793db95a7d01ffbae Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Wed, 23 Aug 2023 17:59:18 +0200 Subject: [PATCH 05/17] Fix PacketTunnelProvider embedding for Alpha builds (#1932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1205324333927521/f Description: On trying to distribute NetP through the Alpha channel, the build were being rejected by App Store Connect as their was an invalid file containing the word app. This turned out to be because the simple cp -r I was doing to move the extension was in fact not moving the extension for Archive builds, but an alias. I had another look at the build output of Xcode’s Embed App Extensions Build Phase which uses an Xcode-internal tool builtin-copy. rsync achieves the same result and has broadly the same options. The --copy-links option is needed to resolve symlinked .appex bundles. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e506715375..b2b7c4c8b7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -5844,7 +5844,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Conditionally embeds the PacketTunnelProvider extension for debug builds.\n# To be moved to the Embed App Extensions phase on release.\nif [ \"${CONFIGURATION}\" = \"Debug\" ] || [ \"${CONFIGURATION}\" = \"Alpha\" ]; then\n# Copy the extension\n cp -R \"${BUILT_PRODUCTS_DIR}/PacketTunnelProvider.appex\" \"${BUILT_PRODUCTS_DIR}/${PLUGINS_FOLDER_PATH}\"\nfi\n"; + shellScript = "# Conditionally embeds PacketTunnelProvider extension for Debug and Alpha builds.\n\n# Conditionally embeds the PacketTunnelProvider extension for debug builds.\\n# To be moved to the Embed App Extensions phase on release.\n\nif [ \"${CONFIGURATION}\" = \"Debug\" ] || [ \"${CONFIGURATION}\" = \"Alpha\" ]; then\n# Copy the extension \n rsync -r --copy-links \"${CONFIGURATION_BUILD_DIR}/PacketTunnelProvider.appex\" \"${CONFIGURATION_BUILD_DIR}/${PLUGINS_FOLDER_PATH}\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ From 4074976af1cff9764d0f109d6a81780105802e67 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Wed, 23 Aug 2023 18:12:20 +0200 Subject: [PATCH 06/17] Improve Sync-related database cleaning logic (#1933) Task/Issue URL: https://app.asana.com/0/414235014887631/1205315938732113/f Description: Only clean database when sync is inactive at app startup. Delay database cleaning until app becomes active in case it starts in background. Update credentialsDatabaseCleanupFailed pixel name to remove noise caused by users remaining on current version. --- Core/BookmarksCleanupErrorHandling.swift | 12 ++++--- Core/CredentialsCleanupErrorHandling.swift | 12 ++++--- Core/PixelEvent.swift | 2 +- Core/SyncBookmarksAdapter.swift | 2 +- Core/SyncCredentialsAdapter.swift | 4 +-- Core/SyncDataProviders.swift | 34 +++++++++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 ++-- DuckDuckGo/AppDelegate.swift | 31 +++++------------ 9 files changed, 67 insertions(+), 38 deletions(-) diff --git a/Core/BookmarksCleanupErrorHandling.swift b/Core/BookmarksCleanupErrorHandling.swift index 3ddafc6b20..aee1f065db 100644 --- a/Core/BookmarksCleanupErrorHandling.swift +++ b/Core/BookmarksCleanupErrorHandling.swift @@ -26,11 +26,15 @@ public class BookmarksCleanupErrorHandling: EventMapping public init() { super.init { event, _, _, _ in - let domainEvent = Pixel.Event.bookmarksCleanupFailed - let processedErrors = CoreDataErrorsParser.parse(error: event.cleanupError as NSError) - let params = processedErrors.errorPixelParameters + if event.cleanupError is BookmarksCleanupCancelledError { + Pixel.fire(pixel: .bookmarksCleanupAttemptedWhileSyncWasEnabled) + } else { + let domainEvent = Pixel.Event.bookmarksCleanupFailed + let processedErrors = CoreDataErrorsParser.parse(error: event.cleanupError as NSError) + let params = processedErrors.errorPixelParameters - Pixel.fire(pixel: domainEvent, error: event.cleanupError, withAdditionalParameters: params) + Pixel.fire(pixel: domainEvent, error: event.cleanupError, withAdditionalParameters: params) + } } } diff --git a/Core/CredentialsCleanupErrorHandling.swift b/Core/CredentialsCleanupErrorHandling.swift index 0443bd43a1..3ce5d639dd 100644 --- a/Core/CredentialsCleanupErrorHandling.swift +++ b/Core/CredentialsCleanupErrorHandling.swift @@ -26,11 +26,15 @@ public class CredentialsCleanupErrorHandling: EventMapping Bool { - #if targetEnvironment(simulator) +#if targetEnvironment(simulator) if ProcessInfo.processInfo.environment["UITESTING"] == "true" { // Disable hardware keyboards. let setHardwareLayout = NSSelectorFromString("setHardwareLayout:") UITextInputMode.activeInputModes - // Filter `UIKeyboardInputMode`s. + // Filter `UIKeyboardInputMode`s. .filter({ $0.responds(to: setHardwareLayout) }) .forEach { $0.perform(setHardwareLayout, with: nil) } } - #endif +#endif // Can be removed after a couple of versions cleanUpMacPromoExperiment2() @@ -113,13 +113,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } removeEmailWaitlistState() - + Database.shared.loadStore { context, error in guard let context = context else { let parameters = [PixelParameters.applicationState: "\(application.applicationState.rawValue)", PixelParameters.dataAvailability: "\(application.isProtectedDataAvailable)"] - + switch error { case .none: fatalError("Could not create database stack: Unknown Error") @@ -192,23 +192,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: Sync initialisation syncDataProviders = SyncDataProviders(bookmarksDatabase: bookmarksDatabase, secureVaultErrorReporter: SecureVaultErrorReporter.shared) - syncService = DDGSync(dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), log: .syncLog) + let syncService = DDGSync(dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), log: .syncLog) syncService.initializeIfNeeded(isInternalUser: InternalUserStore().isInternalUser) - syncStateCancellable = syncService.authStatePublisher - .prepend(syncService.authState) - .map { $0 == .inactive } - .removeDuplicates() - .sink { [weak self] isSyncDisabled in - self?.syncDataProviders.credentialsAdapter.updateDatabaseCleanupSchedule(shouldEnable: isSyncDisabled) - self?.syncDataProviders.bookmarksAdapter.updateDatabaseCleanupSchedule(shouldEnable: isSyncDisabled) - } - syncDataProviders.bookmarksAdapter.databaseCleaner.isSyncActive = { [weak self] in - self?.syncService.authState == .active - } - syncDataProviders.credentialsAdapter.databaseCleaner.isSyncActive = { [weak self] in - self?.syncService.authState == .active - } - + self.syncService = syncService let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main) @@ -275,6 +261,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard !testing else { return } syncService.initializeIfNeeded(isInternalUser: InternalUserStore().isInternalUser) + syncDataProviders.setUpDatabaseCleanersIfNeeded(syncService: syncService) if !(overlayWindow?.rootViewController is AuthenticationViewController) { removeOverlay() From bc095b9b3ea0be4e7b7dd5ad963742c0e2f87b38 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 24 Aug 2023 14:25:57 +0200 Subject: [PATCH 07/17] iOS InContext Email Protection Signup (#1839) Task/Issue URL: https://app.asana.com/0/72649045549333/1202940260106121/f Tech Design URL: CC: Description: Adds Email InContext Signup protection to iOS --- Core/ContentBlocking.swift | 4 +- Core/FeatureFlag.swift | 3 + Core/LocaleExtension.swift | 4 + Core/PixelEvent.swift | 19 + DuckDuckGo.xcodeproj/project.pbxproj | 54 +- .../xcshareddata/swiftpm/Package.resolved | 10 +- DuckDuckGo/AppDelegate.swift | 15 + .../AutofillContentScopeFeatureToggles.swift | 2 +- DuckDuckGo/AutofillLoginPromptView.swift | 19 +- DuckDuckGo/AutofillViews.swift | 11 +- DuckDuckGo/Debug.storyboard | 9 + DuckDuckGo/EmailAddressPromptView.swift | 169 +++++++ .../EmailAddressPromptViewController.swift | 122 +++++ DuckDuckGo/EmailAddressPromptViewModel.swift | 59 +++ DuckDuckGo/EmailSignupPromptView.swift | 157 ++++++ .../EmailSignupPromptViewController.swift | 119 +++++ DuckDuckGo/EmailSignupPromptViewModel.swift | 54 ++ DuckDuckGo/EmailSignupViewController.swift | 467 ++++++++++++++++++ DuckDuckGo/MainViewController+Email.swift | 76 ++- DuckDuckGo/PasswordGenerationPromptView.swift | 19 +- DuckDuckGo/RootDebugViewController.swift | 6 + DuckDuckGo/SaveLoginView.swift | 19 +- DuckDuckGo/TabViewController.swift | 74 +-- DuckDuckGo/UserText.swift | 25 +- DuckDuckGo/bg.lproj/Localizable.strings | 42 ++ DuckDuckGo/cs.lproj/Localizable.strings | 42 ++ DuckDuckGo/da.lproj/Localizable.strings | 42 ++ DuckDuckGo/de.lproj/Localizable.strings | 42 ++ DuckDuckGo/el.lproj/Localizable.strings | 42 ++ DuckDuckGo/en.lproj/Localizable.strings | 42 ++ DuckDuckGo/es.lproj/Localizable.strings | 42 ++ DuckDuckGo/et.lproj/Localizable.strings | 42 ++ DuckDuckGo/fi.lproj/Localizable.strings | 42 ++ DuckDuckGo/fr.lproj/Localizable.strings | 42 ++ DuckDuckGo/hr.lproj/Localizable.strings | 42 ++ DuckDuckGo/hu.lproj/Localizable.strings | 42 ++ DuckDuckGo/it.lproj/Localizable.strings | 42 ++ DuckDuckGo/lt.lproj/Localizable.strings | 42 ++ DuckDuckGo/lv.lproj/Localizable.strings | 42 ++ DuckDuckGo/nb.lproj/Localizable.strings | 42 ++ DuckDuckGo/nl.lproj/Localizable.strings | 42 ++ DuckDuckGo/pl.lproj/Localizable.strings | 42 ++ DuckDuckGo/pt.lproj/Localizable.strings | 42 ++ DuckDuckGo/ro.lproj/Localizable.strings | 42 ++ DuckDuckGo/ru.lproj/Localizable.strings | 42 ++ DuckDuckGo/sk.lproj/Localizable.strings | 42 ++ DuckDuckGo/sl.lproj/Localizable.strings | 42 ++ DuckDuckGo/sv.lproj/Localizable.strings | 42 ++ DuckDuckGo/tr.lproj/Localizable.strings | 42 ++ 49 files changed, 2450 insertions(+), 116 deletions(-) create mode 100644 DuckDuckGo/EmailAddressPromptView.swift create mode 100644 DuckDuckGo/EmailAddressPromptViewController.swift create mode 100644 DuckDuckGo/EmailAddressPromptViewModel.swift create mode 100644 DuckDuckGo/EmailSignupPromptView.swift create mode 100644 DuckDuckGo/EmailSignupPromptViewController.swift create mode 100644 DuckDuckGo/EmailSignupPromptViewModel.swift create mode 100644 DuckDuckGo/EmailSignupViewController.swift diff --git a/Core/ContentBlocking.swift b/Core/ContentBlocking.swift index 011a13af55..b38a36baf3 100644 --- a/Core/ContentBlocking.swift +++ b/Core/ContentBlocking.swift @@ -37,13 +37,15 @@ public final class ContentBlocking { private init(privacyConfigurationManager: PrivacyConfigurationManaging? = nil) { let internalUserDecider = DefaultInternalUserDecider(store: InternalUserStore()) + let statisticsStore = StatisticsUserDefaults() let privacyConfigurationManager = privacyConfigurationManager ?? PrivacyConfigurationManager(fetchedETag: UserDefaultsETagStorage().loadEtag(for: .privacyConfiguration), fetchedData: FileStore().loadAsData(for: .privacyConfiguration), embeddedDataProvider: AppPrivacyConfigurationDataProvider(), localProtection: DomainsProtectionUserDefaultsStore(), errorReporting: Self.debugEvents, - internalUserDecider: internalUserDecider) + internalUserDecider: internalUserDecider, + installDate: statisticsStore.installDate) self.privacyConfigurationManager = privacyConfigurationManager trackerDataManager = TrackerDataManager(etag: UserDefaultsETagStorage().loadEtag(for: .trackerDataSet), diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 2dce192565..69269aa99c 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -28,6 +28,7 @@ public enum FeatureFlag: String { case autofillInlineIconCredentials case autofillAccessCredentialManagement case autofillPasswordGeneration + case incontextSignup case appTrackingProtection case networkProtection } @@ -47,6 +48,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.accessCredentialManagement)) case .autofillPasswordGeneration: return .remoteReleasable(.subfeature(AutofillSubfeature.autofillPasswordGeneration)) + case .incontextSignup: + return .remoteReleasable(.feature(.incontextSignup)) } } } diff --git a/Core/LocaleExtension.swift b/Core/LocaleExtension.swift index b2e1a72e5a..cfbb0126bb 100644 --- a/Core/LocaleExtension.swift +++ b/Core/LocaleExtension.swift @@ -27,4 +27,8 @@ extension Locale { "MC", "MD", "ME", "MK", "MT", "NL", "NO", "PL", "PT", "RO", "RS", "RU", "SE", "SI", "SK", "SM", "TR", "UA", "GB", "VA"].contains(regionCode) } + + public var isEnglishLanguage: Bool { + return Locale.preferredLanguages.first?.lowercased().starts(with: "en") ?? false + } } diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 13806587be..87a490e685 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -435,6 +435,15 @@ extension Pixel { case credentialsCleanupAttemptedWhileSyncWasEnabled case invalidPayload(Configuration) + + case emailIncontextPromptDisplayed + case emailIncontextPromptConfirmed + case emailIncontextPromptDismissed + case emailIncontextPromptDismissedPersistent + case emailIncontextModalDisplayed + case emailIncontextModalDismissed + case emailIncontextModalExitEarly + case emailIncontextModalExitEarlyContinue } } @@ -859,6 +868,16 @@ extension Pixel.Event { case .credentialsCleanupAttemptedWhileSyncWasEnabled: return "m_d_credentials_cleanup_attempted_while_sync_was_enabled" case .invalidPayload(let configuration): return "m_d_\(configuration.rawValue)_invalid_payload".lowercased() + + // MARK: - InContext Email Protection + case .emailIncontextPromptDisplayed: return "m_email_incontext_prompt_displayed" + case .emailIncontextPromptConfirmed: return "m_email_incontext_prompt_confirmed" + case .emailIncontextPromptDismissed: return "m_email_incontext_prompt_dismissed" + case .emailIncontextPromptDismissedPersistent: return "m_email_incontext_prompt_dismissed_persisted" + case .emailIncontextModalDisplayed: return "m_email_incontext_modal_displayed" + case .emailIncontextModalDismissed: return "m_email_incontext_modal_dismissed" + case .emailIncontextModalExitEarly: return "m_email_incontext_modal_exit_early" + case .emailIncontextModalExitEarlyContinue: return "m_email_incontext_modal_exit_early_continue" } } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2b929fa3ea..3c38c91ed3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -669,6 +669,9 @@ B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.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 */; }; + 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 */; }; C14882DA27F2011C00D59F0C /* BookmarksExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D727F2011C00D59F0C /* BookmarksExporter.swift */; }; C14882DC27F2011C00D59F0C /* BookmarksImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D927F2011C00D59F0C /* BookmarksImporter.swift */; }; @@ -680,6 +683,7 @@ C14882ED27F211A000D59F0C /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = C14882EC27F211A000D59F0C /* SwiftSoup */; }; C14E2F7729DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14E2F7629DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift */; }; C158AC7B297AB5DC0008723A /* MockSecureVault.swift in Sources */ = {isa = PBXBuildFile; fileRef = C158AC7A297AB5DC0008723A /* MockSecureVault.swift */; }; + C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159DF062A430B60007834BB /* EmailSignupViewController.swift */; }; C160544129D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C160544029D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift */; }; C17B59592A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */; }; C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */; }; @@ -698,6 +702,9 @@ C1CCCBA7283E101500CF3791 /* FaviconsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CCCBA6283E101500CF3791 /* FaviconsHelper.swift */; }; C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */; }; C1D21E2F293A599C006E5A05 /* AutofillLoginSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */; }; + C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C42A6924000032057B /* EmailAddressPromptView.swift */; }; + C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */; }; + C1F341C92A6926920032057B /* EmailAddressPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */; }; CB258D1229A4F24900DEBA24 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB258D0F29A4D0FD00DEBA24 /* ConfigurationManager.swift */; }; CB258D1329A4F24E00DEBA24 /* ConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB84C7C029A3F0280088A5B8 /* ConfigurationStore.swift */; }; CB258D1D29A52AF900DEBA24 /* EtagStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9896632322C56716007BE4FE /* EtagStorage.swift */; }; @@ -2244,6 +2251,9 @@ B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64DownloadSession.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 = ""; }; + C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptView.swift; sourceTree = ""; }; + C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewModel.swift; sourceTree = ""; }; + C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewController.swift; sourceTree = ""; }; C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSettingStatus.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 = ""; }; @@ -2254,6 +2264,7 @@ C14882E927F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBookmarksCoreDataStorage.swift; sourceTree = ""; }; C14E2F7629DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceUsernameTruncatorTests.swift; sourceTree = ""; }; C158AC7A297AB5DC0008723A /* MockSecureVault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSecureVault.swift; sourceTree = ""; }; + C159DF062A430B60007834BB /* EmailSignupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupViewController.swift; sourceTree = ""; }; C160544029D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceUsernameTruncator.swift; sourceTree = ""; }; C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewModel.swift; sourceTree = ""; }; C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewController.swift; sourceTree = ""; }; @@ -2272,6 +2283,9 @@ C1CCCBA6283E101500CF3791 /* FaviconsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconsHelper.swift; sourceTree = ""; }; C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSession.swift; sourceTree = ""; }; C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSessionTests.swift; sourceTree = ""; }; + C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; }; + C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewModel.swift; sourceTree = ""; }; + C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewController.swift; sourceTree = ""; }; CB1AEFB02799AA940031AE3D /* SwiftUICollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICollectionViewCell.swift; sourceTree = ""; }; CB24F70E29A3EB15006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfigurationURLProvider.swift; path = ../Core/AppConfigurationURLProvider.swift; sourceTree = ""; }; CB258D0C29A4CD0500DEBA24 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; @@ -3521,6 +3535,7 @@ B652DF02287C01EE00C12A9C /* ContentBlocking */, 310D09192799EF5C00DC0060 /* Downloads */, F143C2C51E4A08F300CFDE3A /* DuckDuckGo.entitlements */, + C159DF052A430B36007834BB /* EmailProtection */, 839F119520DBC489007CD8C2 /* Feedback */, 85F2FFFE2215C163006BB258 /* FindInPage */, F13B4BF31F18C73A00814661 /* Home */, @@ -4157,6 +4172,15 @@ name = ImportExport; sourceTree = ""; }; + C159DF052A430B36007834BB /* EmailProtection */ = { + isa = PBXGroup; + children = ( + C1F341C32A6923D70032057B /* EmailAddressPrompt */, + C1CAA3D52A630ECB00807703 /* EmailSignup */, + ); + name = EmailProtection; + sourceTree = ""; + }; C17B59552A03AAC40055F2D1 /* PasswordGeneration */ = { isa = PBXGroup; children = ( @@ -4194,6 +4218,27 @@ name = AutofillLoginUI; sourceTree = ""; }; + C1CAA3D52A630ECB00807703 /* EmailSignup */ = { + isa = PBXGroup; + children = ( + C159DF062A430B60007834BB /* EmailSignupViewController.swift */, + C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */, + C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */, + C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */, + ); + name = EmailSignup; + sourceTree = ""; + }; + C1F341C32A6923D70032057B /* EmailAddressPrompt */ = { + isa = PBXGroup; + children = ( + C1F341C42A6924000032057B /* EmailAddressPromptView.swift */, + C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */, + C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */, + ); + name = EmailAddressPrompt; + sourceTree = ""; + }; CB1AEFB6279AF6420031AE3D /* WidgetEducation */ = { isa = PBXGroup; children = ( @@ -5948,6 +5993,7 @@ EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, + C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, 31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */, EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 0290472029E708B70008FE3C /* AppTPManageTrackersViewModel.swift in Sources */, @@ -5976,6 +6022,7 @@ 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, 854A01332A558B3A00FCC628 /* UIView+Constraints.swift in Sources */, + C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */, 83134D7D20E2D725006CE65D /* FeedbackSender.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, 314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */, @@ -6045,6 +6092,7 @@ 1E8AD1D527C2E22900ABA377 /* DownloadsListSectionViewModel.swift in Sources */, 4BC6DD1C2A60E6AD001EC129 /* ReportBrokenSiteView.swift in Sources */, 31584616281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift in Sources */, + C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */, F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */, F13B4BD51F183B3600814661 /* TabsModelPersistenceExtension.swift in Sources */, 980891A52237D4F500313A70 /* FeedbackNavigator.swift in Sources */, @@ -6137,6 +6185,7 @@ 986C7FA724171C6000A3557D /* BrokenSiteCategories.swift in Sources */, 85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */, 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, + C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, @@ -6167,6 +6216,7 @@ 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */, 0290472829E861BE0008FE3C /* AppTPTrackerDetailViewModel.swift in Sources */, 311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */, + C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */, 316931D727BD10BB0095F5ED /* SaveToDownloadsAlert.swift in Sources */, 31C70B5B2804C61000FB6AD1 /* SaveAutofillLoginManager.swift in Sources */, 85449EFD23FDA71F00512AAF /* KeyboardSettings.swift in Sources */, @@ -6178,6 +6228,7 @@ 1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */, 1E4DCF4E27B6A69600961E25 /* DownloadsListHostingController.swift in Sources */, 020108A129A5610C00644F9D /* AppTPActivityHostingViewController.swift in Sources */, + C1F341C92A6926920032057B /* EmailAddressPromptViewController.swift in Sources */, 02025B0F29884DC500E694E7 /* AppTrackerDataParser.swift in Sources */, 027F48742A4B5904001A1C6C /* AppTPAboutView.swift in Sources */, 311BD1B12836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift in Sources */, @@ -6234,6 +6285,7 @@ 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */, 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */, 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, + C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, 314C92B827C3DD660042EC96 /* QuickLookPreviewView.swift in Sources */, @@ -8816,7 +8868,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 75.0.4; + version = 75.1.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 354e4217b1..c9bd83843d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "02e87697d9e8d897bde0a913b7ed5b0943cbe993", - "version": "75.0.4" + "revision": "f3e85b86d6369cb03bfaf66169057c6452aa751f", + "version": "75.1.0" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", "state": { "branch": null, - "revision": "40bcd0d347b51d14e8114f191df2817d8dcb4284", - "version": "8.1.2" + "revision": "20a6aeddbd86b43fd83c42aa45fdd9ec6db0e0f7", + "version": "8.2.0" } }, { @@ -156,7 +156,7 @@ }, { "package": "TrackerRadarKit", - "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit.git", "state": { "branch": null, "revision": "4684440d03304e7638a2c8086895367e90987463", diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 98afafbac0..cb7f2673f1 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -441,6 +441,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { os_log("App launched with url %s", log: .lifecycleLog, type: .debug, url.absoluteString) + + if handleEmailSignUpDeepLink(url) { + return true + } + NotificationCenter.default.post(name: AutofillLoginListAuthenticator.Notifications.invalidateContext, object: nil) mainViewController?.clearNavigationStack() autoClear?.applicationWillMoveToForeground() @@ -575,6 +580,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } + private func handleEmailSignUpDeepLink(_ url: URL) -> Bool { + guard url.absoluteString.starts(with: URL.emailProtection.absoluteString), + let navViewController = mainViewController?.presentedViewController as? UINavigationController, + let emailSignUpViewController = navViewController.topViewController as? EmailSignupViewController else { + return false + } + emailSignUpViewController.loadUrl(url) + return true + } + private var mainViewController: MainViewController? { return window?.rootViewController as? MainViewController } diff --git a/DuckDuckGo/AutofillContentScopeFeatureToggles.swift b/DuckDuckGo/AutofillContentScopeFeatureToggles.swift index 0c27e1b94e..ddaeb67a4d 100644 --- a/DuckDuckGo/AutofillContentScopeFeatureToggles.swift +++ b/DuckDuckGo/AutofillContentScopeFeatureToggles.swift @@ -28,7 +28,7 @@ extension ContentScopeFeatureToggles { static var supportedFeaturesOniOS: ContentScopeFeatureToggles { let isAutofillEnabledInSettings = AutofillSettingStatus.isAutofillEnabledInSettings return ContentScopeFeatureToggles(emailProtection: true, - emailProtectionIncontextSignup: false, + emailProtectionIncontextSignup: featureFlagger.isFeatureOn(.incontextSignup) && Locale.current.isEnglishLanguage, credentialsAutofill: featureFlagger.isFeatureOn(.autofillCredentialInjecting) && isAutofillEnabledInSettings, identitiesAutofill: false, creditCardsAutofill: false, diff --git a/DuckDuckGo/AutofillLoginPromptView.swift b/DuckDuckGo/AutofillLoginPromptView.swift index 3e232642db..0a75d988ee 100644 --- a/DuckDuckGo/AutofillLoginPromptView.swift +++ b/DuckDuckGo/AutofillLoginPromptView.swift @@ -38,8 +38,7 @@ struct AutofillLoginPromptView: View { return ZStack { AutofillViews.CloseButtonHeader(action: viewModel.dismissView) - .offset(x: AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) ? Const.Size.closeButtonOffsetPortrait - : Const.Size.closeButtonOffset) + .offset(x: horizontalPadding) .zIndex(1) VStack { @@ -60,8 +59,7 @@ struct AutofillLoginPromptView: View { .useScrollView(shouldUseScrollView(), minHeight: frame.height) } - .padding(.horizontal, AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) ? Const.Size.closeButtonOffsetPortrait - : Const.Size.closeButtonOffset) + .padding(.horizontal, horizontalPadding) } @@ -93,6 +91,18 @@ struct AutofillLoginPromptView: View { } } + private var horizontalPadding: CGFloat { + guard AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) else { + return Const.Size.closeButtonOffset + } + + if AutofillViews.isSmallFrame(frame) { + return Const.Size.closeButtonOffsetPortraitSmallFrame + } else { + return Const.Size.closeButtonOffsetPortrait + } + } + private var bottomSpacer: some View { VStack { if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { @@ -166,6 +176,7 @@ private enum Const { enum Size { static let closeButtonOffset: CGFloat = 48.0 static let closeButtonOffsetPortrait: CGFloat = 44.0 + static let closeButtonOffsetPortraitSmallFrame: CGFloat = 16.0 static let topPadding: CGFloat = 56.0 static let headlineTopPadding: CGFloat = 24.0 static let ios15scrollOffset: CGFloat = 80.0 diff --git a/DuckDuckGo/AutofillViews.swift b/DuckDuckGo/AutofillViews.swift index 917336c4dc..f11649b090 100644 --- a/DuckDuckGo/AutofillViews.swift +++ b/DuckDuckGo/AutofillViews.swift @@ -28,6 +28,7 @@ struct AutofillViews { static let savePasswordMinHeight = 340.0 static let updateUsernameMinHeight = 310.0 static let passwordGenerationMinHeight: CGFloat = 310.0 + static let emailSignupPromptMinHeight: CGFloat = 260.0 struct CloseButtonHeader: View { let action: () -> Void @@ -43,7 +44,7 @@ struct AutofillViews { .resizable() .scaledToFit() .frame(width: Const.Size.closeButtonSize, height: Const.Size.closeButtonSize) - .foregroundColor(.primary) + .foregroundColor(Color(designSystemColor: .textPrimary)) } .frame(width: Const.Size.closeButtonTappableArea, height: Const.Size.closeButtonTappableArea) .contentShape(Rectangle()) @@ -75,7 +76,7 @@ struct AutofillViews { var body: some View { Text(title) .daxTitle3() - .foregroundColor(.primary) + .foregroundColor(Color(designSystemColor: .textPrimary)) .frame(maxWidth: .infinity) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) @@ -191,6 +192,11 @@ struct AutofillViews { verticalSizeClass == .regular && horizontalSizeClass == .regular } + // We have specific layouts for the smaller iPhones + static func isSmallFrame(_ frame: CGSize) -> Bool { + frame.width > 0 && frame.width <= Const.Size.smallDevice + } + static func contentHeightExceedsScreenHeight(_ contentHeight: CGFloat) -> Bool { if #available(iOS 16.0, *) { let topSafeAreaInset = UIApplication.shared.connectedScenes @@ -227,6 +233,7 @@ private enum Const { static let logoImage: CGFloat = 20.0 static let buttonCornerRadius: CGFloat = 8.0 static let buttonBorderWidth: CGFloat = 1.0 + static let smallDevice: CGFloat = 320.0 } } diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 6a9151ebf3..f3f572c51a 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -188,6 +188,15 @@ + + + + + + + + + diff --git a/DuckDuckGo/EmailAddressPromptView.swift b/DuckDuckGo/EmailAddressPromptView.swift new file mode 100644 index 0000000000..97f5cbbccf --- /dev/null +++ b/DuckDuckGo/EmailAddressPromptView.swift @@ -0,0 +1,169 @@ +// +// EmailAddressPromptView.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import DesignResourcesKit + +struct EmailAddressPromptView: View { + + @State var frame: CGSize = .zero + @ObservedObject var viewModel: EmailAddressPromptViewModel + @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 ZStack { + AutofillViews.CloseButtonHeader(action: viewModel.closeButtonPressed) + .offset(x: horizontalPadding) + .zIndex(1) + + VStack { + Spacer() + .frame(height: Const.Size.topPadding) + AutofillViews.Headline(title: UserText.emailAliasPromptTitle) + Spacer() + .frame(height: Const.Size.headlineTopPadding) + + VStack { + if let userEmail = viewModel.userEmail { + EmailAddressRow(title: userEmail, + subtitle: UserText.emailAliasPromptUseUserAddressSubtitle) { + viewModel.selectUserEmailPressed() + } + } + EmailAddressRow(title: UserText.emailAliasPromptGeneratePrivateAddress, + subtitle: UserText.emailAliasPromptGeneratePrivateAddressSubtitle) { + viewModel.selectGeneratedEmailPressed() + } + } + .padding(.bottom, 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) + } + + 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 var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.closeButtonOffsetPortraitSmallFrame + } else { + return Const.Size.closeButtonOffsetPortrait + } + } else { + return Const.Size.closeButtonOffset + } + } +} + +private struct EmailAddressRow: View { + + let title: String + let subtitle: String + let action: () -> Void + + var body: some View { + Button { + action() + } label: { + HStack(spacing: 0) { + Image.logo + .resizable() + .scaledToFit() + .frame(width: Const.Size.logoImage, height: Const.Size.logoImage) + .padding(.horizontal, Const.Size.logoHorizontalPadding) + VStack(alignment: .leading, spacing: Const.Size.rowVerticalSpacing) { + Text(title) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textPrimary)) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + Text(subtitle) + .daxFootnoteRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.vertical, Const.Size.rowVerticalPadding) + Spacer() + } + + } + + .background(Color(designSystemColor: .container)) + .cornerRadius(Const.Size.cornerRadius) + } + +} + +// MARK: - Constants + +private enum Const { + + enum Size { + static let closeButtonOffset: CGFloat = 48.0 + static let closeButtonOffsetPortrait: CGFloat = 44.0 + static let closeButtonOffsetPortraitSmallFrame: CGFloat = 16.0 + static let topPadding: CGFloat = 56.0 + static let headlineTopPadding: CGFloat = 24.0 + static let ios15scrollOffset: CGFloat = 80.0 + static let bottomPadding: CGFloat = 36.0 + static let logoImage: CGFloat = 24.0 + static let logoHorizontalPadding: CGFloat = 16.0 + static let rowVerticalSpacing: CGFloat = 3.0 + static let rowVerticalPadding: CGFloat = 11.0 + static let cornerRadius: CGFloat = 8.0 + } +} + +private extension Image { + static let logo = Image("Logo") +} + +struct DuckAddressPromptView_Previews: PreviewProvider { + static var previews: some View { + let viewModel = EmailAddressPromptViewModel(userEmail: "dax@duck.com") + EmailAddressPromptView(viewModel: viewModel) + } +} diff --git a/DuckDuckGo/EmailAddressPromptViewController.swift b/DuckDuckGo/EmailAddressPromptViewController.swift new file mode 100644 index 0000000000..b32fb08048 --- /dev/null +++ b/DuckDuckGo/EmailAddressPromptViewController.swift @@ -0,0 +1,122 @@ +// +// EmailAddressPromptViewController.swift +// DuckDuckGo +// +// Copyright © 2023 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 BrowserServicesKit +import Core +import SwiftUI +import UIKit + +class EmailAddressPromptViewController: UIViewController { + + typealias EmailAddressPromptViewControllerCompletion = (_ addressType: EmailManagerPermittedAddressType, _ autosave: Bool) -> Void + let completion: EmailAddressPromptViewControllerCompletion + + private var viewModel: EmailAddressPromptViewModel? + private let emailManager: EmailManager + + private var pixelParameters: [String: String] = [:] + + internal init(_ emailManager: EmailManager, completion: @escaping EmailAddressPromptViewControllerCompletion) { + self.emailManager = emailManager + self.completion = completion + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = UIColor(designSystemColor: .surface) + + setupEmailAddressPromptView() + + if let cohort = emailManager.cohort { + pixelParameters[PixelParameters.emailCohort] = cohort + } + } + + private func setupEmailAddressPromptView() { + let emailAddressPromptViewModel = EmailAddressPromptViewModel(userEmail: emailManager.userEmail) + emailAddressPromptViewModel.delegate = self + self.viewModel = emailAddressPromptViewModel + + let emailAddressPromptView = EmailAddressPromptView(viewModel: emailAddressPromptViewModel) + let controller = UIHostingController(rootView: emailAddressPromptView) + controller.view.backgroundColor = .clear + presentationController?.delegate = self + installChildViewController(controller) + } + +} + +extension EmailAddressPromptViewController: UISheetPresentationControllerDelegate { + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + Pixel.fire(pixel: .emailTooltipDismissed, withAdditionalParameters: pixelParameters, includedParameters: []) + + completion(.none, false) + } +} + +extension EmailAddressPromptViewController: EmailAddressPromptViewModelDelegate { + + func emailAddressPromptViewModelDidSelectUserEmail(_ viewModel: EmailAddressPromptViewModel) { + pixelParameters[PixelParameters.emailLastUsed] = emailManager.lastUseDate + emailManager.updateLastUseDate() + + Pixel.fire(pixel: .emailUserPressedUseAddress, withAdditionalParameters: pixelParameters, includedParameters: []) + + completion(.user, false) + + dismiss(animated: true) + } + + func emailAddressPromptViewModelDidSelectGeneratedEmail(_ viewModel: EmailAddressPromptViewModel) { + pixelParameters[PixelParameters.emailLastUsed] = emailManager.lastUseDate + emailManager.updateLastUseDate() + + Pixel.fire(pixel: .emailUserPressedUseAlias, withAdditionalParameters: pixelParameters, includedParameters: []) + + completion(.generated, true) + + dismiss(animated: true) + } + + func emailAddressPromptViewModelDidClose(_ viewModel: EmailAddressPromptViewModel) { + Pixel.fire(pixel: .emailTooltipDismissed, withAdditionalParameters: pixelParameters, includedParameters: []) + + completion(.none, false) + + dismiss(animated: true) + } + + func emailAddressPromptViewModelDidResizeContent(_ viewModel: EmailAddressPromptViewModel, 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/EmailAddressPromptViewModel.swift b/DuckDuckGo/EmailAddressPromptViewModel.swift new file mode 100644 index 0000000000..a30a28b15f --- /dev/null +++ b/DuckDuckGo/EmailAddressPromptViewModel.swift @@ -0,0 +1,59 @@ +// +// EmailAddressPromptViewModel.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import BrowserServicesKit + +protocol EmailAddressPromptViewModelDelegate: AnyObject { + func emailAddressPromptViewModelDidSelectUserEmail(_ viewModel: EmailAddressPromptViewModel) + func emailAddressPromptViewModelDidSelectGeneratedEmail(_ viewModel: EmailAddressPromptViewModel) + func emailAddressPromptViewModelDidClose(_ viewModel: EmailAddressPromptViewModel) + func emailAddressPromptViewModelDidResizeContent(_ viewModel: EmailAddressPromptViewModel, contentHeight: CGFloat) +} + +class EmailAddressPromptViewModel: ObservableObject { + + weak var delegate: EmailAddressPromptViewModelDelegate? + + var contentHeight: CGFloat = AutofillViews.passwordGenerationMinHeight { + didSet { + guard contentHeight != oldValue else { return } + delegate?.emailAddressPromptViewModelDidResizeContent(self, + contentHeight: max(contentHeight, AutofillViews.emailSignupPromptMinHeight)) + } + } + + let userEmail: String? + + init(userEmail: String?) { + self.userEmail = userEmail + } + + func selectUserEmailPressed() { + delegate?.emailAddressPromptViewModelDidSelectUserEmail(self) + } + + func selectGeneratedEmailPressed() { + delegate?.emailAddressPromptViewModelDidSelectGeneratedEmail(self) + } + + func closeButtonPressed() { + delegate?.emailAddressPromptViewModelDidClose(self) + } +} diff --git a/DuckDuckGo/EmailSignupPromptView.swift b/DuckDuckGo/EmailSignupPromptView.swift new file mode 100644 index 0000000000..debe3195ab --- /dev/null +++ b/DuckDuckGo/EmailSignupPromptView.swift @@ -0,0 +1,157 @@ +// +// EmailSignupPromptView.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import DesignResourcesKit + +struct EmailSignupPromptView: View { + + @State var frame: CGSize = .zero + @ObservedObject var viewModel: EmailSignupPromptViewModel + @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 ZStack { + AutofillViews.CloseButtonHeader(action: viewModel.closeButtonPressed) + .offset(x: horizontalPadding) + .zIndex(1) + + VStack { + Spacer() + .frame(height: Const.Size.topPadding) + IconAndTitle() + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Headline(title: UserText.emailSignupPromptTitle) + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Description(text: UserText.emailSignupPromptSubtitle) + 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) + } + + 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 struct IconAndTitle: View { + var body: some View { + HStack { + Image.logo + .resizable() + .scaledToFit() + .frame(width: Const.Size.logoImage, height: Const.Size.logoImage) + Text(UserText.emailProtection) + .daxFootnoteRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + } + } + } + + private var contentViewSpacer: some View { + VStack { + if AutofillViews.isIPhoneLandscape(verticalSizeClass) { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeightLandscape) + } else { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeight) + } + } + } + + private var ctaView: some View { + VStack(spacing: Const.Size.ctaVerticalSpacing) { + AutofillViews.PrimaryButton(title: UserText.emailSignupPromptSignUpButton, + action: viewModel.continueSignupPressed) + + AutofillViews.TertiaryButton(title: UserText.emailSignupPromptDoNotSignUpButton, + action: viewModel.rejectSignupPressed) + } + } + + private var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.closeButtonOffsetPortraitSmallFrame + } else { + return Const.Size.closeButtonOffsetPortrait + } + } else { + return Const.Size.closeButtonOffset + } + } +} + +// MARK: - Constants + +private enum Const { + + enum Size { + static let closeButtonOffset: CGFloat = 48.0 + static let closeButtonOffsetPortrait: CGFloat = 44.0 + static let closeButtonOffsetPortraitSmallFrame: CGFloat = 16.0 + static let topPadding: CGFloat = 56.0 + static let headlineTopPadding: CGFloat = 24.0 + static let ios15scrollOffset: CGFloat = 80.0 + static let contentSpacerHeight: CGFloat = 24.0 + static let contentSpacerHeightLandscape: CGFloat = 30.0 + static let ctaVerticalSpacing: CGFloat = 8.0 + static let bottomPadding: CGFloat = 12.0 + static let bottomPaddingIPad: CGFloat = 24.0 + static let logoImage: CGFloat = 20.0 + } +} + +private extension Image { + static let logo = Image("Logo") +} + +struct EmailSignupPromptView_Previews: PreviewProvider { + static var previews: some View { + let viewModel = EmailSignupPromptViewModel() + EmailSignupPromptView(viewModel: viewModel) + } +} diff --git a/DuckDuckGo/EmailSignupPromptViewController.swift b/DuckDuckGo/EmailSignupPromptViewController.swift new file mode 100644 index 0000000000..a0c00de99c --- /dev/null +++ b/DuckDuckGo/EmailSignupPromptViewController.swift @@ -0,0 +1,119 @@ +// +// EmailSignupPromptViewController.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import SwiftUI +import Core + +class EmailSignupPromptViewController: UIViewController { + + typealias EmailSignupPromptViewControllerCompletion = (_ continueSignup: Bool) -> Void + let completion: EmailSignupPromptViewControllerCompletion + + private static let inContextEmailSignupPromptDismissedPermanentlyAtKey = "Autofill.InContextEmailSignup.dismissed.permanently.at" + + private var viewModel: EmailSignupPromptViewModel? + + private var inContextEmailSignupPromptDismissedPermanentlyAt: Double? { + get { + UserDefaults().object(forKey: Self.inContextEmailSignupPromptDismissedPermanentlyAtKey) as? Double ?? nil + } + + set { + UserDefaults().set(newValue, forKey: Self.inContextEmailSignupPromptDismissedPermanentlyAtKey) + } + } + + internal init(completion: @escaping EmailSignupPromptViewControllerCompletion) { + self.completion = completion + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = UIColor(designSystemColor: .surface) + + setupEmailSignupPromptView() + + Pixel.fire(pixel: .emailIncontextPromptDisplayed) + } + + private func setupEmailSignupPromptView() { + let emailSignupPromptViewModel = EmailSignupPromptViewModel() + emailSignupPromptViewModel.delegate = self + self.viewModel = emailSignupPromptViewModel + + let emailSignupPromptView = EmailSignupPromptView(viewModel: emailSignupPromptViewModel) + let controller = UIHostingController(rootView: emailSignupPromptView) + controller.view.backgroundColor = .clear + presentationController?.delegate = self + installChildViewController(controller) + } + +} + +extension EmailSignupPromptViewController: UISheetPresentationControllerDelegate { + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + Pixel.fire(pixel: .emailIncontextPromptDismissed) + + completion(false) + } +} + +extension EmailSignupPromptViewController: EmailSignupPromptViewModelDelegate { + + func emailSignupPromptViewModelDidSelect(_ viewModel: EmailSignupPromptViewModel) { + Pixel.fire(pixel: .emailIncontextPromptConfirmed) + + dismiss(animated: true) + completion(true) + } + + func emailSignupPromptViewModelDidReject(_ viewModel: EmailSignupPromptViewModel) { + inContextEmailSignupPromptDismissedPermanentlyAt = Date().timeIntervalSince1970 + Pixel.fire(pixel: .emailIncontextPromptDismissedPersistent) + + completion(false) + dismiss(animated: true) + } + + func emailSignupPromptViewModelDidClose(_ viewModel: EmailSignupPromptViewModel) { + Pixel.fire(pixel: .emailIncontextPromptDismissed) + + completion(false) + dismiss(animated: true) + } + + func emailSignupPromptViewModelDidResizeContent(_ viewModel: EmailSignupPromptViewModel, 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/EmailSignupPromptViewModel.swift b/DuckDuckGo/EmailSignupPromptViewModel.swift new file mode 100644 index 0000000000..acb82763aa --- /dev/null +++ b/DuckDuckGo/EmailSignupPromptViewModel.swift @@ -0,0 +1,54 @@ +// +// EmailSignupPromptViewModel.swift +// DuckDuckGo +// +// Copyright © 2023 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 EmailSignupPromptViewModelDelegate: AnyObject { + func emailSignupPromptViewModelDidSelect(_ viewModel: EmailSignupPromptViewModel) + func emailSignupPromptViewModelDidReject(_ viewModel: EmailSignupPromptViewModel) + func emailSignupPromptViewModelDidClose(_ viewModel: EmailSignupPromptViewModel) + func emailSignupPromptViewModelDidResizeContent(_ viewModel: EmailSignupPromptViewModel, contentHeight: CGFloat) +} + +class EmailSignupPromptViewModel: ObservableObject { + + weak var delegate: EmailSignupPromptViewModelDelegate? + + var contentHeight: CGFloat = AutofillViews.passwordGenerationMinHeight { + didSet { + guard contentHeight != oldValue else { + return + } + delegate?.emailSignupPromptViewModelDidResizeContent(self, + contentHeight: max(contentHeight, AutofillViews.emailSignupPromptMinHeight)) + } + } + + func continueSignupPressed() { + delegate?.emailSignupPromptViewModelDidSelect(self) + } + + func rejectSignupPressed() { + delegate?.emailSignupPromptViewModelDidReject(self) + } + + func closeButtonPressed() { + delegate?.emailSignupPromptViewModelDidClose(self) + } +} diff --git a/DuckDuckGo/EmailSignupViewController.swift b/DuckDuckGo/EmailSignupViewController.swift new file mode 100644 index 0000000000..88e4a7bf6b --- /dev/null +++ b/DuckDuckGo/EmailSignupViewController.swift @@ -0,0 +1,467 @@ +// +// EmailSignupViewController.swift +// DuckDuckGo +// +// Copyright © 2023 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 BrowserServicesKit +import Common +import Core +import Networking +import UserScript +import WebKit +import DesignResourcesKit +import SecureStorage + +// swiftlint:disable file_length +class EmailSignupViewController: UIViewController { + + private enum Constants { + static let duckDuckGoTitle: String = "DuckDuckGo" + static let backImage = UIImage(systemName: "chevron.left") + + static let signUpUrl: String = "https://duckduckgo.com/email/start-incontext" + + static let emailPath: String = "email/" + static let emailStartInContextPath: String = "email/start-incontext" + static let emailChooseAddressPath: String = "email/choose-address" + static let emailReviewPath: String = "email/review" + static let emailWelcomePath: String = "email/welcome" + static let emailWelcomeInContextPath: String = "email/welcome-incontext" + } + + private enum SignupState { + case start + case emailEntered + case complete + case other + } + + let completion: ((Bool) -> Void) + + private var webView: WKWebView! + + private var webViewUrlObserver: NSKeyValueObservation? + + lazy private var featureFlagger = AppDependencyProvider.shared.featureFlagger + + lazy private var emailManager: EmailManager = { + let emailManager = EmailManager() + emailManager.requestDelegate = self + return emailManager + }() + + lazy private var vaultManager: SecureVaultManager = { + let manager = SecureVaultManager(includePartialAccountMatches: true, + tld: AppDependencyProvider.shared.storageCache.tld) + manager.delegate = self + return manager + }() + + private var url: URL? { + didSet { + guard let url = url else { + navigationItem.rightBarButtonItems = [] + return + } + if url.absoluteString.hasSuffix(Constants.emailPath) || url.absoluteString.hasSuffix(Constants.emailStartInContextPath) { + signupStage = .start + } else if url.absoluteString.hasSuffix(Constants.emailChooseAddressPath) || url.absoluteString.hasSuffix(Constants.emailReviewPath) { + signupStage = .emailEntered + } else if url.absoluteString.hasSuffix(Constants.emailWelcomePath) || url.absoluteString.hasSuffix(Constants.emailWelcomeInContextPath) { + signupStage = .complete + } else { + signupStage = .other + } + } + } + + @Published private var signupStage: SignupState = .start { + didSet { + updateNavigationBarButtons() + } + } + + private var canGoBack: Bool { + let webViewCanGoBack = webView.canGoBack + let navigatedToError = webView.url != nil + return webViewCanGoBack || navigatedToError + } + + private lazy var backBarButtonItem: UIBarButtonItem = { + let button = UIButton(type: .system) + button.setImage(Constants.backImage, for: .normal) + button.setTitle(UserText.backButtonTitle, for: .normal) + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + button.addTarget(self, action: #selector(backButtonPressed), for: .touchUpInside) + return UIBarButtonItem(customView: button) + }() + + private lazy var cancelBarButtonItem: UIBarButtonItem = { + UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed)) + }() + + private lazy var nextBarButtonItem: UIBarButtonItem = { + UIBarButtonItem(title: UserText.navigationTitleDone, + style: .plain, + target: self, + action: #selector(nextButtonPressed)) + }() + + // MARK: - Public interface + + init(completion: @escaping ((Bool) -> Void)) { + self.completion = completion + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupWebView() + setupNavigationBarTitle() + addDuckDuckGoEmailObserver() + applyTheme(ThemeManager.shared.currentTheme) + + isModalInPresentation = true + navigationController?.presentationController?.delegate = self + + Pixel.fire(pixel: .emailIncontextModalDisplayed) + } + + + func loadUrl(_ url: URL?) { + guard let url = url else { return } + + let request = URLRequest.userInitiated(url) + webView.load(request) + } + + + // MARK: - Private + + private func setupWebView() { + let configuration = WKWebViewConfiguration.persistent() + let userContentController = UserContentController() + configuration.userContentController = userContentController + userContentController.delegate = self + + webView = WKWebView(frame: view.bounds, configuration: configuration) + webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + view.addSubview(webView) + + webViewUrlObserver = webView.observe(\.url, options: .new, changeHandler: { [weak self] _, _ in + self?.webViewUrlHasChanged() + }) + + if #available(iOS 16.4, *) { + updateWebViewInspectability() + } + + // Slight delay needed for userScripts to load otherwise email protection webpage reports that this is an unsupported browser + let workItem = DispatchWorkItem { [unowned self] in + self.loadUrl(URL(string: Constants.signUpUrl)) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: workItem) + } + + @available(iOS 16.4, *) @objc + private func updateWebViewInspectability() { +#if DEBUG + webView.isInspectable = true +#else + webView.isInspectable = AppUserDefaults().inspectableWebViewEnabled +#endif + } + + private func webViewUrlHasChanged() { + url = webView.url + } + + private func setupNavigationBarTitle() { + let titleLabel: UILabel = UILabel() + titleLabel.text = Constants.duckDuckGoTitle + titleLabel.font = .daxFootnoteRegular() + titleLabel.textColor = UIColor(designSystemColor: .textSecondary) + + let subtitleLabel: UILabel = UILabel() + subtitleLabel.text = UserText.emailProtection + subtitleLabel.font = .daxHeadline() + subtitleLabel.textColor = UIColor(designSystemColor: .textPrimary) + + let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + stackView.axis = .vertical + stackView.alignment = .center + + navigationItem.titleView = stackView + } + + private func addDuckDuckGoEmailObserver() { + NotificationCenter.default.addObserver(self, + selector: #selector(onDuckDuckGoEmailSignIn), + name: .emailDidSignIn, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(onDuckDuckGoEmailDidCloseEmailProtection), + name: .emailDidCloseEmailProtection, + object: nil) + } + + @objc + private func onDuckDuckGoEmailSignIn(_ notification: Notification) { + if signupStage != .complete { + completed(true) + } + } + + @objc + private func onDuckDuckGoEmailDidCloseEmailProtection(_ notification: Notification) { + emailSignupCompleted() + } + + private func updateNavigationBarButtons() { + switch signupStage { + case .start: + navigationItem.leftBarButtonItem = cancelBarButtonItem + navigationItem.rightBarButtonItem = nil + case .complete: + navigationItem.leftBarButtonItem = nil + navigationItem.rightBarButtonItem = nextBarButtonItem + default: + navigationItem.leftBarButtonItem = canGoBack ? backBarButtonItem : nil + navigationItem.rightBarButtonItem = nil + } + } + + @objc + private func backButtonPressed() { + if canGoBack { + webView.goBack() + } + } + + @objc + private func cancelButtonPressed() { + Pixel.fire(pixel: .emailIncontextModalDismissed) + completed(false) + } + + @objc + private func nextButtonPressed() { + emailSignupCompleted() + } + + private func emailSignupCompleted() { + completed(true) + } + + private func completed(_ success: Bool) { + completion(success) + dismiss(animated: true) + } +} + +// MARK: - UIPopoverPresentationControllerDelegate + +extension EmailSignupViewController: UIPopoverPresentationControllerDelegate { + + func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + return .none + } + +} + +// MARK: - UIAdaptivePresentationControllerDelegate + +extension EmailSignupViewController: UIAdaptivePresentationControllerDelegate { + + func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { + if case .emailEntered = signupStage { + let alert = UIAlertController(title: UserText.emailSignupExitEarlyAlertTitle, message: nil, preferredStyle: .alert) + + let continueAction = UIAlertAction(title: UserText.emailSignupExitEarlyActionContinue, style: .default) { _ in + Pixel.fire(pixel: .emailIncontextModalExitEarlyContinue) + } + + let cancelAction = UIAlertAction(title: UserText.emailSignupExitEarlyActionExit, style: .default) { [weak self] _ in + Pixel.fire(pixel: .emailIncontextModalExitEarly) + self?.completed(false) + } + + alert.addAction(continueAction) + alert.addAction(cancelAction) + alert.preferredAction = continueAction + + present(alert, animated: true) + } else if case .complete = signupStage { + completed(true) + } else { + Pixel.fire(pixel: .emailIncontextModalDismissed) + completed(false) + } + } + +} + +// MARK: - UserContentControllerDelegate + +extension EmailSignupViewController: UserContentControllerDelegate { + + func userContentController(_ userContentController: UserContentController, + didInstallContentRuleLists contentRuleLists: [String: WKContentRuleList], + userScripts: UserScriptsProvider, + updateEvent: ContentBlockerRulesManager.UpdateEvent) { + guard let userScripts = userScripts as? UserScripts else { fatalError("Unexpected UserScripts") } + + userScripts.autofillUserScript.emailDelegate = emailManager + userScripts.autofillUserScript.vaultDelegate = vaultManager + } +} + +// MARK: - EmailManagerRequestDelegate + +extension EmailSignupViewController: EmailManagerRequestDelegate { + + // swiftlint:disable function_parameter_count + func emailManager(_ emailManager: EmailManager, requested url: URL, method: String, headers: [String: String], parameters: [String: String]?, httpBody: Data?, timeoutInterval: TimeInterval) async throws -> Data { + let method = APIRequest.HTTPMethod(rawValue: method) ?? .post + let configuration = APIRequest.Configuration(url: url, + method: method, + queryParameters: parameters ?? [:], + headers: APIRequest.Headers(additionalHeaders: headers), + body: httpBody, + timeoutInterval: timeoutInterval) + let request = APIRequest(configuration: configuration, urlSession: .session()) + return try await request.fetch().data ?? { throw AliasRequestError.noDataError }() + } + // swiftlint:enable function_parameter_count + + func emailManagerKeychainAccessFailed(accessType: EmailKeychainAccessType, error: EmailKeychainAccessError) { + var parameters = [ + PixelParameters.emailKeychainAccessType: accessType.rawValue, + PixelParameters.emailKeychainError: error.errorDescription + ] + + if case let .keychainLookupFailure(status) = error { + parameters[PixelParameters.emailKeychainKeychainStatus] = String(status) + parameters[PixelParameters.emailKeychainKeychainOperation] = "lookup" + } + + if case let .keychainDeleteFailure(status) = error { + parameters[PixelParameters.emailKeychainKeychainStatus] = String(status) + parameters[PixelParameters.emailKeychainKeychainOperation] = "delete" + } + + if case let .keychainSaveFailure(status) = error { + parameters[PixelParameters.emailKeychainKeychainStatus] = String(status) + parameters[PixelParameters.emailKeychainKeychainOperation] = "save" + } + + Pixel.fire(pixel: .emailAutofillKeychainError, withAdditionalParameters: parameters) + } + +} + +// MARK: - SecureVaultManagerDelegate + +extension EmailSignupViewController: SecureVaultManagerDelegate { + + func secureVaultInitFailed(_ error: SecureStorageError) { + SecureVaultErrorReporter.shared.secureVaultInitFailed(error) + } + + func secureVaultManagerIsEnabledStatus(_: SecureVaultManager) -> Bool { + let isEnabled = AutofillSettingStatus.isAutofillEnabledInSettings && featureFlagger.isFeatureOn(.autofillCredentialInjecting) + return isEnabled + } + + func secureVaultManagerShouldSaveData(_: BrowserServicesKit.SecureVaultManager) -> Bool { + return false + } + + func secureVaultManager(_ vault: SecureVaultManager, + promptUserToStoreAutofillData data: AutofillData, + withTrigger trigger: AutofillUserScript.GetTriggerType?) { + // no-op + } + + func secureVaultManager(_: SecureVaultManager, + promptUserToAutofillCredentialsForDomain domain: String, + withAccounts accounts: [SecureVaultModels.WebsiteAccount], + withTrigger trigger: AutofillUserScript.GetTriggerType, + completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) { + // no-op + } + + func secureVaultManager(_: SecureVaultManager, + promptUserWithGeneratedPassword password: String, + completionHandler: @escaping (Bool) -> Void) { + // no-op + } + + func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) { + // no-op + } + + func secureVaultManager(_: SecureVaultManager, didRequestAuthenticationWithCompletionHandler: @escaping (Bool) -> Void) { + // no-op + } + + func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, didRequestCreditCardsManagerForDomain domain: String) { + // no-op + } + + func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, didRequestIdentitiesManagerForDomain domain: String) { + // no-op + } + + func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, didRequestPasswordManagerForDomain domain: String) { + // no-op + } + + func secureVaultManager(_: SecureVaultManager, didReceivePixel pixel: AutofillUserScript.JSPixel) { + guard !pixel.isEmailPixel else { + // The iOS app uses a native email autofill UI, and sends its pixels separately. Ignore pixels sent from the JS layer. + return + } + + Pixel.fire(pixel: .autofillJSPixelFired(pixel)) + } + +} + +// MARK: Themable + +extension EmailSignupViewController: Themable { + + func decorate(with theme: Theme) { + view.backgroundColor = theme.backgroundColor + + navigationController?.navigationBar.barTintColor = theme.barBackgroundColor + navigationController?.navigationBar.tintColor = theme.navigationBarTintColor + } + +} + +// swiftlint:enable file_length diff --git a/DuckDuckGo/MainViewController+Email.swift b/DuckDuckGo/MainViewController+Email.swift index 6cc583210f..df085bc017 100644 --- a/DuckDuckGo/MainViewController+Email.swift +++ b/DuckDuckGo/MainViewController+Email.swift @@ -102,60 +102,58 @@ extension MainViewController: EmailManagerRequestDelegate { // MARK: - EmailManagerAliasPermissionDelegate extension MainViewController: EmailManagerAliasPermissionDelegate { - func emailManager(_ emailManager: BrowserServicesKit.EmailManager, didRequestPermissionToProvideAliasWithCompletion: @escaping (BrowserServicesKit.EmailManagerPermittedAddressType, Bool) -> Void) { + func emailManager(_ emailManager: EmailManager, + didRequestPermissionToProvideAliasWithCompletion: @escaping (EmailManagerPermittedAddressType, Bool) -> Void) { DispatchQueue.main.async { - let alert = UIAlertController(title: UserText.emailAliasAlertTitle, message: nil, preferredStyle: .actionSheet) - alert.overrideUserInterfaceStyle() - - var pixelParameters: [String: String] = [:] - - if let cohort = emailManager.cohort { - pixelParameters[PixelParameters.emailCohort] = cohort + let emailAddressPromptViewController = EmailAddressPromptViewController(emailManager) { addressType, autosave in + didRequestPermissionToProvideAliasWithCompletion(addressType, autosave) } - if let userEmail = emailManager.userEmail { - let actionTitle = String(format: UserText.emailAliasAlertUseUserAddress, userEmail) - alert.addAction(title: actionTitle) { - pixelParameters[PixelParameters.emailLastUsed] = emailManager.lastUseDate - emailManager.updateLastUseDate() - - Pixel.fire(pixel: .emailUserPressedUseAddress, withAdditionalParameters: pixelParameters, includedParameters: []) - - didRequestPermissionToProvideAliasWithCompletion(.user, false) + if #available(iOS 15.0, *) { + if let presentationController = emailAddressPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.emailSignupPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] + } } } + self.present(emailAddressPromptViewController, animated: true) + } - alert.addAction(title: UserText.emailAliasAlertGeneratePrivateAddress) { - pixelParameters[PixelParameters.emailLastUsed] = emailManager.lastUseDate - emailManager.updateLastUseDate() - - Pixel.fire(pixel: .emailUserPressedUseAlias, withAdditionalParameters: pixelParameters, includedParameters: []) - - didRequestPermissionToProvideAliasWithCompletion(.generated, true) - } + } - alert.addAction(title: UserText.emailAliasAlertDecline) { - Pixel.fire(pixel: .emailTooltipDismissed, withAdditionalParameters: pixelParameters, includedParameters: []) + func emailManager(_ emailManager: EmailManager, didRequestInContextSignUp completionHandler: @escaping (_ shouldContinue: Bool) -> Void) { - didRequestPermissionToProvideAliasWithCompletion(.none, false) + let emailSignupPromptViewController = EmailSignupPromptViewController { shouldContinue in + if shouldContinue { + let signupViewController = EmailSignupViewController { shouldContinue in + completionHandler(shouldContinue) + } + let signupNavigationController = UINavigationController(rootViewController: signupViewController) + self.present(signupNavigationController, animated: true, completion: nil) + } else { + completionHandler(shouldContinue) } + } - if UIDevice.current.userInterfaceIdiom == .pad { - // make sure the completion handler is called if the alert is dismissed by tapping outside the alert - alert.addAction(title: "", style: .cancel) { - Pixel.fire(pixel: .emailTooltipDismissed, withAdditionalParameters: pixelParameters) - didRequestPermissionToProvideAliasWithCompletion(.none, false) + if #available(iOS 15.0, *) { + if let presentationController = emailSignupPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.emailSignupPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] } } - - alert.popoverPresentationController?.permittedArrowDirections = [] - alert.popoverPresentationController?.delegate = self - let bounds = self.view.bounds - let point = Point(x: Int((bounds.maxX - bounds.minX) / 2.0), y: Int(bounds.maxY)) - self.present(controller: alert, fromView: self.view, atPoint: point) } + self.present(emailSignupPromptViewController, animated: true) + } } diff --git a/DuckDuckGo/PasswordGenerationPromptView.swift b/DuckDuckGo/PasswordGenerationPromptView.swift index 2aae578153..1ed7fcdc0f 100644 --- a/DuckDuckGo/PasswordGenerationPromptView.swift +++ b/DuckDuckGo/PasswordGenerationPromptView.swift @@ -38,8 +38,7 @@ struct PasswordGenerationPromptView: View { return ZStack { AutofillViews.CloseButtonHeader(action: viewModel.cancelButtonPressed) - .offset(x: AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) ? Const.Size.closeButtonOffsetPortrait - : Const.Size.closeButtonOffset) + .offset(x: horizontalPadding) .zIndex(1) VStack { @@ -66,8 +65,7 @@ struct PasswordGenerationPromptView: View { .useScrollView(shouldUseScrollView(), minHeight: frame.height) } - .padding(.horizontal, AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) ? Const.Size.closeButtonOffsetPortrait - : Const.Size.closeButtonOffset) + .padding(.horizontal, horizontalPadding) } @@ -138,6 +136,18 @@ struct PasswordGenerationPromptView: View { action: viewModel.cancelButtonPressed) } } + + private var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.closeButtonOffsetPortraitSmallFrame + } else { + return Const.Size.closeButtonOffsetPortrait + } + } else { + return Const.Size.closeButtonOffset + } + } } // MARK: - View Helpers @@ -173,6 +183,7 @@ private enum Const { enum Size { static let closeButtonOffset: CGFloat = 48.0 static let closeButtonOffsetPortrait: CGFloat = 44.0 + static let closeButtonOffsetPortraitSmallFrame: CGFloat = 16.0 static let topPadding: CGFloat = 56.0 static let ios15scrollOffset: CGFloat = 80.0 static let passwordButtonSpacing: CGFloat = 10.0 diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 324d9c2a46..65343a00b1 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -36,6 +36,7 @@ class RootDebugViewController: UITableViewController { case crashMemory = 667 case toggleInspectableWebViews = 668 case toggleInternalUserState = 669 + case resetEmailProtectionInContextSignUp = 670 } @IBOutlet weak var shareButton: UIBarButtonItem! @@ -137,6 +138,11 @@ class RootDebugViewController: UITableViewController { NotificationCenter.default.post(Notification(name: AppUserDefaults.Notifications.inspectableWebViewsToggled)) } + if tableView.cellForRow(at: indexPath)?.tag == Row.resetEmailProtectionInContextSignUp.rawValue { + EmailManager().resetEmailProtectionInContextPrompt() + tableView.deselectRow(at: indexPath, animated: true) + } + } } diff --git a/DuckDuckGo/SaveLoginView.swift b/DuckDuckGo/SaveLoginView.swift index d993fe92c6..4e80ca014a 100644 --- a/DuckDuckGo/SaveLoginView.swift +++ b/DuckDuckGo/SaveLoginView.swift @@ -84,8 +84,7 @@ struct SaveLoginView: View { return ZStack { AutofillViews.CloseButtonHeader(action: viewModel.cancelButtonPressed) - .offset(x: AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) ? Const.Size.closeButtonOffsetPortrait - : Const.Size.closeButtonOffset) + .offset(x: horizontalPadding) .zIndex(1) VStack { @@ -112,8 +111,7 @@ struct SaveLoginView: View { }) .useScrollView(shouldUseScrollView(), minHeight: frame.height) } - .padding(.horizontal, AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) ? Const.Size.closeButtonOffsetPortrait - : Const.Size.closeButtonOffset) + .padding(.horizontal, horizontalPadding) } private func shouldUseScrollView() -> Bool { @@ -148,6 +146,18 @@ struct SaveLoginView: View { } } + private var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.closeButtonOffsetPortraitSmallFrame + } else { + return Const.Size.closeButtonOffsetPortrait + } + } else { + return Const.Size.closeButtonOffset + } + } + private var bottomSpacer: some View { VStack { if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { @@ -190,6 +200,7 @@ private enum Const { enum Size { static let closeButtonOffset: CGFloat = 48.0 static let closeButtonOffsetPortrait: CGFloat = 44.0 + static let closeButtonOffsetPortraitSmallFrame: CGFloat = 16.0 static let topPadding: CGFloat = 56.0 static let headlineTopPadding: CGFloat = 24.0 static let ios15scrollOffset: CGFloat = 80.0 diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 58bf473f0a..99bed4608a 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -2281,6 +2281,10 @@ extension TabViewController: SecureVaultManagerDelegate { return isEnabled } + func secureVaultManagerShouldSaveData(_: SecureVaultManager) -> Bool { + true + } + func secureVaultManager(_ vault: SecureVaultManager, promptUserToStoreAutofillData data: AutofillData, withTrigger trigger: AutofillUserScript.GetTriggerType?) { @@ -2307,18 +2311,6 @@ extension TabViewController: SecureVaultManagerDelegate { } } - private func deleteLoginFor(accountIdInt: Int64) { - do { - let secureVault = try? AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared) - if secureVault == nil { - os_log("Failed to make vault") - } - try secureVault?.deleteWebsiteCredentialsFor(accountId: accountIdInt) - } catch { - Pixel.fire(pixel: .secureVaultError, error: error) - } - } - func secureVaultManager(_: SecureVaultManager, promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], @@ -2347,6 +2339,27 @@ extension TabViewController: SecureVaultManagerDelegate { } } + func secureVaultManager(_: SecureVaultManager, + promptUserWithGeneratedPassword password: String, + completionHandler: @escaping (Bool) -> Void) { + let passwordGenerationPromptViewController = PasswordGenerationPromptViewController(generatedPassword: password) { useGeneratedPassword in + completionHandler(useGeneratedPassword) + } + + if #available(iOS 15.0, *) { + if let presentationController = passwordGenerationPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.passwordGenerationMinHeight + })] + } else { + presentationController.detents = [.medium()] + } + } + } + self.present(passwordGenerationPromptViewController, animated: true) + } + /// Using Bool for detent size parameter to be backward compatible with iOS 14 func presentAutofillPromptViewController(accountMatches: AccountMatches, domain: String, @@ -2386,47 +2399,10 @@ extension TabViewController: SecureVaultManagerDelegate { // No-op, don't need to do anything here } - func secureVaultManagerShouldAutomaticallyUpdateCredentialsWithoutUsername(_: SecureVaultManager, shouldSilentlySave: Bool) -> Bool { - guard AutofillSettingStatus.isAutofillEnabledInSettings, - featureFlagger.isFeatureOn(.autofillPasswordGeneration) else { return false } - return shouldSilentlySave ? true : false - } - - func secureVaultManagerShouldSilentlySaveGeneratedPassword(_: SecureVaultManager) -> Bool { - guard AutofillSettingStatus.isAutofillEnabledInSettings, - featureFlagger.isFeatureOn(.autofillPasswordGeneration) else { return false } - return true - } - - func secureVaultManagerShouldSaveData(_: SecureVaultManager) -> Bool { - true - } - func secureVaultManager(_: SecureVaultManager, didRequestAuthenticationWithCompletionHandler: @escaping (Bool) -> Void) { // We don't have auth yet } - func secureVaultManager(_: SecureVaultManager, - promptUserWithGeneratedPassword password: String, - completionHandler: @escaping (Bool) -> Void) { - let passwordGenerationPromptViewController = PasswordGenerationPromptViewController(generatedPassword: password) { useGeneratedPassword in - completionHandler(useGeneratedPassword) - } - - if #available(iOS 15.0, *) { - if let presentationController = passwordGenerationPromptViewController.presentationController as? UISheetPresentationController { - if #available(iOS 16.0, *) { - presentationController.detents = [.custom(resolver: { _ in - AutofillViews.passwordGenerationMinHeight - })] - } else { - presentationController.detents = [.medium()] - } - } - } - self.present(passwordGenerationPromptViewController, animated: true) - } - func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, didRequestCreditCardsManagerForDomain domain: String) { } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index c90b363cdc..10911d1ab9 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -308,6 +308,11 @@ public struct UserText { public static let emailBrowsingMenuUseNewDuckAddress = NSLocalizedString("email.browsingMenu.useNewDuckAddress", value: "Generate Private Duck Address", comment: "Email option title in the browsing menu") public static let emailBrowsingMenuAlert = NSLocalizedString("email.browsingMenu.alert", value: "New address copied to your clipboard", comment: "Title for the email copy browsing menu alert") + public static let emailAliasPromptTitle = NSLocalizedString("email.aliasAlert.prompt.title", value: "Select email address", comment: "Title for the email alias selection prompt") + public static let emailAliasPromptUseUserAddressSubtitle = NSLocalizedString("email.aliasAlert.prompt.useUserAddress.subtitle", value: "Block email trackers", comment: "Subtitle for choosing primary user email address") + public static let emailAliasPromptGeneratePrivateAddress = NSLocalizedString("email.aliasAlert.prompt.generatePrivateAddress", value: "Generate Private Duck Address", comment: "Option for generating a private email address") + public static let emailAliasPromptGeneratePrivateAddressSubtitle = NSLocalizedString("email.aliasAlert.prompt.generatePrivateAddress.subtitle", value: "Block email trackers & hide address", comment: "Subtitle for generating a private email address") + public static let emailAliasAlertTitle = NSLocalizedString("email.aliasAlert.title", value: "Block email trackers with a Duck Address", comment: "Title for the email alias selection alert") public static let emailAliasAlertUseUserAddress = NSLocalizedString("email.aliasAlert.useUserAddress", value: "Use %@", comment: "Parameter is an email address (string)") public static let emailAliasAlertGeneratePrivateAddress = NSLocalizedString("email.aliasAlert.generatePrivateAddress", value: "Generate Private Duck Address", comment: "Option for generating a private email address") @@ -641,7 +646,7 @@ In addition to the details entered into this form, your app issue report will co static let inviteDialogUnrecognizedCodeMessage = NSLocalizedString("invite.dialog.unrecognized.code.message", value: "We didn’t recognize this Invite Code.", comment: "Message to show after user enters an unrecognized invite code") static let inviteDialogErrorAlertOKButton = NSLocalizedString("invite.alert.ok.button", value: "OK", comment: "OK title for invite screen alert dismissal button") - + // MARK: Notifications public static let macWaitlistAvailableNotificationTitle = NSLocalizedString("mac-waitlist.available.notification.title", value: "DuckDuckGo for Mac is ready!", comment: "Title for the macOS waitlist notification") @@ -770,14 +775,21 @@ In addition to the details entered into this form, your app issue report will co static let autofillDeactivate = NSLocalizedString("pm.deactivate", value: "Deactivate", comment: "Deactivate button") static let autofillActivate = NSLocalizedString("pm.activate", value: "Reactivate", comment: "Activate button") + // Email Protection In-context Signup + public static let emailProtection = NSLocalizedString("email-protection", value: "Email Protection", comment: "Email protection service offered by DuckDuckGo") + public static let emailSignupPromptTitle = NSLocalizedString("email.signup-prompt.title", value:"Hide Your Email and\nBlock Trackers", comment: "Title for prompt to sign up for email protection") + public static let emailSignupPromptSubtitle = NSLocalizedString("email.signup-prompt.subtitle", value:"Create a unique, random address that also removes hidden trackers and forwards email to your inbox.", comment: "Subtitle for prompt to sign up for email protection") + public static let emailSignupPromptSignUpButton = NSLocalizedString("email.signup-prompt.signup-button.cta", value:"Protect My Email", comment: "Button title choosing to sign up for email protection") + public static let emailSignupPromptDoNotSignUpButton = NSLocalizedString("email.signup-prompt.do-not-signup-button.cta", value:"Don’t Show Again", comment: "Button title choosing not to sign up for email protection and not to be prompted again") + public static let emailSignupExitEarlyAlertTitle = NSLocalizedString("email.signup-prompt.alert.title", value: "If you exit now, your Duck Address will not be saved!", comment: "Title for exiting the Email Protection signup early alert") + public static let emailSignupExitEarlyActionContinue = NSLocalizedString("email.signup-prompt.alert.continue", value: "Continue Setup", comment: "Option to continue the Email Protection signup") + public static let emailSignupExitEarlyActionExit = NSLocalizedString("email.signup-prompt.alert.exit", value: "Exit Setup", comment: "Option to exit the Email Protection signup") - - - - + public static let backButtonTitle = NSLocalizedString("navbar.back-button.title", value:"Back", comment: "Title for back button in navigation bar") + public static let nextButtonTitle = NSLocalizedString("navbar.next-button.title", value:"Next", comment: "Title for next button in navigation bar to progress forward") // MARK: Omnibar - + public static let omnibarNotificationCookiesManaged = NSLocalizedString("omnibar.notification.cookies-managed", value:"Cookies Managed", comment: "Text displayed on notification appearing in the address bar when the browser dismissed the cookie popup automatically rejecting it") public static let omnibarNotificationPopupHidden = NSLocalizedString("omnibar.notification.popup-hidden", value:"Pop-up Hidden", comment: "Text displayed on notification appearing in the address bar when the browser hides a cookie popup") @@ -809,4 +821,5 @@ In addition to the details entered into this form, your app issue report will co // MARK: Errors static let unknownErrorTryAgainMessage = NSLocalizedString("error.unknown.try.again", value: "An unknown error has occurred", comment: "Generic error message on a dialog for when the cause is not known.") + } diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index b42ba64bc6..4c76c1b984 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Стартирано изтегляне на %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Защита на имейл"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Отмени"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Генериране на личен Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Генериране на личен Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Блокиране на имейл тракерите и скриване на адреса"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Изберете имейл адрес"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Блокиране на имейл тракери"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Блокирайте имейл тракерите с Duck адрес"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Блокирайте имейл тракерите и скрийте своя адрес"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Продължаване с настройката"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Изход от настройката"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Ако излезете сега, Вашият Duck Address няма да бъде запазен!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Не показвай повече"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Защита на моя имейл"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Създайте уникален, произволен адрес, който премахва скритите тракери и препраща имейлите към пощенската Ви кутия."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Скрийте имейл адреса си и\nблокирайте тракерите"; + /* Empty list state placholder */ "empty.bookmarks" = "Все още няма добавени отметки"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Меню за сърфиране"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Обратно"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Следващ"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Готово"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 51469176a8..5fcfacbdc4 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Stahování souboru %@ bylo zahájeno"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Ochrana e-mailu"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Zrušit"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Vygenerovat soukromou adresu Duck"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Vygenerovat soukromou adresu Duck"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokuj e-mailové trackery a skryj svou adresu"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Vyber e-mailovou adresu"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokuj e-mailové trackery"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokování e-mailových trackerů pomocí adresy Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokování trackerů v e-mailu a skrytí adresy"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Pokračovat v nastavení"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Ukončit nastavení"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Pokud teď odejdeš, tvoje adresa Duck Address se neuloží."; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Znovu neukazovat"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Chránit můj e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Vytvoř si jedinečnou, náhodnou adresu, která bude odstraňovat skryté trackery a přeposílat e-maily do tvé schránky."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Skryj svůj e-mail\na blokuj trackery"; + /* Empty list state placholder */ "empty.bookmarks" = "Zatím nebyly přidány žádné záložky."; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Procházení nabídky"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Zpět"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Další"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Hotovo"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index da49be4967..a1f1e6f870 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Download startet for %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-mailbeskyttelse"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Annullér"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Opret privat Duck-adresse"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Opret privat Duck-adresse"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Bloker e-mailtrackere og skjul adresse"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Vælg e-mailadresse"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokerer e-mailtrackere"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokér e-mailtrackere med en Duck-adresse"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Bloker e-mailtrackere, og skjul din adresse"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Fortsæt opsætning"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Afslut opsætning"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Hvis du afslutter nu, vil din Duck Address ikke blive gemt!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Vis ikke igen"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Beskyt min e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Opret en unik, tilfældig adresse, der også fjerner skjulte trackere og videresender e-mails til din indbakke."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Skjul din e-mail og \n bloker trackere"; + /* Empty list state placholder */ "empty.bookmarks" = "Ingen bogmærker tilføjet endnu"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Browsingmenu"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Tilbage"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Næste"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Færdig"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 19ec945465..348510eeda 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Download von %@ gestartet"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-Mail-Schutz"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Abbrechen"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Private Duck-Adresse generieren"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Private Duck-Adresse generieren"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "E-Mail-Tracker blockieren & Adresse verbergen"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "E-Mail-Adresse auswählen"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "E-Mail-Tracker blockieren"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "E-Mail-Tracker mit einer Duck-Adresse blockieren"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "E-Mail-Tracker blockieren und deine Adresse verbergen"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Einrichtung fortsetzen"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Einrichtung beenden"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Wenn du jetzt beendest, wird deine Duck Address nicht gespeichert!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Nicht erneut anzeigen"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Meine E-Mail-Adresse schützen"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Einmalige, zufällige Adresse erstellen, die versteckte Tracker entfernt und E-Mails an deinen Posteingang weiterleitet."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "E-Mail-Adresse verbergen und Tracker blockieren"; + /* Empty list state placholder */ "empty.bookmarks" = "Noch keine Lesezeichen hinzugefügt"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Browsing-Optionen"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Zurück"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Weiter"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Fertig"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index a025dcaf7a..9500c6ebcb 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Άρχισε η λήψη για το αρχείο %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Προστασία email"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Ακύρωση"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Δημιουργήστε ιδιωτική Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Δημιουργήστε ιδιωτική Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Αποκλεισμός εφαρμογών παρακολούθησης email και απόκρυψη διεύθυνσης"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Επιλογή διεύθυνσης email"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Αποκλεισμός εφαρμογών παρακολούθησης email"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Αποκλείστε εφαρμογές παρακολούθησης email με μια Διεύθυνση Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Αποκλείστε εφαρμογές παρακολούθησης email και αποκρύψτε τη διεύθυνσή σας"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Συνέχιση εγκατάστασης"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Έξοδος από την εγκατάσταση"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Εάν εξέλθετε τώρα, η Duck Address δεν θα αποθηκευτεί!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Να μην εμφανιστεί ξανά"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Προστασία του email μου"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Δημιουργήστε μια μοναδική, τυχαία διεύθυνση η οποία αφαιρεί επίσης κρυφές εφαρμογές παρακολούθησης και προωθεί email στα εισερχόμενά σας."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Απόκρυψη του email σας και \n αποκλεισμός εφαρμογών παρακολούθησης"; + /* Empty list state placholder */ "empty.bookmarks" = "Δεν προστέθηκαν σελιδοδείκτες ακόμα"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Μενού περιήγησης"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Πίσω"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Επόμενο"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Ολοκληρώθηκε"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 2cbb744d4f..9533c9651f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -922,12 +922,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Download started for %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Email Protection"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Cancel"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generate Private Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generate Private Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Block email trackers & hide address"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Select email address"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Block email trackers"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Block email trackers with a Duck Address"; @@ -952,6 +967,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Block email trackers and hide your address"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Continue Setup"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Exit Setup"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "If you exit now, your Duck Address will not be saved!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Don’t Show Again"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Protect My Email"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Create a unique, random address that also removes hidden trackers and forwards email to your inbox."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Hide Your Email and\nBlock Trackers"; + /* Empty list state placholder */ "empty.bookmarks" = "No bookmarks added yet"; @@ -1300,6 +1336,12 @@ https://duckduckgo.com/mac"; /* No comment provided by engineer. */ "menu.button.hint" = "Browsing Menu"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Back"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Next"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Done"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 2038a3b8c8..3e65ef534d 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Descarga de %@ iniciada"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Protección del correo electrónico"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Cancelar"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generar Duck Address privada"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generar Duck Address privada"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Bloquear rastreadores de correo electrónico y ocultar dirección"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Seleccionar dirección de correo electrónico"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Bloquear rastreadores de correo electrónico"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Bloquea los rastreadores de correo electrónico con una dirección de Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Bloquea los rastreadores de correo electrónico y oculta tu dirección"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Continuar con la configuración"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Salir de Configuración"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Si sales ahora, no se guardará tu Duck Address."; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "No volver a mostrar"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Proteger mi correo electrónico"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Crea una dirección aleatoria única que también elimine los rastreadores ocultos y reenvíe el correo electrónico a tu bandeja de entrada."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Ocultar tu correo electrónico y\nbloquear rastreadores"; + /* Empty list state placholder */ "empty.bookmarks" = "Aún no se han añadido marcadores"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Menú de navegación"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Volver"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Siguiente"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Hecho"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index 4477ad5b20..e036ea52fd 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "%@ allalaadimine algas"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-posti kaitse"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Tühista"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Loo privaatne Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Loo privaatne Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokeeri e-posti jälgijad ja peida aadress"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Vali e-posti aadress"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokeeri e-posti jälgijad"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokeerige e-posti jälgijad Ducki aadressiga"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokeeri meilijälgurid ja peida oma aadress"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Jätka seadistamist"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Välju seadistusest"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Kui sa praegu väljud, siis sinu Duck Addressi ei salvestata!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Ära näita uuesti"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Kaitse minu e-posti aadressi"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Loo unikaalne, juhuslik aadress, mis eemaldab ka varjatud jäglijad ja edastab e-kirjad sinu postkasti."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Peida oma e-post ja\nblokeeri jälgijad"; + /* Empty list state placholder */ "empty.bookmarks" = "Järjehoidjaid pole veel lisatud"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Sirvimismenüü"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Tagasi"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Järgmine"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Valmis"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index 5b34f94c8c..e458219cd0 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Tiedoston %@ lataus aloitettu"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Sähköpostisuojaus"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Peruuta"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Luo yksityinen Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Luo yksityinen Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Estä sähköpostin seurantaohjelmat ja piilota osoite"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Valitse sähköpostiosoite"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Estä sähköpostin seurantaohjelmat"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Estä sähköpostiseuranta Duck-osoitteella"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Estä sähköpostiseuraajat ja piilota osoitteesi"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Jatka asennusta"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Poistu asennuksesta"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Jos poistut nyt, Duck Address -osoitettasi ei tallenneta!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Älä näytä uudelleen"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Suojaa sähköpostini"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Luo yksilöllinen, satunnainen osoite, joka lisäksi poistaa piilotetut seurantaohjelmat ja välittää sähköpostin omaan postilaatikkoosi."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Piilota sähköpostisi ja\nEstä seurantaohjelmat"; + /* Empty list state placholder */ "empty.bookmarks" = "Kirjanmerkkejä ei ole vielä lisätty"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Selausvalikko"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Takaisin"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Seuraava"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Valmis"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 0eeb3f5d3f..6c227441e1 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Début du téléchargement pour %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Protection des e-mails"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Annuler"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Générer une Duck Address privée"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Générer une Duck Address privée"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Bloquer les traqueurs d'e-mails et masquer l'adresse"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Sélectionnez une adresse e-mail"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Bloquer les traqueurs d'e-mails"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Bloquer les traqueurs d'e-mails avec une adresse Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Bloquez les traqueurs d'e-mails et masquez votre adresse"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Poursuivre la configuration"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Quitter la configuration"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Si vous quittez cette page maintenant, votre Duck Address ne sera pas enregistrée !"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Ne plus afficher"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Protéger mon adresse e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Créez une adresse unique et aléatoire qui supprime les traqueurs masqués et transfère les e-mails vers votre boîte de réception."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Masquez votre adresse e-mail et bloquez les traqueurs"; + /* Empty list state placholder */ "empty.bookmarks" = "Aucun signet ajouté pour le moment"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Menu de navigation"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Retour"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Suivant"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Terminé"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 3e366c028b..8055f70a2c 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Preuzimanje je počelo za %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Zaštita e-pošte"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Otkaži"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generiraj privatnu adresu Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generiraj privatnu adresu Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokiraj praćenje e-pošte i sakrij adresu"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Odabir adrese e-pošte"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokiranje alata za praćenje e-pošte"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokirajte praćenje e-pošte s Duck adresom"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokiraj programe za praćenje e-pošte i sakrij svoju adresu"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Nastavi s postavljanjem"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Izađi iz postavljanja"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Ako izađeš sada, tvoja adresa Duck Address neće biti spremljena!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Ne prikazuj ponovno"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Zaštiti moju e-poštu"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Izradi jedinstvenu, nasumičnu adresu koja također uklanja skrivene alate za praćenje (\"tragače\") i prosljeđuje e-poštu u tvoj sandučić za pristiglu poštu."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Sakrij svoju e-poštu i \n blokiraj tragače"; + /* Empty list state placholder */ "empty.bookmarks" = "Još nema dodanih knjižnih oznaka"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Izbornik pregledavanja"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Natrag"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Dalje"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Gotovo"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 0f8341fd1f..2573062e8e 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "A letöltés elindult: %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-mail védelem"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Mégsem"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Privát Duck-cím generálása"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Privát Duck-cím létrehozása"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "E-mail-nyomkövetők blokkolása, és a cím elrejtése"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "E-mail-cím kiválasztása"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "E-mail-nyomkövetők blokkolása"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "E-mail nyomkövetők letiltása Duck-címmel"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "E-mail nyomkövetők letiltása, és a cím elrejtése"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Beállítás folytatása"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Kilépés a beállításokból"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Ha most kilépsz, a Duck-cím nem lesz mentve!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Ne jelenjen meg újra"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "E-mail védelme"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Hozz létre egy egyedi, véletlenszerű címet, amely eltávolítja a rejtett nyomkövetőket is, és a postafiókodba továbbítja az e-maileket."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "E-mail elrejtése és\nnyomkövetők blokkolása"; + /* Empty list state placholder */ "empty.bookmarks" = "Még nincsenek könyvjelzők hozzáadva"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Tallózás a menüben"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Vissza"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Következő"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Kész"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index 35ac7b1ce8..bde7ab56be 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Download di %@ avviato"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Protezione email"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Annulla"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Genera Duck Address privato"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Genera Duck Address privato"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blocca i sistemi di tracciamento e-mail e nascondi il tuo indirizzo"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Seleziona l'indirizzo e-mail"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blocca i sistemi di tracciamento e-mail"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blocca i sistemi di tracciamento e-mail con un indirizzo Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blocca i sistemi di tracciamento delle email e nascondi il tuo indirizzo"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Prosegui la configurazione"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Esci dalla configurazione"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Se esci ora, il tuo Duck Address non verrà salvato!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Non mostrare più"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Proteggi la mia e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Crea un indirizzo univoco e casuale che rimuove anche i sistemi di tracciamento nascosti e inoltra le e-mail alla tua casella di posta."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Nascondi il tuo indirizzo e-mail e \n blocca i sistemi di tracciamento"; + /* Empty list state placholder */ "empty.bookmarks" = "Non è ancora stato aggiunto nessun segnalibro"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Menu di navigazione"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Indietro"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Successivo"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Fatto"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index cd71f47d90..c6184eae1a 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "%@ atsiusntimas prasidėjo."; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "El. pašto apsauga"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Atšaukti"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generuoti privatų „Duck“ adresą"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generuoti privatų „Duck“ adresą"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokuoti el. pašto stebėjimo priemones ir slėpti adresą"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Pasirinkite el. pašto adresą"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokuoti el. pašto sekimo priemones"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokuoti el. pašto adresų stebėjimo priemones „Duck“ adresams"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokuokite el. laiškų sekimo priemones ir paslėpkite savo adresą"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Tęsti sąranką"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Išeiti iš sąrankos"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Jei išeisite dabar, jūsų „Duck“ adresas nebus išsaugotas!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Daugiau nerodyti"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Apsaugoti mano el. paštą"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Sukurkite unikalų atsitiktinį adresą, kuriuo taip pat pašalinamos slaptos sekimo priemonės ir el. laiškai persiunčiami į pašto dėžutę."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Paslėpkite savo el. paštą ir \n blokuokite stebėjimo priemones"; + /* Empty list state placholder */ "empty.bookmarks" = "Žymių dar nepridėta"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Naršymo meniu"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Atgal"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Kitas"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Atlikta"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index e16d3af7a4..70f7227972 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "%@ lejupielāde sākta"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-pasta aizsardzība"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Atcelt"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Ģenerēt privātu Duck adresi"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Ģenerēt privātu Duck adresi"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Bloķē e-pasta izsekotājus un paslēp adresi"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Izvēlies e-pasta adresi"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Bloķē e-pasta izsekotājus"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Bloķē e-pasta izsekotājus ar Duck adresi"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Bloķē e-pasta izsekotājus un paslēp savu adresi"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Turpināt iestatīšanu"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Iziet no iestatīšanas"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Ja tagad iziesi, tava Duck adrese netiks saglabāta!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Turpmāk nerādīt"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Aizsargāt manu e-pastu"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Izveido unikālu, nejauši izvēlētu adresi, kas arī aizvāc slēptos izsekotājus un pārsūta e-pastus uz tavu pastkastīti."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Paslēp savu e-pastu un\nbloķē izsekotājus"; + /* Empty list state placholder */ "empty.bookmarks" = "Vēl nav pievienota neviena grāmatzīme"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Pārlūkošanas izvēlne"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Atpakaļ"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Nākamais"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Gatavs"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 8d95dcf8e3..07082fbf6d 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Nedlasting av %@ har startet"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-postbeskyttelse"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Avbryt"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generer privat Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generer privat Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokker e-postsporere og skjul adresse"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Velg e-postadresse"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokker e-postsporere"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokker e-postsporere med en Duck-adresse"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokker e-postsporere og skjul adressen din"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Fortsett oppsett"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Avslutt oppsett"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Hvis du avslutter nå, blir ikke din Duck Address lagret!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Ikke vis dette igjen"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Beskytt e-postadressen min"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Opprett en unik, tilfeldig adresse som også fjerner skjulte sporere og videresender e-post til innboksen din."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Skjul e-postadressen din og\nblokker sporere"; + /* Empty list state placholder */ "empty.bookmarks" = "Ingen bokmerker lagt til ennå"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Alternativer"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Tilbake"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Neste"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Ferdig"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index dafe521a76..4ade938659 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Downloaden gestart voor %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-mailbescherming"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Annuleren"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Privé-Duck Address genereren"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Privé-Duck Address genereren"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "E-mailtrackers blokkeren en adres verbergen"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "E-mailadres selecteren"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "E-mailtrackers blokkeren"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "E-mailtrackers met een Duck-adres blokkeren"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokkeer e-mailtrackers en verberg je adres"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Doorgaan met instellen"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Instellen beëindigen"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Als je nu afsluit, wordt je Duck Address niet opgeslagen!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Niet meer weergeven"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Mijn e-mailadres beschermen"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Maak een uniek, willekeurig adres dat ook verborgen trackers verwijdert en e-mails doorstuurt naar je inbox."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Je e-mailadres verbergen en\n trackers blokkeren"; + /* Empty list state placholder */ "empty.bookmarks" = "Nog geen bladwijzers toegevoegd"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Optiemenu"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Terug"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Volgende"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Klaar"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index eb7810a81f..a0cbfc1983 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Rozpoczęto pobieranie %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Ochrona poczty e-mail"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Anuluj"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Wygeneruj prywatny adres Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Wygeneruj prywatny adres Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Zablokuj mechanizmy śledzące pocztę e-mail i ukryj adres"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Wybierz adres e-mail"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokuj mechanizmy śledzące pocztę e-mail"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokuj skrypty śledzące wiadomości e-mail za pomocą adresu Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Zablokuj skrypty śledzące pocztę e-mail i ukryj swój adres"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Kontynuuj konfigurację"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Wyjdź z konfiguracji"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Jeśli teraz wyjdziesz, Twój Duck Address nie zostanie zapisany!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Nie pokazuj więcej"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Chroń moją pocztę e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Utwórz unikalny, losowy adres, który usuwa ukryte mechanizmy śledzące i przekazuje wiadomości e-mail do Twojej skrzynki odbiorczej."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Ukryj swój adres e-mail i\nblokuj skrypty śledzące"; + /* Empty list state placholder */ "empty.bookmarks" = "Nie dodano jeszcze żadnych zakładek"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Dostępne opcje"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Wstecz"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Dalej"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Gotowe"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index fb24dd8910..79fa186a0d 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Transferência iniciada de %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Proteção de e-mail"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Cancelar"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Gerar um Duck Address Privado"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Gerar um Duck Address Privado"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Bloquear rastreadores de e-mail e ocultar endereço"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Seleciona o endereço de e-mail"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Bloquear rastreadores de e-mail"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Bloqueie os rastreadores de e-mail com um endereço do Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Bloqueie rastreadores de e-mail e oculte o seu endereço."; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Continuar configuração"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Sair da instalação"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Se saíres agora, o teu Duck Address não será guardado!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Não mostrar novamente"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Proteger o meu e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Cria um endereço aleatório exclusivo que também remove rastreadores escondidos e encaminha o e-mail para a tua caixa de entrada."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Ocultar o teu e-mail e\nbloquear rastreadores"; + /* Empty list state placholder */ "empty.bookmarks" = "Ainda não foram adicionados marcadores"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Menu de navegação"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Retroceder"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Seguinte"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Feito"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 89816f2c3e..9de05a6078 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Descărcarea a început pentru %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Protecția comunicațiilor prin e-mail"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Renunță"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generează o Duck Address privată"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generează o Duck Address privată"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blochează tehnologiile de urmărire din e-mailuri și ascunde adresa"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Selectează adresa de e-mail"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blochează tehnologiile de urmărire din e-mail"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blochează instrumentele de urmărire a e-mailului cu o adresă Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blochează tehnologiile de urmărire prin e-mail și ascunde-ți adresa"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Continuă configurarea"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Ieși din configurare"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Dacă ieși acum, Duck Address nu va fi salvată!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Nu mai afișa din nou"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Protejează-mi adresa de e-mail"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Creează o adresă unică, aleatorie, care elimină și tehnologiile de urmărire ascunse și redirecționează e-mailurile către căsuța ta de inbox."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Ascunde-ți e-mailul și blochează tehnologiile de urmărire"; + /* Empty list state placholder */ "empty.bookmarks" = "Nu s-a adăugat niciun semn de carte"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Meniul de navigare"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Înapoi"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Următorul"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Terminat"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index a15ecfd28d..3c22edb920 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Началась загрузка файла %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Защита электронной почты"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Отменить"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Создать адрес на Duck"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Создать адрес на Duck"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Скрывает ваш адрес и блокирует почтовые трекеры"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Выберите адрес электронной почты"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Для блокировки почтовых трекеров"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Блокировать почтовые трекеры с адресом Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Блокировка почтовых трекеров и скрытие адреса"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Продолжить настройку"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Выйти из настройки"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Если вы прервете настройку, ваш адрес Duck Address не сохранится!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Больше не показывать"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Защитить почту"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Создайте уникальный случайный адрес, который также удалит скрытые трекеры и перенаправит электронную почту на ваш ящик."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Скрытие адреса почты\nи блокировка трекеров"; + /* Empty list state placholder */ "empty.bookmarks" = "Закладок пока нет"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Меню браузера"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Назад"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Далее"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Готово"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 568d9dac97..25cde04a4c 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "%@ – začalo sa sťahovanie"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Ochrana e-mailu"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Zrušiť"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generovať súkromnú adresu Duck"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generovať súkromnú adresu Duck"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokujte sledovače e-mailov a skryte adresu"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Vyberte e-mailovú adresu"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokuje e-mailové sledovače"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokujte e-mailové sledovače (trackery) s adresou Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Zablokujte nástroje na sledovanie e‑mailov a skryte vašu adresu"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Pokračovať v nastavovaní"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Odísť z nastavení"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Ak teraz skončíte, vaša Duck Address sa neuloží!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Nabudúce už nezobrazovať"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Ochrana môjho e-mailu"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Vytvorte si náhodnú jedinečnú adresu, ktorá odstráni aj skryté sledovacie prvky a prepošle e-maily do vašej schránky."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Skryte svoj e-mail\na blokujte sledovače"; + /* Empty list state placholder */ "empty.bookmarks" = "Zatiaľ neboli pridané žiadne záložky"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Ponuka prehliadania"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Späť"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Ďalšie"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Hotovo"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index a4cf1fd000..f008a7fdb0 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Prenos za %@ se je začel"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "Zaščita e-pošte"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Prekliči"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Ustvarjanje zasebnega naslova Duck"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Ustvarjanje zasebnega naslova Duck"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blokirajte sledilnike e-pošte in skrijte naslov"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Izberite e-poštni naslov"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blokirajte sledilnike e-pošte"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blokiraj sledilnike e-pošte z naslovom Duck"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blokirajte sledilnike e-pošte in skrijte svoj naslov"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Nadaljujte nastavitev"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Izhod iz nastavitev"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Če zaprete zdaj, vaš naslov Duck Address ne bo shranjen."; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Ne prikaži več"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Zaščiti mojo e-pošto"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Ustvarite edinstven, naključen naslov, ki odstrani tudi skrite sledilnike in posreduje e-pošto v vaš e-poštni predal."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Skrijte svojo e-pošto in \nblokirajte sledilnike"; + /* Empty list state placholder */ "empty.bookmarks" = "Zaznamki še niso dodani"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Brskanje po meniju"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Nazaj"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Naslednji"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Končano"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index d92650bd2c..da2bc2485e 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Nerladdning påbörjad för %@"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-postskydd"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "Avbryt"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Generera privat Duck Address"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Generera privat Duck Address"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "Blockera e-postspårare och dölj din adress"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "Välj e-postadress"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "Blockera e-postspårare"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Blockera e-postspårare med en Duck-adress"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "Blockera e-postspårare och dölj din adress"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Fortsätt inställningen"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Avsluta inställningen"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Om du avslutar nu kommer din Duck Address inte att sparas!"; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Visa inte igen"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "Skydda min e-postadress"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Skapa en unik, slumpmässig adress som också tar bort dolda spårare och vidarebefordrar e-post till din inkorg."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "Dölj din e-postadress och\nblockera spårare"; + /* Empty list state placholder */ "empty.bookmarks" = "Inga bokmärken har lagts till ännu"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Bläddra-meny"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Tillbaka"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Nästa"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Klart"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 25f774eb76..0cabb0273f 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -658,12 +658,27 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "%@ için indirme başlatıldı"; +/* Email protection service offered by DuckDuckGo */ +"email-protection" = "E-posta Koruması"; + /* Cancel option for the email alias alert */ "email.aliasAlert.decline" = "İptal"; /* Option for generating a private email address */ "email.aliasAlert.generatePrivateAddress" = "Özel Duck Address Oluştur"; +/* Option for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress" = "Özel Duck Address Oluştur"; + +/* Subtitle for generating a private email address */ +"email.aliasAlert.prompt.generatePrivateAddress.subtitle" = "E-posta izleyicileri engelleyin ve adresi gizleyin"; + +/* Title for the email alias selection prompt */ +"email.aliasAlert.prompt.title" = "E-posta adresini seçin"; + +/* Subtitle for choosing primary user email address */ +"email.aliasAlert.prompt.useUserAddress.subtitle" = "E-posta izleyicileri engelleyin"; + /* Title for the email alias selection alert */ "email.aliasAlert.title" = "Duck Adresi ile e-posta izleyicileri engelleyin"; @@ -688,6 +703,27 @@ /* Subtitle for the email settings cell */ "email.settings.subtitle" = "E-posta izleyicileri engelleyin ve adresinizi gizleyin"; +/* Option to continue the Email Protection signup */ +"email.signup-prompt.alert.continue" = "Kuruluma Devam Et"; + +/* Option to exit the Email Protection signup */ +"email.signup-prompt.alert.exit" = "Kurulumdan Çık"; + +/* Title for exiting the Email Protection signup early alert */ +"email.signup-prompt.alert.title" = "Şimdi çıkarsanız Duck Address kaydedilmez."; + +/* Button title choosing not to sign up for email protection and not to be prompted again */ +"email.signup-prompt.do-not-signup-button.cta" = "Bir Daha Gösterme"; + +/* Button title choosing to sign up for email protection */ +"email.signup-prompt.signup-button.cta" = "E-postamı Koru"; + +/* Subtitle for prompt to sign up for email protection */ +"email.signup-prompt.subtitle" = "Gizli izleyicileri de kaldıran ve e-postaları gelen kutunuza ileten benzersiz, rastgele bir adres oluşturun."; + +/* Title for prompt to sign up for email protection */ +"email.signup-prompt.title" = "E-postanızı Gizleyin ve \n İzleyicileri Engelleyin"; + /* Empty list state placholder */ "empty.bookmarks" = "Henüz yer imi eklenmedi"; @@ -1015,6 +1051,12 @@ /* No comment provided by engineer. */ "menu.button.hint" = "Göz Atma Menüsü"; +/* Title for back button in navigation bar */ +"navbar.back-button.title" = "Geri"; + +/* Title for next button in navigation bar to progress forward */ +"navbar.next-button.title" = "Sonraki"; + /* Finish editing bookmarks button */ "navigation.title.done" = "Bitti"; From 4e77c16b08e5738fce6403418f65b29ae6a989e5 Mon Sep 17 00:00:00 2001 From: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Date: Thu, 24 Aug 2023 07:50:20 -0500 Subject: [PATCH 08/17] Update BSK with autofill 8.2.0 (#1937) Task/Issue URL: https://app.asana.com/0/1205338112741817/1205338112741817 Autofill Release: https://github.com/duckduckgo/duckduckgo-autofill/releases/tag/8.2.0 BSK PR: duckduckgo/BrowserServicesKit#475 Description Updates Autofill to version 8.2.0. Autofill 8.2.0 release notes What's Changed Updates for iOS in-context signup support by @alistairjcbrown in Updates for iOS in-context signup support duckduckgo-autofill#347 Trigger tooltip option on pointer up by @alistairjcbrown in Trigger tooltip option on pointer up duckduckgo-autofill#333 Full Changelog: duckduckgo/duckduckgo-autofill@8.1.2...8.2.0 From ca1e14dc681753d89eb2b8fb1b8a36e7ca76371a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 24 Aug 2023 17:48:24 +0200 Subject: [PATCH 09/17] Update Favicons to include 2 letters + colors (macOS parity) (#1927) Task/Issue URL: https://app.asana.com/0/1142021229838617/1205285857217353/f Description: Updates the favicon generator to support multiple characters. It also makes sure we pass random colors for fake icon generation (which was not working before) --- .../AutofillListItemTableViewCell.swift | 2 +- .../AutofillLoginDetailsHeaderView.swift | 2 +- .../AutofillLoginDetailsViewModel.swift | 9 +++- .../AutofillLoginListItemViewModel.swift | 13 ++++-- DuckDuckGo/FaviconViewModel.swift | 8 ++-- DuckDuckGo/FaviconsHelper.swift | 45 ++++++++++++------- DuckDuckGo/UIImageViewExtension.swift | 4 +- 7 files changed, 53 insertions(+), 30 deletions(-) diff --git a/DuckDuckGo/AutofillListItemTableViewCell.swift b/DuckDuckGo/AutofillListItemTableViewCell.swift index 8f51311c97..f531ff4aa6 100644 --- a/DuckDuckGo/AutofillListItemTableViewCell.swift +++ b/DuckDuckGo/AutofillListItemTableViewCell.swift @@ -126,7 +126,7 @@ class AutofillListItemTableViewCell: UITableViewCell { private func setupContentView(with item: AutofillLoginListItemViewModel) { titleLabel.text = item.title subtitleLabel.text = item.subtitle - iconImageView.loadFavicon(forDomain: item.account.domain, usingCache: .fireproof, preferredFakeFaviconLetter: item.preferredFaviconLetter) + iconImageView.loadFavicon(forDomain: item.account.domain, usingCache: .fireproof, preferredFakeFaviconLetters: item.preferredFaviconLetters) } override func layoutSubviews() { diff --git a/DuckDuckGo/AutofillLoginDetailsHeaderView.swift b/DuckDuckGo/AutofillLoginDetailsHeaderView.swift index 0db46ba19f..c69862cea1 100644 --- a/DuckDuckGo/AutofillLoginDetailsHeaderView.swift +++ b/DuckDuckGo/AutofillLoginDetailsHeaderView.swift @@ -28,7 +28,7 @@ struct AutofillLoginDetailsHeaderView: View { HStack(spacing: Constants.horizontalStackSpacing) { FaviconView(viewModel: FaviconViewModel(domain: viewModel.domain, cacheType: .fireproof, - preferredFakeFaviconLetter: viewModel.preferredFakeFaviconLetter)) + preferredFakeFaviconLetters: viewModel.preferredFakeFaviconLetters)) .scaledToFit() .frame(width: Constants.imageSize, height: Constants.imageSize) .accessibilityHidden(true) diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 21d1c7fd34..13ba6d835a 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -455,13 +455,18 @@ final class AutofillLoginDetailsHeaderViewModel: ObservableObject { @Published var title: String = "" @Published var subtitle: String = "" @Published var domain: String = "" - @Published var preferredFakeFaviconLetter: String? + @Published var preferredFakeFaviconLetters: String? func updateData(with account: SecureVaultModels.WebsiteAccount, tld: TLD, autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, autofillDomainNameUrlSort: AutofillDomainNameUrlSort) { self.title = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher) self.subtitle = UserText.autofillLoginDetailsLastUpdated(for: (dateFormatter.string(from: account.lastUpdated))) self.domain = account.domain ?? "" - self.preferredFakeFaviconLetter = account.firstTLDLetter(tld: tld, autofillDomainNameUrlSort: autofillDomainNameUrlSort) + + // Update favicon + let accountName = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher) + let accountTitle = (account.title?.isEmpty == false) ? account.title! : "#" + self.preferredFakeFaviconLetters = tld.eTLDplus1(accountName) ?? accountTitle + } } diff --git a/DuckDuckGo/AutofillLoginListItemViewModel.swift b/DuckDuckGo/AutofillLoginListItemViewModel.swift index cd68bd66e2..098a6176b4 100644 --- a/DuckDuckGo/AutofillLoginListItemViewModel.swift +++ b/DuckDuckGo/AutofillLoginListItemViewModel.swift @@ -25,12 +25,18 @@ import Common final class AutofillLoginListItemViewModel: Identifiable, Hashable { @Published var image = UIImage(systemName: "globe")! + var preferredFaviconLetters: String { + let accountName = self.account.name(tld: tld, autofillDomainNameUrlMatcher: urlMatcher) + let accountTitle = (account.title?.isEmpty == false) ? account.title! : "#" + return tld.eTLDplus1(accountName) ?? accountTitle + } + let account: SecureVaultModels.WebsiteAccount let title: String let subtitle: String - let preferredFaviconLetter: String? let id = UUID() let tld: TLD + let urlMatcher: AutofillDomainNameUrlMatcher internal init(account: SecureVaultModels.WebsiteAccount, tld: TLD, @@ -40,8 +46,7 @@ final class AutofillLoginListItemViewModel: Identifiable, Hashable { self.tld = tld self.title = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher) self.subtitle = account.username ?? "" - self.preferredFaviconLetter = account.firstTLDLetter(tld: tld, autofillDomainNameUrlSort: autofillDomainNameUrlSort) - + self.urlMatcher = autofillDomainNameUrlMatcher fetchImage() } @@ -49,7 +54,7 @@ final class AutofillLoginListItemViewModel: Identifiable, Hashable { FaviconsHelper.loadFaviconSync(forDomain: account.domain, usingCache: .fireproof, useFakeFavicon: true, - preferredFakeFaviconLetter: preferredFaviconLetter) { image, _ in + preferredFakeFaviconLetters: preferredFaviconLetters) { image, _ in if let image = image { self.image = image } else { diff --git a/DuckDuckGo/FaviconViewModel.swift b/DuckDuckGo/FaviconViewModel.swift index 0549b0a4d4..2b9ee92dd7 100644 --- a/DuckDuckGo/FaviconViewModel.swift +++ b/DuckDuckGo/FaviconViewModel.swift @@ -26,17 +26,17 @@ final class FaviconViewModel { private let domain: String private let useFakeFavicon: Bool private let cacheType: Favicons.CacheType - private let preferredFaviconLetter: String? + private let preferredFaviconLetters: String? internal init(domain: String, useFakeFavicon: Bool = true, cacheType: Favicons.CacheType = .tabs, - preferredFakeFaviconLetter: String? = nil) { + preferredFakeFaviconLetters: String? = nil) { self.domain = domain self.useFakeFavicon = useFakeFavicon self.cacheType = cacheType - self.preferredFaviconLetter = preferredFakeFaviconLetter + self.preferredFaviconLetters = preferredFakeFaviconLetters loadFavicon() } @@ -44,7 +44,7 @@ final class FaviconViewModel { FaviconsHelper.loadFaviconSync(forDomain: domain, usingCache: cacheType, useFakeFavicon: useFakeFavicon, - preferredFakeFaviconLetter: preferredFaviconLetter) { image, _ in + preferredFakeFaviconLetters: preferredFaviconLetters) { image, _ in if let image = image { self.image = image } diff --git a/DuckDuckGo/FaviconsHelper.swift b/DuckDuckGo/FaviconsHelper.swift index f52e75e142..2d0fafe810 100644 --- a/DuckDuckGo/FaviconsHelper.swift +++ b/DuckDuckGo/FaviconsHelper.swift @@ -20,15 +20,18 @@ import UIKit import Core import Kingfisher +import Common struct FaviconsHelper { - + + private static let tld: TLD = AppDependencyProvider.shared.storageCache.tld + static func loadFaviconSync(forDomain domain: String?, usingCache cacheType: Favicons.CacheType, useFakeFavicon: Bool, - preferredFakeFaviconLetter: String? = nil, + preferredFakeFaviconLetters: String? = nil, completion: ((UIImage?, Bool) -> Void)? = nil) { - + func complete(_ image: UIImage?) { var fake = false var resultImage: UIImage? @@ -38,7 +41,8 @@ struct FaviconsHelper { } else if useFakeFavicon, let domain = domain { fake = true resultImage = Self.createFakeFavicon(forDomain: domain, - preferredFakeFaviconLetter: preferredFakeFaviconLetter) + backgroundColor: UIColor.forDomain(domain), + preferredFakeFaviconLetters: preferredFakeFaviconLetters) } completion?(resultImage, fake) } @@ -92,12 +96,13 @@ struct FaviconsHelper { size: CGFloat = 192, backgroundColor: UIColor = UIColor.greyishBrown2, bold: Bool = true, - preferredFakeFaviconLetter: String? = nil) -> UIImage? { + preferredFakeFaviconLetters: String? = nil, + letterCount: Int = 2) -> UIImage? { let cornerRadius = size * 0.125 - let fontSize = size * 0.76 - let imageRect = CGRect(x: 0, y: 0, width: size, height: size) + let padding = size * 0.16 + let labelFrame = CGRect(x: padding, y: padding, width: imageRect.width - (2 * padding), height: imageRect.height - (2 * padding)) let renderer = UIGraphicsImageRenderer(size: imageRect.size) let icon = renderer.image { imageContext in @@ -106,21 +111,29 @@ struct FaviconsHelper { context.setFillColor(backgroundColor.cgColor) context.addPath(CGPath(roundedRect: imageRect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)) context.fillPath() - - let label = UILabel(frame: imageRect) - label.font = bold ? UIFont.boldAppFont(ofSize: fontSize) : UIFont.appFont(ofSize: fontSize) + + let label = UILabel(frame: labelFrame) + label.numberOfLines = 1 + label.adjustsFontSizeToFitWidth = true + label.minimumScaleFactor = 0.1 + label.baselineAdjustment = .alignCenters + label.font = bold ? UIFont.boldAppFont(ofSize: size) : UIFont.appFont(ofSize: size) label.textColor = UIColor.white label.textAlignment = .center - label.text = preferredFakeFaviconLetter ?? String(domain.droppingWwwPrefix().prefix(1).uppercased()) - label.sizeToFit() - - context.translateBy(x: (imageRect.width - label.bounds.width) / 2.0, - y: (imageRect.height - label.font.ascender) / 2.0 - (label.font.ascender - label.font.capHeight) / 2.0) - + + if let prefferedPrefix = preferredFakeFaviconLetters?.prefix(letterCount).capitalized { + label.text = prefferedPrefix + } else { + label.text = String(tld.eTLDplus1(domain)?.prefix(letterCount) ?? "#").capitalized + } + + context.translateBy(x: padding, y: padding) + label.layer.draw(in: context) } return icon.withRenderingMode(.alwaysOriginal) } + } diff --git a/DuckDuckGo/UIImageViewExtension.swift b/DuckDuckGo/UIImageViewExtension.swift index f4ff5d395c..dc2f7bf4e0 100644 --- a/DuckDuckGo/UIImageViewExtension.swift +++ b/DuckDuckGo/UIImageViewExtension.swift @@ -27,14 +27,14 @@ extension UIImageView { func loadFavicon(forDomain domain: String?, usingCache cacheType: Favicons.CacheType, useFakeFavicon: Bool = true, - preferredFakeFaviconLetter: String? = nil, + preferredFakeFaviconLetters: String? = nil, completion: ((UIImage?, Bool) -> Void)? = nil) { func load() { FaviconsHelper.loadFaviconSync(forDomain: domain, usingCache: cacheType, useFakeFavicon: useFakeFavicon, - preferredFakeFaviconLetter: preferredFakeFaviconLetter) { image, fake in + preferredFakeFaviconLetters: preferredFakeFaviconLetters) { image, fake in self.image = image completion?(image, fake) } From c979ac7adc20f8c17a4c490e06f8819d33585b81 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Thu, 24 Aug 2023 20:08:19 +0100 Subject: [PATCH 10/17] Add hotfixes and coldfixes to the allowed branches for releases (#1944) --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a0819c8ded..82e01ff463 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,11 @@ on: pull_request: branches: - release/** + - hotfix/** + - coldfix/** - '!release/**-' # filter out PRs matching that pattern + - '!hotfix/**-' + - '!coldfix/**-' types: [closed] jobs: From bcd2552495d0562cf809fb9babaf6e8ef1856f9f Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Thu, 24 Aug 2023 20:14:24 +0100 Subject: [PATCH 11/17] Elle/GitHub actions coldfix (#1945) --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82e01ff463..8a23744c96 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,8 @@ jobs: case "${{ github.ref }}" in *release/*) ;; *hotfix/*) ;; - *) echo "👎 Not a release or hotfix branch"; exit 1 ;; + *coldfix/*) ;; + *) echo "👎 Not a release, hotfix, or coldfix branch"; exit 1 ;; esac - name: Register SSH keys for access to certificates From af07186d8b011eb3bd01d23555081f1fe43947ed Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 25 Aug 2023 16:49:19 +0100 Subject: [PATCH 12/17] Add pixels to measure set as default (#1950) Task/Issue URL: https://app.asana.com/0/0/1205353165912634/f Tech Design URL: CC: Description: Adds pixels to measure clicks on the onboarding buttons and wether we've been set as default using opening a URL within the last 7 days as a proxy. Steps to test this PR: Test that clicking on set as default screen buttons fire the correct pixel When performing a search the app or launching the app, it should fire a daily pixel with default_browser parameter set to true or false To set it to true set the app as default then launch a URL (e.g. from a reminder) After confirming the DailyPixel only fires once per day hack the code in SetAsDefaultStatistics.swift so that it's a regular pixel and you will be able to observe the pixel and its parameter After setting default to true, update the Date in the isDefault check to Date.distantFuture and ensure that the pixel sends with default_browser as false --- Core/Pixel.swift | 2 + Core/PixelEvent.swift | 14 ++++- Core/SetAsDefaultStatistics.swift | 55 +++++++++++++++++++ Core/StatisticsLoader.swift | 8 ++- Core/UserDefaultsPropertyWrapper.swift | 3 +- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++ DuckDuckGo/AppDelegate.swift | 1 + ...boardingDefaultBroswerViewController.swift | 6 ++ 8 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 Core/SetAsDefaultStatistics.swift diff --git a/Core/Pixel.swift b/Core/Pixel.swift index fb0f2bf23c..8955f627a7 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -109,6 +109,8 @@ public struct PixelParameters { // Remote messaging public static let message = "message" public static let sheetResult = "success" + + public static let defaultBrowser = "default_browser" } public struct PixelValues { diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 87a490e685..6930f29edd 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -309,6 +309,10 @@ extension Pixel { case lockScreenWidgetVoiceSearch case lockScreenWidgetNewEmail + // MARK: Set as Default + case onboardingSetDefaultOpened + case onboardingSetDefaultSkipped + // MARK: debug pixels case dbCrashDetected @@ -436,6 +440,8 @@ extension Pixel { case invalidPayload(Configuration) + case dailyActiveUser + case emailIncontextPromptDisplayed case emailIncontextPromptConfirmed case emailIncontextPromptDismissed @@ -733,13 +739,17 @@ extension Pixel.Event { case .remoteMessageSecondaryActionClicked: return "m_remote_message_secondary_action_clicked" case .remoteMessageSheet: return "m_remote_message_sheet" - // Lock Screen Widgets + // MARK: Lock Screen Widgets case .lockScreenWidgetNewSearch: return "m_lockscreen_newsearch" case .lockScreenWidgetFavorites: return "m_lockscreen_favorites" case .lockScreenWidgetFireButton: return "m_lockscreen_fire" case .lockScreenWidgetVoiceSearch: return "m_lockscreen_voicesearch" case .lockScreenWidgetNewEmail: return "m_lockscreen_newemail" + // MARK: Set as default measuring + case .onboardingSetDefaultOpened: return "m_onboarding_set-default-opened" + case .onboardingSetDefaultSkipped: return "m_onboarding_set-default-skipped" + // MARK: debug pixels case .dbCrashDetected: return "m_d_crash" @@ -869,6 +879,8 @@ extension Pixel.Event { case .invalidPayload(let configuration): return "m_d_\(configuration.rawValue)_invalid_payload".lowercased() + case .dailyActiveUser: return "m_daily_active_user" + // MARK: - InContext Email Protection case .emailIncontextPromptDisplayed: return "m_email_incontext_prompt_displayed" case .emailIncontextPromptConfirmed: return "m_email_incontext_prompt_confirmed" diff --git a/Core/SetAsDefaultStatistics.swift b/Core/SetAsDefaultStatistics.swift new file mode 100644 index 0000000000..3faa71aa24 --- /dev/null +++ b/Core/SetAsDefaultStatistics.swift @@ -0,0 +1,55 @@ +// +// SetAsDefaultStatistics.swift +// DuckDuckGo +// +// Copyright © 2023 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 + +/// Measuring set as default usage. To be removed mid-end October. +public class SetAsDefaultStatistics { + + @UserDefaultsWrapper(key: .defaultBrowserUsageLastSeen, defaultValue: nil) + var defaultBrowserUsageLastSeen: Date? + + /// We assume we're default if we the app was launched with a URL in the last 7 days + public var isDefault: Bool { + guard let lastSeen = defaultBrowserUsageLastSeen, + let days = Calendar.current.numberOfDaysBetween(lastSeen, and: Date()) else { return false } + return (0...7).contains(days) + } + + public init() { } + + public func openedAsDefault() { + defaultBrowserUsageLastSeen = Date() + } + + public func setAsDefaultOpened() { + Pixel.fire(pixel: .onboardingSetDefaultOpened) + } + + public func setAsDefaultSkipped() { + Pixel.fire(pixel: .onboardingSetDefaultSkipped) + } + + public func fireDailyActiveUser() { + DailyPixel.fire(pixel: .dailyActiveUser, withAdditionalParameters: [ + PixelParameters.defaultBrowser: "\(isDefault)" + ]) + } + +} diff --git a/Core/StatisticsLoader.swift b/Core/StatisticsLoader.swift index b0b3fae304..3f59087d26 100644 --- a/Core/StatisticsLoader.swift +++ b/Core/StatisticsLoader.swift @@ -86,7 +86,9 @@ public class StatisticsLoader { requestInstallStatistics(completion: completion) return } - + + SetAsDefaultStatistics().fireDailyActiveUser() + let configuration = APIRequest.Configuration(url: url) let request = APIRequest(configuration: configuration, urlSession: .session()) @@ -109,7 +111,9 @@ public class StatisticsLoader { requestInstallStatistics(completion: completion) return } - + + SetAsDefaultStatistics().fireDailyActiveUser() + let configuration = APIRequest.Configuration(url: url) let request = APIRequest(configuration: configuration, urlSession: .session()) diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index f9509ffa1e..9650661d20 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -92,7 +92,8 @@ public struct UserDefaultsWrapper { case lastAppTrackingProtectionHistoryFetchTimestamp = "com.duckduckgo.ios.appTrackingProtection.lastTrackerHistoryFetchTimestamp" case appTPUsed = "com.duckduckgo.ios.appTrackingProtection.appTPUsed" - + + case defaultBrowserUsageLastSeen = "com.duckduckgo.ios.default-browser-usage-last-seen" } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 735c33c11b..97840eeafc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -407,6 +407,7 @@ 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */; }; 8565A34D1FC8DFE400239327 /* LaunchTabNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */; }; 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8577A1C4255D2C0D00D43FCD /* HitTestingToolbar.swift */; }; + 8577C6602A964BAC00788B3A /* SetAsDefaultStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8577C65F2A964BAC00788B3A /* SetAsDefaultStatistics.swift */; }; 857EEB752095FFAC008A005C /* HomeRowInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857EEB742095FFAC008A005C /* HomeRowInstructionsViewController.swift */; }; 858566E8252E4F56007501B8 /* Debug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 858566E7252E4F56007501B8 /* Debug.storyboard */; }; 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858566FA252E55D6007501B8 /* ImageCacheDebugViewController.swift */; }; @@ -1396,6 +1397,7 @@ 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotification.swift; sourceTree = ""; }; 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotificationTests.swift; sourceTree = ""; }; 8577A1C4255D2C0D00D43FCD /* HitTestingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestingToolbar.swift; sourceTree = ""; }; + 8577C65F2A964BAC00788B3A /* SetAsDefaultStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetAsDefaultStatistics.swift; sourceTree = ""; }; 857EEB742095FFAC008A005C /* HomeRowInstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRowInstructionsViewController.swift; sourceTree = ""; }; 858566E7252E4F56007501B8 /* Debug.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Debug.storyboard; sourceTree = ""; }; 858566FA252E55D6007501B8 /* ImageCacheDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheDebugViewController.swift; sourceTree = ""; }; @@ -4385,6 +4387,7 @@ 853A717520F62FE800FE60BC /* Pixel.swift */, 1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */, 1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */, + 8577C65F2A964BAC00788B3A /* SetAsDefaultStatistics.swift */, ); name = Statistics; sourceTree = ""; @@ -6617,6 +6620,7 @@ 85F21DC621145DD5002631A6 /* global.swift in Sources */, F41C2DA326C1925700F9A760 /* BookmarksAndFolders.xcdatamodeld in Sources */, F4F6DFBA26EFF28A00ED7E12 /* BookmarkObjects.swift in Sources */, + 8577C6602A964BAC00788B3A /* SetAsDefaultStatistics.swift in Sources */, 836A941D247F23C600BF8EF5 /* UserAgentManager.swift in Sources */, 4B83397329AFB8D2003F7EA9 /* AppTrackingProtectionFeedbackModel.swift in Sources */, 85CA53A824BB343700A6288C /* Favicons.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index cb7f2673f1..44add6f264 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -452,6 +452,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { showKeyboardIfSettingOn = false if !handleAppDeepLink(app, mainViewController, url) { + SetAsDefaultStatistics().openedAsDefault() mainViewController?.loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) } diff --git a/DuckDuckGo/OnboardingDefaultBroswerViewController.swift b/DuckDuckGo/OnboardingDefaultBroswerViewController.swift index a8a4816a6f..807db19c25 100644 --- a/DuckDuckGo/OnboardingDefaultBroswerViewController.swift +++ b/DuckDuckGo/OnboardingDefaultBroswerViewController.swift @@ -35,9 +35,15 @@ class OnboardingDefaultBroswerViewController: OnboardingContentViewController { } override func onContinuePressed(navigationHandler: @escaping () -> Void) { + SetAsDefaultStatistics().setAsDefaultOpened() if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) } super.onContinuePressed(navigationHandler: navigationHandler) } + + override func onSkipPressed(navigationHandler: @escaping () -> Void) { + SetAsDefaultStatistics().setAsDefaultSkipped() + super.onSkipPressed(navigationHandler: navigationHandler) + } } From 85a4640cedfce249da771a26b8264c8eaf731bfd Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Fri, 25 Aug 2023 17:44:09 +0100 Subject: [PATCH 13/17] Release script hot fix changes + "coldfix" (#1857) --- scripts/prepare_release.sh | 96 ++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 35de729ed6..97a6ec729c 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -5,7 +5,8 @@ set -eo pipefail mute=">/dev/null 2>&1" version="$1" release_branch_parent="develop" -hotfix_branch_parent="main" +tag=${version} +hotfix_branch_parent="tags/${tag}" # Get the directory where the script is stored script_dir=$(dirname "$(readlink -f "$0")") @@ -50,11 +51,12 @@ print_usage_and_exit() { cat <<- EOF Usage: - $ $(basename "$0") [-h] [-v] + $ $(basename "$0") [-h] [-v] Current version: $(cut -d' ' -f3 < "${base_dir}/Configuration/Version.xcconfig") Options: - -h Make hotfix release + -h Make hotfix release. Requires the version to be the one to hotfix, and a branch with the fix as the second parameter + -c Make coldfix release (i.e. a new build number on an existing release). Requires the version to be the one to coldfix, and a branch with the fix as the second parameter -v Enable verbose mode EOF @@ -63,17 +65,36 @@ print_usage_and_exit() { } read_command_line_arguments() { + number_of_arguments="$#" + local regexp="^[0-9]+(\.[0-9]+)*$" if [[ ! "$1" =~ $regexp ]]; then print_usage_and_exit "💥 Error: Wrong app version specified" fi - shift 1 + if [[ "$#" -ne 1 ]]; then + if [[ "$2" == -* ]]; then + shift 1 + else + fix_branch=$2 + shift 2 + fi + fi + + branch_name="release" - while getopts 'hv' option; do + while getopts 'hcv' option; do case "${option}" in h) is_hotfix=1 + branch_name="hotfix" + fix_type_name="hotfix" + ;; + c) + is_hotfix=1 + is_coldfix=1 + branch_name="coldfix" + fix_type_name="coldfix" ;; v) mute= @@ -86,7 +107,19 @@ read_command_line_arguments() { shift $((OPTIND-1)) - [[ $is_hotfix ]] && branch_name="hotfix" || branch_name="release" + if [[ $is_hotfix ]]; then + if [[ $number_of_arguments -ne 3 ]]; then + print_usage_and_exit "💥 Error: Wrong number of arguments. Did you specify a fix branch?" + fi + + version_to_hotfix=${version} + if ! [[ $is_coldfix ]]; then + IFS='.' read -ra arrIN <<< "$version" + patch_number=$((arrIN[2] + 1)) + version="${arrIN[0]}.${arrIN[1]}.$patch_number" + fi + fi + release_branch="${branch_name}/${version}" changes_branch="${release_branch}-changes" } @@ -106,21 +139,41 @@ assert_clean_state() { fi } +assert_hotfix_tag_exists_if_necessary() { + if [[ ! $is_hotfix ]]; then + return + fi + printf '%s' "Checking tag to ${fix_type_name} ... " + + # Make sure tag is available locally if it exists + eval git fetch origin "+refs/tags/${tag}:refs/tags/${tag}" "$mute" + + if [[ $(git tag -l "$version_to_hotfix" "$mute") ]]; then + echo "✅" + else + die "💥 Error: Tag ${version_to_hotfix} does not exist" + fi +} + create_release_branch() { if [[ ${is_hotfix} ]]; then - printf '%s' "Creating hotfix branch ... " - eval git checkout ${hotfix_branch_parent} "$mute" + printf '%s' "Creating ${fix_type_name} branch ... " + + eval git checkout "${hotfix_branch_parent}" "$mute" else printf '%s' "Creating release branch ... " eval git checkout ${release_branch_parent} "$mute" + eval git pull "$mute" fi - eval git pull "$mute" eval git checkout -b "${release_branch}" "$mute" eval git checkout -b "${changes_branch}" "$mute" echo "✅" } update_marketing_version() { + if [[ $is_coldfix ]]; then + return + fi printf '%s' "Setting app version ... " "$script_dir/set_version.sh" "${version}" git add "${base_dir}/Configuration/Version.xcconfig" \ @@ -172,6 +225,20 @@ update_release_notes() { fi } +merge_fix_branch_if_necessary() { + if [[ ! $is_hotfix ]]; then + return + fi + + printf '%s' "Merging fix branch ... " + eval git checkout "${fix_branch}" "$mute" + eval git pull "$mute" + + eval git checkout "${changes_branch}" "$mute" + eval git merge "${fix_branch}" "$mute" + echo "✅" +} + create_pull_request() { printf '%s' "Creating PR ... " eval git push origin "${release_branch}" "$mute" @@ -185,14 +252,23 @@ main() { assert_ios_directory assert_fastlane_installed assert_gh_installed_and_authenticated + read_command_line_arguments "$@" + stash assert_clean_state + assert_hotfix_tag_exists_if_necessary + create_release_branch + update_marketing_version update_build_version - update_embedded_files + if ! [[ $is_hotfix ]]; then + update_embedded_files + fi update_release_notes + merge_fix_branch_if_necessary + create_pull_request } From 68e437603afd6f4241ef0e6205a17436a9e629f9 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 25 Aug 2023 19:40:49 +0200 Subject: [PATCH 14/17] Separate bundle and group ids in iOS Alpha config file (#1957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1205101983701300/f Description: On implementing PR: Separate app groups across Alpha and other builds, the app groups were updated for Alpha builds, but the generic Configuration.xcconfig that was used for all build configs has defined a single app group prefix for all configurations. This resulted in a crash on startup. To fix this, we’ll need to define a separate app group prefix. This could be done through an override in the Xcode Build Settings, but I feel it’s cleaner to do this through a Configuration-Alpha.xcconfig. --- Configuration/Configuration-Alpha.xcconfig | 28 ++++++++++++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 4 +++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Configuration/Configuration-Alpha.xcconfig diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig new file mode 100644 index 0000000000..4c15890703 --- /dev/null +++ b/Configuration/Configuration-Alpha.xcconfig @@ -0,0 +1,28 @@ +// +// Configuration-Alpha.xcconfig +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#include "DuckDuckGoDeveloper.xcconfig" +#include? "ExternalDeveloper.xcconfig" +#include? "Version.xcconfig" + +// The app bundle identifier +APP_ID = com.duckduckgo.mobile.ios.alpha + +// A prefix for group ids. Must start with "group.". +GROUP_ID_PREFIX = group.com.duckduckgo.alpha diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 97840eeafc..9c1299ef2c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2329,6 +2329,7 @@ EE72CA842A862D000043B5B3 /* NetworkProtectionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugViewController.swift; sourceTree = ""; }; EE7917902A83DE93008DFF28 /* CombineTestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTestUtilities.swift; sourceTree = ""; }; EE8594982A44791C008A6D06 /* NetworkProtectionTunnelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionTunnelController.swift; sourceTree = ""; }; + EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Configuration-Alpha.xcconfig"; path = "Configuration/Configuration-Alpha.xcconfig"; sourceTree = ""; }; EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionPacketTunnelProvider.swift; sourceTree = ""; }; EEFD562E2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteViewModel.swift; sourceTree = ""; }; EEFE9C722A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionStatusViewModelTests.swift; sourceTree = ""; }; @@ -3479,6 +3480,7 @@ children = ( EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, + EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, 84E341941E2F7EFB00BDBA6F /* DuckDuckGo */, F143C2E51E4A4CD400CFDE3A /* Core */, 8390446D20BDCE10006461CD /* ShareExtension */, @@ -8238,7 +8240,7 @@ }; EE5A7C462A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */; + baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; From 4aba0810ffdc30cb34eab214298731c039e75ead Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Sat, 26 Aug 2023 11:50:55 +0200 Subject: [PATCH 15/17] Updates BSK (#1956) Task/Issue URL: https://app.asana.com/0/0/1205354504536671/f BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/479 macOS PR: https://github.com/duckduckgo/macos-browser/pull/1552 ## Description Updates to an upcoming version of BSK. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift | 4 +++- DuckDuckGo/NetworkProtectionTunnelController.swift | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9c1299ef2c..5c0261cf33 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8874,7 +8874,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 75.1.0; + version = 75.2.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c9bd83843d..d21e48b79f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "f3e85b86d6369cb03bfaf66169057c6452aa751f", - "version": "75.1.0" + "revision": "7221163e523b8454df3931a70689a807f9228c40", + "version": "75.2.0" } }, { diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index af653f4f43..60839f3148 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -47,7 +47,9 @@ extension ConnectionServerInfoObserverThroughSession { extension NetworkProtectionKeychainTokenStore { convenience init() { // Error events to be added as part of https://app.asana.com/0/1203137811378537/1205112639044115/f - self.init(keychainType: .dataProtection(.unspecified), errorEvents: nil) + self.init(keychainType: .dataProtection(.unspecified), + serviceName: "\(Bundle.main.bundleIdentifier!).authToken", + errorEvents: nil) } } diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index b4ec36c5cc..69b7006d50 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -28,7 +28,7 @@ import NetworkProtection final class NetworkProtectionTunnelController: TunnelController { static var simulationOptions = NetworkProtectionSimulationOptions() - private let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), errorEvents: nil) + private let tokenStore = NetworkProtectionKeychainTokenStore() private let errorStore = NetworkProtectionTunnelErrorStore() // MARK: - Starting & Stopping the VPN From 283daf94ea95bce4b333928e86ba14e66426f737 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Mon, 28 Aug 2023 15:42:18 +0200 Subject: [PATCH 16/17] Use unencrypted password for deduplicated Credentials objects (#1959) Task/Issue URL: https://app.asana.com/0/1199230911884351/1205352938100234/f Description: Credentials deduplication code looks for an existing credentials entity in the Secure Vault, then returns it to Sync code where its title is updated, and then the credentials entity is passed back to Secure Vault to be stored. The storing function expects unencrypted password (such as when passed from the UI) and does encryption before storing. The issue here was that we were passing an already encrypted password (just retrieved from Secure Vault) in which case the encryption function failed to encrypt the value, returning nil. This patch fixes the problem on the Sync Data Provider end (to feed an unencrypted password to Secure Vault for saving) and also adds an assertion failure to AutofillSecureVault when an encrypted password is passed to encryptPassword method. --- 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 5c0261cf33..06c4b72cf0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8874,7 +8874,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 75.2.0; + version = 75.2.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d21e48b79f..a654645eb6 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "7221163e523b8454df3931a70689a807f9228c40", - "version": "75.2.0" + "revision": "61947c2a53c43bd388f84001981e106c93775add", + "version": "75.2.1" } }, { @@ -156,7 +156,7 @@ }, { "package": "TrackerRadarKit", - "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit.git", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", "state": { "branch": null, "revision": "4684440d03304e7638a2c8086895367e90987463", From 8512ab1b31694991647344ca5b11948e2fb7f629 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 28 Aug 2023 17:36:32 +0200 Subject: [PATCH 17/17] Release 7.87.0 (#1961) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 51 ++++++++++++++++--- DuckDuckGo.xcodeproj/project.pbxproj | 42 +++++++-------- DuckDuckGo/Settings.bundle/Root.plist | 2 +- fastlane/README.md | 2 +- 6 files changed, 71 insertions(+), 32 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 6d5438c3a2..2e7fdf9b45 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.86.0 +MARKETING_VERSION = 7.87.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 71e4028d61..cefd93a865 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 = "\"2308844373650298899d98be1f5e6766\"" - public static let embeddedDataSHA = "160ffa9156640742a4be26a644354940ad0f3ea42e98e3cbfbe26e8d7a5c653a" + public static let embeddedDataETag = "\"c5c151e11d18bd98195388b7f8ce1911\"" + public static let embeddedDataSHA = "973b19cd9b1e8801faf4892c952ff0bfc8cf944e8591a1738ce82f3a647c9391" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index e0effbaa9d..1c2e0c4cbe 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1692659899657, + "version": 1692988300938, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -103,6 +103,10 @@ }, "autoconsent": { "exceptions": [ + { + "domain": "allocine.fr", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1241" + }, { "domain": "bild.de", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/326" @@ -242,6 +246,10 @@ { "domain": "gfds.de", "reason": "https://github.com/duckduckgo/autoconsent/issues/130" + }, + { + "domain": "motorsport.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1250" } ], "settings": { @@ -250,7 +258,7 @@ ] }, "state": "enabled", - "hash": "114934e1f0153202bf5e6f238b35ad88" + "hash": "989de4ea1ac1a481d56e6bfe2ca337fb" }, "autofill": { "exceptions": [ @@ -1077,6 +1085,10 @@ { "domain": "freenom.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1109" + }, + { + "domain": "iamexpat.nl", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1247" } ], "settings": { @@ -1094,7 +1106,7 @@ } }, "state": "disabled", - "hash": "0a080f38c1774ed81b4f93d2e593a669" + "hash": "747adbd628db0bccd219c15f29583d02" }, "clickToPlay": { "exceptions": [], @@ -3580,6 +3592,16 @@ "state": "enabled", "hash": "831a734e08585b40a38556ad9d108e7b" }, + "networkProtection": { + "state": "disabled", + "features": { + "waitlist": { + "state": "disabled" + } + }, + "exceptions": [], + "hash": "bf4c9cd751a7626bd89136f6cc98ccf1" + }, "newTabContinueSetUp": { "exceptions": [], "state": "disabled", @@ -3835,9 +3857,10 @@ "cnn.com", "eurogamer.net", "seattletimes.com", - "wcvb.com" + "wcvb.com", + "wildrivers.lostcoastoutpost.com" ], - "reason": "corriere.it - Example URL: https://www.corriere.it/video-articoli/2022/07/13/missione-wwf-liberare-mare-plastica/9abb64de-029d-11ed-a0cc-ad3c68cacbae.shtml; Clicking on the video to play causes a still frame to show and the video does not continue. eurogamer.net, seattletimes.com - An unskippable adwall appears which prevents interaction with the page. cnn.com - https://github.com/duckduckgo/privacy-configuration/issues/1220 wcvb.com - https://github.com/duckduckgo/privacy-configuration/issues/1088" + "reason": "corriere.it - Example URL: https://www.corriere.it/video-articoli/2022/07/13/missione-wwf-liberare-mare-plastica/9abb64de-029d-11ed-a0cc-ad3c68cacbae.shtml; Clicking on the video to play causes a still frame to show and the video does not continue. eurogamer.net, seattletimes.com - An unskippable adwall appears which prevents interaction with the page. cnn.com - https://github.com/duckduckgo/privacy-configuration/issues/1220 wcvb.com - https://github.com/duckduckgo/privacy-configuration/issues/1088 wildrivers.lostcoastoutpost.com - https://github.com/duckduckgo/privacy-configuration/issues/1252" } ] }, @@ -5155,6 +5178,17 @@ } ] }, + "ipify.org": { + "rules": [ + { + "rule": "api.ipify.org/", + "domains": [ + "mass.gov" + ], + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1239" + } + ] + }, "jimstatic.com": { "rules": [ { @@ -6305,7 +6339,7 @@ } }, "exceptions": [], - "hash": "331ee3a6561d1cf6a042fd47770e0ceb" + "hash": "03ab20dcdf147caa335a214edd204220" }, "trackingCookies1p": { "settings": { @@ -6368,6 +6402,11 @@ "state": "disabled", "hash": "5d33a7d6a3f780d2e07076e209a5bccb" }, + "voiceSearch": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" + }, "webCompat": { "exceptions": [], "state": "enabled", diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 06c4b72cf0..6e80c84e1d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -7533,7 +7533,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -7570,7 +7570,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -7662,7 +7662,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -7689,7 +7689,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -7835,7 +7835,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -7859,7 +7859,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -7923,7 +7923,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -7958,7 +7958,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -7992,7 +7992,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -8022,7 +8022,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8308,7 +8308,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8334,7 +8334,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8366,7 +8366,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8403,7 +8403,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8439,7 +8439,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8474,11 +8474,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -8652,11 +8652,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -8685,10 +8685,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + 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 3e7529d290..630cf2f059 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.86.0 + 7.87.0 Key version Title diff --git a/fastlane/README.md b/fastlane/README.md index 9733c1e6d0..b0375dd0f9 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -35,7 +35,7 @@ Fetches and updates certificates and provisioning profiles for Ad-Hoc distributi [bundle exec] fastlane sync_signing_alpha ``` -Fetches and updates certificates and provisioning profiles for Alpha TestFlight distribution + ### adhoc