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: 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/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/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/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/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 { 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/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 bd20438a6b..6e80c84e1d 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 */; }; @@ -669,6 +670,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 +684,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 +703,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 */; }; @@ -1389,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 = ""; }; @@ -2244,6 +2253,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 +2266,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 +2285,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 = ""; }; @@ -2301,6 +2317,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 = ""; }; @@ -2310,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 = ""; }; @@ -2565,6 +2585,7 @@ 02025665298818B200E694E7 /* PacketTunnelProvider */ = { isa = PBXGroup; children = ( + EE3B98EC2A963538002F63A0 /* PacketTunnelProviderAlpha.entitlements */, 02025670298818CB00E694E7 /* ProxyServer */, 02025666298818B200E694E7 /* AppTrackingProtectionPacketTunnelProvider.swift */, 02025B1429884EA500E694E7 /* DDGObserverFactory.swift */, @@ -3457,7 +3478,9 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, + EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, 84E341941E2F7EFB00BDBA6F /* DuckDuckGo */, F143C2E51E4A4CD400CFDE3A /* Core */, 8390446D20BDCE10006461CD /* ShareExtension */, @@ -3501,6 +3524,7 @@ 84E341941E2F7EFB00BDBA6F /* DuckDuckGo */ = { isa = PBXGroup; children = ( + EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */, CB258D1129A4F1BB00DEBA24 /* Configuration */, 1E908BED29827C480008C8F3 /* Autoconsent */, 3157B43627F4C8380042D3D7 /* Favicons */, @@ -3515,6 +3539,7 @@ B652DF02287C01EE00C12A9C /* ContentBlocking */, 310D09192799EF5C00DC0060 /* Downloads */, F143C2C51E4A08F300CFDE3A /* DuckDuckGo.entitlements */, + C159DF052A430B36007834BB /* EmailProtection */, 839F119520DBC489007CD8C2 /* Feedback */, 85F2FFFE2215C163006BB258 /* FindInPage */, F13B4BF31F18C73A00814661 /* Home */, @@ -4151,6 +4176,15 @@ name = ImportExport; sourceTree = ""; }; + C159DF052A430B36007834BB /* EmailProtection */ = { + isa = PBXGroup; + children = ( + C1F341C32A6923D70032057B /* EmailAddressPrompt */, + C1CAA3D52A630ECB00807703 /* EmailSignup */, + ); + name = EmailProtection; + sourceTree = ""; + }; C17B59552A03AAC40055F2D1 /* PasswordGeneration */ = { isa = PBXGroup; children = ( @@ -4188,6 +4222,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 = ( @@ -4334,6 +4389,7 @@ 853A717520F62FE800FE60BC /* Pixel.swift */, 1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */, 1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */, + 8577C65F2A964BAC00788B3A /* SetAsDefaultStatistics.swift */, ); name = Statistics; sourceTree = ""; @@ -5781,7 +5837,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; @@ -5838,7 +5894,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 */ @@ -5942,6 +5998,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 */, @@ -5970,6 +6027,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 */, @@ -6039,6 +6097,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 */, @@ -6131,6 +6190,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 */, @@ -6161,6 +6221,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 */, @@ -6172,6 +6233,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 */, @@ -6228,6 +6290,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 */, @@ -6559,6 +6622,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 */, @@ -7469,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; @@ -7506,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; @@ -7598,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 = ( @@ -7625,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; @@ -7771,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; @@ -7795,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 = ( @@ -7859,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; @@ -7894,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; @@ -7928,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 = ( @@ -7958,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; @@ -8176,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; @@ -8241,10 +8305,10 @@ 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 = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8270,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; @@ -8302,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; @@ -8335,11 +8399,11 @@ 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; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8371,11 +8435,11 @@ 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; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8410,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"; @@ -8588,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"; @@ -8621,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"; @@ -8810,7 +8874,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 75.0.2; + 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 42e1c53d24..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": "8d4c3d884762d954183a9496961b87d254b43539", - "version": "75.0.2" + "revision": "61947c2a53c43bd388f84001981e106c93775add", + "version": "75.2.1" } }, { @@ -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.git", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", "state": { "branch": null, "revision": "4684440d03304e7638a2c8086895367e90987463", 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/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 554aabf44e..44add6f264 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -59,7 +59,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private var showKeyboardIfSettingOn = true private var lastBackgroundDate: Date? - private(set) var syncService: DDGSyncing! + private(set) var syncService: DDGSync! private(set) var syncDataProviders: SyncDataProviders! private var syncDidFinishCancellable: AnyCancellable? private var syncStateCancellable: AnyCancellable? @@ -69,16 +69,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> 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() @@ -454,12 +441,18 @@ 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() showKeyboardIfSettingOn = false if !handleAppDeepLink(app, mainViewController, url) { + SetAsDefaultStatistics().openedAsDefault() mainViewController?.loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) } @@ -588,6 +581,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/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/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/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/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/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/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/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index f2f386d2e1..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(useSystemKeychain: false, 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 b7dc7e9eaf..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(useSystemKeychain: false, errorEvents: nil) + private let tokenStore = NetworkProtectionKeychainTokenStore() private let errorStore = NetworkProtectionTunnelErrorStore() // MARK: - Starting & Stopping the VPN 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) + } } 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/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/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/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) } 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"; 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) } 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 + + + 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 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 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 }