From 74227745ec8dedeca7293af0e608cf5a158b16f3 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 6 Dec 2023 02:13:37 +0100 Subject: [PATCH 01/99] General View, Sync, Logins, Appeareance first implementation --- DuckDuckGo.xcodeproj/project.pbxproj | 52 ++++ DuckDuckGo/AppIconManager.swift | 33 +-- .../AutoconsentSettingsViewController.swift | 2 +- ...ofillLoginSettingsListViewController.swift | 14 +- DuckDuckGo/Base.lproj/Settings.storyboard | 10 +- .../DoNotSellSettingsViewController.swift | 2 +- DuckDuckGo/FireButtonAnimator.swift | 4 +- DuckDuckGo/MainViewController+Segues.swift | 31 +-- DuckDuckGo/MainViewController.swift | 3 +- DuckDuckGo/OptionalBinding.swift | 32 +++ DuckDuckGo/SettingsAppeareanceView.swift | 96 +++++++ DuckDuckGo/SettingsCell.swift | 115 +++++++++ DuckDuckGo/SettingsGeneralView.swift | 80 ++++++ DuckDuckGo/SettingsLoginsView.swift | 100 ++++++++ DuckDuckGo/SettingsModel.swift | 20 ++ DuckDuckGo/SettingsSyncView.swift | 80 ++++++ DuckDuckGo/SettingsUserText.swift | 26 ++ DuckDuckGo/SettingsView.swift | 109 ++++++++ DuckDuckGo/SettingsViewController.swift | 6 +- DuckDuckGo/SettingsViewModel.swift | 242 ++++++++++++++++++ DuckDuckGo/Theme.swift | 10 +- .../UnprotectedSitesViewController.swift | 2 +- DuckDuckGo/UserText.swift | 5 + DuckDuckGo/View+onChageSkippingFirst.swift | 44 ++++ DuckDuckGo/WidgetEducationView.swift | 2 +- DuckDuckGo/en.lproj/Localizable.strings | 6 + 26 files changed, 1076 insertions(+), 50 deletions(-) create mode 100644 DuckDuckGo/OptionalBinding.swift create mode 100644 DuckDuckGo/SettingsAppeareanceView.swift create mode 100644 DuckDuckGo/SettingsCell.swift create mode 100644 DuckDuckGo/SettingsGeneralView.swift create mode 100644 DuckDuckGo/SettingsLoginsView.swift create mode 100644 DuckDuckGo/SettingsModel.swift create mode 100644 DuckDuckGo/SettingsSyncView.swift create mode 100644 DuckDuckGo/SettingsUserText.swift create mode 100644 DuckDuckGo/SettingsView.swift create mode 100644 DuckDuckGo/SettingsViewModel.swift create mode 100644 DuckDuckGo/View+onChageSkippingFirst.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e82c09e6e9..8ee2304dff 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -768,6 +768,13 @@ CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; + D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; + D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; + D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; + D6E83C382B1F2236006C8AFB /* SettingsGeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */; }; + D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */; }; + D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */; }; + D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2392,6 +2399,13 @@ CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; + D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; + D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsGeneralView.swift; sourceTree = ""; }; + D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSyncView.swift; sourceTree = ""; }; + D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLoginsView.swift; sourceTree = ""; }; + D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppeareanceView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4469,6 +4483,35 @@ name = Resources; sourceTree = ""; }; + D6E83C322B1F1279006C8AFB /* Sections */ = { + isa = PBXGroup; + children = ( + D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */, + D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */, + D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */, + D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, + ); + name = Sections; + sourceTree = ""; + }; + D6E83C3B2B1F27BA006C8AFB /* Views */ = { + isa = PBXGroup; + children = ( + D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, + D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */, + D6E83C302B1EA309006C8AFB /* SettingsCell.swift */, + D6E83C322B1F1279006C8AFB /* Sections */, + ); + name = Views; + sourceTree = ""; + }; + D6E83C442B1FFCF9006C8AFB /* Extensions */ = { + isa = PBXGroup; + children = ( + ); + name = Extensions; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5097,6 +5140,8 @@ F1AB2B401E3F75A000868554 /* Settings */ = { isa = PBXGroup; children = ( + D6E83C442B1FFCF9006C8AFB /* Extensions */, + D6E83C3B2B1F27BA006C8AFB /* Views */, 858566F1252E55AE007501B8 /* Debug */, 85449EF723FDA03D00512AAF /* Model */, 85449EF623FDA03100512AAF /* UI */, @@ -6271,6 +6316,7 @@ 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, + D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, @@ -6292,12 +6338,14 @@ B6BA95C328891E33004ABA20 /* BrowsingMenuAnimator.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, + D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 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 */, + D6E83C382B1F2236006C8AFB /* SettingsGeneralView.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, 314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */, 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */, @@ -6493,6 +6541,7 @@ 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, 31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */, + D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */, @@ -6504,6 +6553,7 @@ 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */, F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */, 85F2FFCD2211F615006BB258 /* MainViewController+KeyCommands.swift in Sources */, + D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */, 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */, 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, @@ -6540,6 +6590,7 @@ 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, + D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */, @@ -6600,6 +6651,7 @@ 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */, 1EF24235273BB9D200DE3D02 /* IntervalSlider.swift in Sources */, 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, + D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */, diff --git a/DuckDuckGo/AppIconManager.swift b/DuckDuckGo/AppIconManager.swift index dbee15c1c3..d47c446eb9 100644 --- a/DuckDuckGo/AppIconManager.swift +++ b/DuckDuckGo/AppIconManager.swift @@ -21,9 +21,18 @@ import Common import UIKit import Core -class AppIconManager { +class AppIconManager: ObservableObject { static var shared = AppIconManager() + + @Published var appIcon: AppIcon = { + guard let appIconName = UIApplication.shared.alternateIconName, + let appIcon = AppIcon(rawValue: appIconName) else { + return AppIcon.defaultAppIcon + } + + return appIcon + }() var isAppIconChangeSupported: Bool { UIApplication.shared.supportsAlternateIcons @@ -37,22 +46,16 @@ class AppIconManager { let alternateIconName = appIcon != AppIcon.defaultAppIcon ? appIcon.rawValue : nil UIApplication.shared.setAlternateIconName(alternateIconName) { error in - if let error = error { - os_log("Error while changing app icon: %s", log: .generalLog, type: .debug, error.localizedDescription) - completionHandler?(error) - } else { - completionHandler?(nil) + DispatchQueue.main.async { + if let error = error { + os_log("Error while changing app icon: %s", log: .generalLog, type: .debug, error.localizedDescription) + completionHandler?(error) + } else { + self.appIcon = appIcon + completionHandler?(nil) + } } } } - var appIcon: AppIcon { - guard let appIconName = UIApplication.shared.alternateIconName, - let appIcon = AppIcon(rawValue: appIconName) else { - return AppIcon.defaultAppIcon - } - - return appIcon - } - } diff --git a/DuckDuckGo/AutoconsentSettingsViewController.swift b/DuckDuckGo/AutoconsentSettingsViewController.swift index 4a068fe614..470ba711d4 100644 --- a/DuckDuckGo/AutoconsentSettingsViewController.swift +++ b/DuckDuckGo/AutoconsentSettingsViewController.swift @@ -39,7 +39,7 @@ final class AutoconsentSettingsViewController: UITableViewController { autoconsentToggle.isOn = appSettings.autoconsentEnabled - let fontSize = SettingsViewController.fontSizeForHeaderView + let fontSize = SettingsViewModel.fontSizeForHeaderView let text = NSAttributedString(string: UserText.autoconsentInfoText, attributes: [ NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize) ]) diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index 3c7d0f86a8..4ead6d37fe 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -102,14 +102,17 @@ final class AutofillLoginSettingsListViewController: UIViewController { multiplier: 1, constant: (tableView.frame.height / 2)) }() + + var selectedAccount: SecureVaultModels.WebsiteAccount? - init(appSettings: AppSettings, currentTabUrl: URL? = nil, syncService: DDGSyncing, syncDataProviders: SyncDataProviders) { + init(appSettings: AppSettings, currentTabUrl: URL? = nil, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, selectedAccount: SecureVaultModels.WebsiteAccount?) { let secureVault = try? AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared) if secureVault == nil { os_log("Failed to make vault") } self.viewModel = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: secureVault, currentTabUrl: currentTabUrl) self.syncService = syncService + self.selectedAccount = selectedAccount super.init(nibName: nil, bundle: nil) syncUpdatesCancellable = syncDataProviders.credentialsAdapter.syncDidCompletePublisher @@ -148,6 +151,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) authenticate() + } override func viewWillDisappear(_ animated: Bool) { @@ -290,10 +294,18 @@ final class AutofillLoginSettingsListViewController: UIViewController { self.delegate?.autofillLoginSettingsListViewControllerDidFinish(self) } } else { + showSelectedAccountIfRequired() self.syncService.scheduler.requestSyncImmediately() } } } + + private func showSelectedAccountIfRequired() { + if let account = selectedAccount { + showAccountDetails(account, animated: false) + selectedAccount = nil + } + } private func presentDeleteConfirmation(for title: String, domain: String) { let message = title.isEmpty ? UserText.autofillLoginListLoginDeletedToastMessageNoTitle diff --git a/DuckDuckGo/Base.lproj/Settings.storyboard b/DuckDuckGo/Base.lproj/Settings.storyboard index 63439e5402..1532ebc469 100644 --- a/DuckDuckGo/Base.lproj/Settings.storyboard +++ b/DuckDuckGo/Base.lproj/Settings.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1254,7 +1254,7 @@ - + @@ -1522,7 +1522,7 @@ - + @@ -2346,7 +2346,7 @@ - + diff --git a/DuckDuckGo/DoNotSellSettingsViewController.swift b/DuckDuckGo/DoNotSellSettingsViewController.swift index 8b6e1a6f89..735c66b652 100644 --- a/DuckDuckGo/DoNotSellSettingsViewController.swift +++ b/DuckDuckGo/DoNotSellSettingsViewController.swift @@ -71,7 +71,7 @@ extension DoNotSellSettingsViewController: Themable { /// Apply attributes for NSAtrtributedStrings for copy text func applyAtributes(theme: Theme) { - let fontSize = SettingsViewController.fontSizeForHeaderView + let fontSize = SettingsViewModel.fontSizeForHeaderView let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 1.16 let tempStr = NSMutableAttributedString(string: UserText.doNotSellInfoText + " ", diff --git a/DuckDuckGo/FireButtonAnimator.swift b/DuckDuckGo/FireButtonAnimator.swift index 4480c98cc4..adaa4742c1 100644 --- a/DuckDuckGo/FireButtonAnimator.swift +++ b/DuckDuckGo/FireButtonAnimator.swift @@ -20,13 +20,15 @@ import UIKit import Lottie -enum FireButtonAnimationType: String, CaseIterable { +enum FireButtonAnimationType: String, CaseIterable, Identifiable { case fireRising case waterSwirl case airstream case none + var id: String { self.rawValue } + var descriptionText: String { switch self { case .fireRising: diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 4eb01f301a..c272747966 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -22,6 +22,7 @@ import Common import Core import Bookmarks import BrowserServicesKit +import SwiftUI extension MainViewController { @@ -195,7 +196,7 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.openLogins(accountDetails: account) + $0.setIsPresentingLoginsViewWithAccount(accountDetails: account) } } @@ -203,26 +204,22 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.showSync() + $0.setIsPresentingSyncView(true) } } - private func launchSettings(completion: ((SettingsViewController) -> Void)? = nil) { + + private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { os_log(#function, log: .generalLog, type: .debug) - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - - let settings = storyboard.instantiateViewController(identifier: "SettingsViewController") { coder in - SettingsViewController(coder: coder, - bookmarksDatabase: self.bookmarksDatabase, - syncService: self.syncService, - syncDataProviders: self.syncDataProviders, - internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - } - - let controller = ThemableNavigationController(rootViewController: settings) - controller.modalPresentationStyle = .automatic - present(controller, animated: true) { - completion?(settings) + let settingsModel = SettingsViewModel(bookmarksDatabase: self.bookmarksDatabase, + syncService: self.syncService, + syncDataProviders: self.syncDataProviders, + internalUserDecider: AppDependencyProvider.shared.internalUserDecider) + let settingsView = SettingsView(viewModel: settingsModel) + let settingsController = UIHostingController(rootView: settingsView) + settingsController.modalPresentationStyle = .automatic + present(settingsController, animated: true) { + completion?(settingsModel) } } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index fa2a0b7336..06beba8748 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1004,7 +1004,8 @@ class MainViewController: UIViewController { appSettings: appSettings, currentTabUrl: currentTabUrl, syncService: syncService, - syncDataProviders: syncDataProviders + syncDataProviders: syncDataProviders, + selectedAccount: nil ) autofillSettingsViewController.delegate = self let navigationController = UINavigationController(rootViewController: autofillSettingsViewController) diff --git a/DuckDuckGo/OptionalBinding.swift b/DuckDuckGo/OptionalBinding.swift new file mode 100644 index 0000000000..962a0924e4 --- /dev/null +++ b/DuckDuckGo/OptionalBinding.swift @@ -0,0 +1,32 @@ +// +// OptionalBinding.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 + +extension Binding { + + /// Creates a binding by projecting the value of an optional to a non-optional, replacing `nil` values with a default. + /// As onChange props are optional (nil by default) This prevents onChange events from triggering when props are initialized + init(_ source: Binding, replacingNilWith defaultValue: Value) { + self.init( + get: { source.wrappedValue ?? defaultValue }, + set: { source.wrappedValue = $0 } + ) + } +} diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift new file mode 100644 index 0000000000..5b5c65eef6 --- /dev/null +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -0,0 +1,96 @@ +// TODO: Remove transition animation if showing a selected account// +// GeneralSection.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 UIKit + +struct SettingsAppeareanceView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + @State var selectedTheme: ThemeName = .systemDefault + @State var setIsPresentingAppIconView: Bool = false + @State var selectedFireButtonAnimation: FireButtonAnimationType = .fireRising + @State private var isFirstUpdate = true + + var body: some View { + Section(header: Text("Appeareance")) { + Picker("Theme", selection: $selectedTheme) { + ForEach(ThemeName.allCases) { option in + Text(option.rawValue).tag(option) + } + } + NavigationLink(destination: AppIconSettingsViewControllerRepresentable(), isActive: $setIsPresentingAppIconView) { + ImageCell(label: "App Icon", image: Image(uiImage: viewModel.state.appIcon.smallImage ?? UIImage())) + } + + Picker("Fire Button Animation", selection: $selectedFireButtonAnimation) { + ForEach(FireButtonAnimationType.allCases) { option in + Text(option.descriptionText).tag(option) + } + } + + /* + RightDetailCell(label: "Text Size", value: "100%", action: viewModel.selectTextSize) + RightDetailCell(label: "Address Bar Position", value: "Top", action: viewModel.selectBarPosition) + */ + } + + .onAppear { + selectedTheme = viewModel.state.appTheme + selectedFireButtonAnimation = viewModel.state.fireButtonAnimation + isFirstUpdate = true + } + + .onChange(of: selectedTheme) { newValue in + viewModel.setTheme(theme: newValue) + } + + .onChange(of: selectedFireButtonAnimation) { newValue in + if isFirstUpdate { + isFirstUpdate = false + viewModel.setFireButtonAnimation(newValue, showAnimation: false) + } else { + viewModel.setFireButtonAnimation(newValue) + } + } + + } +} + +struct AppIconSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = AppIconSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> AppIconSettingsViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "AppIcon") as! AppIconSettingsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: AppIconSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift new file mode 100644 index 0000000000..85899b58d1 --- /dev/null +++ b/DuckDuckGo/SettingsCell.swift @@ -0,0 +1,115 @@ +// +// SettingsCell.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 + +protocol SettingsCell { + var label: String { get set } + var action: () -> Void { get set } + var enabled: Bool { get set } +} + +struct PlainCell: View, SettingsCell { + + var label: String + var action: () -> Void = {} + var disclosureIndicator: Bool = true + var enabled: Bool = true + + var body: some View { + Button(action: action) { + Text(label) + }.disabled(!enabled) + } +} + +struct RightDetailCell: View, SettingsCell { + + var label: String + var value: String + var action: () -> Void = {} + var disclosureIndicator: Bool = true + var enabled: Bool = true + + var body: some View { + Button(action: action) { + HStack { + Text(label) + Spacer() + Text(value) + } + }.disabled(!enabled) + } +} + +struct ToggleCell: View, SettingsCell { + + var label: String + var action: () -> Void = {} + var disclosureIndicator: Bool = true + var enabled: Bool = true + @State var value: Bool = false + + var body: some View { + Toggle(isOn: $value) { + Text(label) + } + } +} + +struct ImageCell: View, SettingsCell { + + var label: String + var image: Image + var action: () -> Void = {} + var disclosureIndicator: Bool = true + var enabled: Bool = true + + var body: some View { + Button(action: action) { + HStack { + Text(label) + Spacer() + image + .resizable() + .scaledToFit() + .frame(width: 25, height: 25) // Adjust the size as needed + } + }.disabled(!enabled) + } +} + +struct SubtitleCell: View, SettingsCell { + + var label: String + var subtitle: String + var action: () -> Void = {} + var disclosureIndicator: Bool = true + var enabled: Bool + + var body: some View { + Button(action: action) { + HStack { + Text(label) + Text(subtitle).font(.subheadline) + } + .disabled(!enabled) + } + } +} diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift new file mode 100644 index 0000000000..fb600e4c85 --- /dev/null +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -0,0 +1,80 @@ +// +// GeneralSection.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 UIKit + +struct SettingsGeneralView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + @State var isPresentingAddToDockView: Bool = false + @State var isPresentingAddWidgetView: Bool = false + + var body: some View { + Section { + PlainCell(label: "Set as Default Browser", action: viewModel.setAsDefaultBrowser) + PlainCell(label: "Add App to Your Dock", action: { + { viewModel.setIsPresentingAddToDockView(true) }() + }) + NavigationLink(destination: WidgetEducationView(), isActive: $isPresentingAddWidgetView) { + PlainCell(label: "Add Widget to Home Screen", action: { viewModel.setIsPresentingAddWidgetView(true) }) + } + } + + .onChange(of: viewModel.state.isPresentingAddToDockView) { newValue in + isPresentingAddToDockView = newValue + } + + .onChange(of: isPresentingAddToDockView) { newValue in + viewModel.setIsPresentingAddToDockView(newValue) + } + + .onChange(of: isPresentingAddWidgetView) { newValue in + viewModel.setIsPresentingAddWidgetView(newValue) + } + + // Modal View + .fullScreenCover(isPresented: $isPresentingAddToDockView) { + HomeRowInstructionsViewControllerRepresentable() + } + } + +} + +struct HomeRowInstructionsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = HomeRowInstructionsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> HomeRowInstructionsViewController { + let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: HomeRowInstructionsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift new file mode 100644 index 0000000000..7cd39a9b73 --- /dev/null +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -0,0 +1,100 @@ +// TODO: Remove transition animation if showing a selected account// +// GeneralSection.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 UIKit +import Core +import DDGSync +import BrowserServicesKit + +struct SettingsLoginsView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + @State var isPresentingLoginsView: Bool = false + + var body: some View { + if viewModel.state.shouldShowLoginsCell { + let autofillController = AutofillLoginSettingsListViewControllerRepresentable(appSettings: viewModel.appSettings, + syncService: viewModel.syncService, + syncDataProviders: viewModel.syncDataProviders, + delegate: viewModel, + selectedAccount: viewModel.state.loginsViewSelectedAccount) + Section { + // TODO: Remove transition animation if showing a selected account + NavigationLink(destination: autofillController, isActive: $isPresentingLoginsView) { + PlainCell(label: UserText.autofillLoginListTitle, action: { viewModel.setIsPresentingLoginsView(true) }) + + } + } + + .onAppear { + isPresentingLoginsView = viewModel.state.isPresentingLoginsView + } + + .onChange(of: isPresentingLoginsView) { _ in + viewModel.setIsPresentingLoginsView(true) + } + + .onChange(of: viewModel.state.isPresentingLoginsView) { newValue in + isPresentingLoginsView = newValue + } + + } + } + +} + + +struct AutofillLoginSettingsListViewControllerRepresentable: UIViewControllerRepresentable { + + let appSettings: AppSettings + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + let delegate: AutofillLoginSettingsListViewControllerDelegate + let selectedAccount: SecureVaultModels.WebsiteAccount? + + typealias UIViewControllerType = AutofillLoginSettingsListViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> AutofillLoginSettingsListViewController { + let autofillController = AutofillLoginSettingsListViewController( + appSettings: appSettings, + syncService: syncService, + syncDataProviders: syncDataProviders, + selectedAccount: selectedAccount + ) + + context.coordinator.parentObserver = autofillController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems + + }) + + return autofillController + } + + func updateUIViewController(_ uiViewController: AutofillLoginSettingsListViewController, context: Self.Context) { + + } + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift new file mode 100644 index 0000000000..f1de1c53d2 --- /dev/null +++ b/DuckDuckGo/SettingsModel.swift @@ -0,0 +1,20 @@ +// +// SettingsModel.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 diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift new file mode 100644 index 0000000000..8d0ee40433 --- /dev/null +++ b/DuckDuckGo/SettingsSyncView.swift @@ -0,0 +1,80 @@ +// +// GeneralSection.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 UIKit +import Core +import DDGSync + +struct SettingsSyncView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + @State var isPresentingSyncView: Bool = false + + var body: some View { + let syncSettingsController = SyncSettingsViewControllerRepresentable(syncService: viewModel.syncService, + syncDataProviders: viewModel.syncDataProviders) + if viewModel.state.shouldShowSyncCell { + Section { + NavigationLink(destination: syncSettingsController, isActive: $isPresentingSyncView) { + PlainCell(label: UserText.syncTitle, action: { viewModel.setIsPresentingSyncView(true) }) + } + } + + .onAppear { + isPresentingSyncView = viewModel.state.isPresentingSyncView + } + + .onChange(of: viewModel.state.isPresentingSyncView) { newValue in + isPresentingSyncView = newValue + } + + .onChange(of: isPresentingSyncView) { newValue in + viewModel.setIsPresentingSyncView(newValue) + } + } + } +} + +// TODO: Sync is already SwiftUI - This should be migrated to a SwiftUI View +struct SyncSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + + typealias UIViewControllerType = SyncSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Context) -> SyncSettingsViewController { + let viewController = SyncSettingsViewController(syncService: syncService, + syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems + }) + return viewController + } + + func updateUIViewController(_ uiViewController: SyncSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} diff --git a/DuckDuckGo/SettingsUserText.swift b/DuckDuckGo/SettingsUserText.swift new file mode 100644 index 0000000000..186afee8ee --- /dev/null +++ b/DuckDuckGo/SettingsUserText.swift @@ -0,0 +1,26 @@ +// +// SettingsUserText.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 Core + +public struct SettingsUserText { + + + + diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift new file mode 100644 index 0000000000..f010ac6737 --- /dev/null +++ b/DuckDuckGo/SettingsView.swift @@ -0,0 +1,109 @@ +// +// SettingsView.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 UIKit + +struct SettingsView: View { + + @StateObject var viewModel: SettingsViewModel + + var body: some View { + NavigationView { + List { + SettingsGeneralView() + SettingsSyncView() + SettingsLoginsView() + SettingsAppeareanceView() + } + .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) + .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { + }) + .environmentObject(viewModel) + } + + } + +} + + + /* + Section(header: Text("Privacy"), + footer: Text("If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening.")) { + RightDetailCell(label: "Global Privacy Control (GMC)", value: "Enabled") + RightDetailCell(label: "Manage Cookie Popups", value: "Disabled") + PlainCell(label: "Set as Default Browser") + PlainCell(label: "Fireproof Sites") + RightDetailCell(label: "Automatically Clear Data", value: "Off") + ToggleCell(label: "Application Lock", value: false) + } + + + Section(header: Text("Customize"), + footer: Text("Disable to prevent links from automatically opening in other installed apps.")) { + PlainCell(label: "Keyboard") + ToggleCell(label: "Autocomplete Suggestions", value: false) + ToggleCell(label: "Private Voice Search", value: false) + ToggleCell(label: "Long Press Previews", value: false) + ToggleCell(label: "Open Links in Associated Apps", value: false) + } + Section(header: Text("More from DuckDuckGo")) { + SubtitleCell(label: "Email Protection", subtitle: "Block Email Trackers and hide your address") + SubtitleCell(label: "DuckDuckGo Mac App", subtitle: "Browse privaly with our app for Mac") + SubtitleCell(label: "DuckDuckGo Windows App", subtitle: "Browse privaly with our app for Windows") + SubtitleCell(label: "Network Protection", subtitle: "Join the private waitlist") + } + Section(header: Text("About")) { + PlainCell(label: "About DuckDuckGo") + RightDetailCell(label: "Version", value: "7.99.0.2") + PlainCell(label: "Share Feedback") + } + Section { + PlainCell(label: "Debug Menu") + } + */ + + +/* +struct SettingsAppeareanceView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + + var body: some View { + Section(header: Text("Appeareance")) { + RightDetailCell(label: "Theme", + value: "System", + action: viewModel.showTheme) + ImageCell(label: "App Icon", + image: Image(systemName: "photo"), + action: viewModel.selectIcon) + RightDetailCell(label: "Fire Button Animation", + value: "Inferno", + action: viewModel.selectFireAnimation) + RightDetailCell(label: "Text Size", + value: "100%", + action: viewModel.selectTextSize) + + RightDetailCell(label: "Address Bar Position", + value: "Top", + action: viewModel.selectBarPosition) + } + } +} +*/ diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 1ee3f0ef18..18bc4c8f44 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -396,7 +396,8 @@ class SettingsViewController: UITableViewController { let autofillController = AutofillLoginSettingsListViewController( appSettings: appSettings, syncService: syncService, - syncDataProviders: syncDataProviders + syncDataProviders: syncDataProviders, + selectedAccount: nil ) autofillController.delegate = self Pixel.fire(pixel: .autofillSettingsOpened) @@ -407,7 +408,8 @@ class SettingsViewController: UITableViewController { let autofillController = AutofillLoginSettingsListViewController( appSettings: appSettings, syncService: syncService, - syncDataProviders: syncDataProviders + syncDataProviders: syncDataProviders, + selectedAccount: account ) autofillController.delegate = self let detailsController = autofillController.makeAccountDetailsScreen(account) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift new file mode 100644 index 0000000000..5898615ada --- /dev/null +++ b/DuckDuckGo/SettingsViewModel.swift @@ -0,0 +1,242 @@ +// +// SettingsViewModel.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 Core +import BrowserServicesKit +import Persistence +import SwiftUI +import Common +import DDGSync +import Combine + +#if APP_TRACKING_PROTECTION +import NetworkExtension +#endif + +#if NETWORK_PROTECTION +import NetworkProtection +#endif + +struct SettingsState { + var isPresentingAddToDockView = false + var isPresentingAddWidgetView = false + var isPresentingSyncView = false + var isPresentingLoginsView = false + var loginsViewSelectedAccount: SecureVaultModels.WebsiteAccount? + var isPresentingAppIconView = false + + var shouldShowSyncCell = false + var shouldShowLoginsCell = false + var appTheme: ThemeName = .systemDefault + var appIcon: AppIcon = .defaultAppIcon + var fireButtonAnimation: FireButtonAnimationType = .fireRising +} + +final class SettingsViewModel: ObservableObject { + + private let bookmarksDatabase: CoreDataDatabase + private lazy var emailManager = EmailManager() + private lazy var versionProvider: AppVersion = AppVersion.shared + fileprivate lazy var privacyStore = PrivacyUserDefaults() + private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) + fileprivate lazy var variantManager = AppDependencyProvider.shared.variantManager + fileprivate lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger + + private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + + fileprivate let internalUserDecider: InternalUserDecider + +#if NETWORK_PROTECTION + private let connectionObserver = ConnectionStatusObserverThroughSession() +#endif + private var cancellables: Set = [] + + private var shouldShowDebugCell: Bool { + return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild + } + + private var shouldShowVoiceSearchCell: Bool { + AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable + } + + private var shouldShowTextSizeCell: Bool { + return UIDevice.current.userInterfaceIdiom != .pad + } + + private var shouldShowAddressBarPositionCell: Bool { + return UIDevice.current.userInterfaceIdiom != .pad + } + + private lazy var shouldShowNetPCell: Bool = { +#if NETWORK_PROTECTION + if #available(iOS 15, *) { + return featureFlagger.isFeatureOn(.networkProtection) + } else { + return false + } +#else + return false +#endif + }() + + // MARK: + var appIconSubscription: AnyCancellable? + + @Published private(set) var state: SettingsState + + init(bookmarksDatabase: CoreDataDatabase, + syncService: DDGSyncing, + syncDataProviders: SyncDataProviders, + internalUserDecider: InternalUserDecider, + state: SettingsState = SettingsState()) { + self.bookmarksDatabase = bookmarksDatabase + self.syncService = syncService + self.syncDataProviders = syncDataProviders + self.internalUserDecider = internalUserDecider + self.state = state + configureView() + } + + func configureView() { + state.shouldShowSyncCell = featureFlagger.isFeatureOn(.sync) + state.shouldShowLoginsCell = featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) + state.appTheme = appSettings.currentThemeName + state.appIcon = AppIconManager.shared.appIcon + createAppIconSubscriber() + state.fireButtonAnimation = appSettings.currentFireButtonAnimation + } + + private func createAppIconSubscriber() { + appIconSubscription = AppIconManager.shared.$appIcon + .sink { newIcon in + self.state.appIcon = newIcon + } + } + + func openCookiePopupManagement() { + // showCookiePopupManagement(animated: true) + } + +} + +// MARK: User Actions +extension SettingsViewModel { + + func setAsDefaultBrowser() { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + } + + func setIsPresentingAddToDockView(_ value: Bool) { + state.isPresentingAddToDockView = value + } + + func setIsPresentingAddWidgetView(_ value: Bool) { + state.isPresentingAddWidgetView = value + } + + func setIsPresentingSyncView(_ value: Bool) { + state.isPresentingSyncView = value + } + + func setIsPresentingLoginsView(_ value: Bool) { + state.isPresentingLoginsView = value + if !state.isPresentingLoginsView { + Pixel.fire(pixel: .autofillSettingsOpened) + } + } + + func setIsPresentingLoginsViewWithAccount(accountDetails: SecureVaultModels.WebsiteAccount) { + state.loginsViewSelectedAccount = accountDetails + setIsPresentingLoginsView(true) + } + + func setTheme(theme: ThemeName) { + appSettings.currentThemeName = theme + state.appTheme = theme + ThemeManager.shared.enableTheme(with: theme) + ThemeManager.shared.updateUserInterfaceStyle() + } + + func setIsPresentingAppIconView(_ value: Bool) { + state.isPresentingAppIconView = value + } + + func setFireButtonAnimation(_ value: FireButtonAnimationType, showAnimation: Bool = true) { + appSettings.currentFireButtonAnimation = value + NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) + + if showAnimation { + animator.animate { + // no op + } onTransitionCompleted: { + // no op + } completion: { + // no op + } + } + + } + + func selectFireAnimation() {} + func selectTextSize() {} + func selectBarPosition() {} +} + +extension SettingsViewModel { + static var fontSizeForHeaderView: CGFloat { + let contentSize = UIApplication.shared.preferredContentSizeCategory + switch contentSize { + case .extraSmall: + return 12 + case .small: + return 12 + case .medium: + return 12 + case .large: + return 13 + case .extraLarge: + return 15 + case .extraExtraLarge: + return 17 + case .extraExtraExtraLarge: + return 19 + case .accessibilityMedium: + return 23 + case .accessibilityLarge: + return 27 + case .accessibilityExtraLarge: + return 33 + case .accessibilityExtraExtraLarge: + return 38 + case .accessibilityExtraExtraExtraLarge: + return 44 + default: + return 13 + } + } +} + +extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { + func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { + state.isPresentingLoginsView = false + } +} diff --git a/DuckDuckGo/Theme.swift b/DuckDuckGo/Theme.swift index e145c51444..61a5cce0af 100644 --- a/DuckDuckGo/Theme.swift +++ b/DuckDuckGo/Theme.swift @@ -19,10 +19,12 @@ import UIKit -enum ThemeName: String { - case systemDefault - case light - case dark +enum ThemeName: String, CaseIterable, Identifiable { + case systemDefault = "System Default" + case light = "Light" + case dark = "Dark" + + var id: String { self.rawValue } } protocol Theme { diff --git a/DuckDuckGo/UnprotectedSitesViewController.swift b/DuckDuckGo/UnprotectedSitesViewController.swift index 9a25969853..4fc53ac499 100644 --- a/DuckDuckGo/UnprotectedSitesViewController.swift +++ b/DuckDuckGo/UnprotectedSitesViewController.swift @@ -48,7 +48,7 @@ class UnprotectedSitesViewController: UITableViewController { configureBackButton() - let fontSize = SettingsViewController.fontSizeForHeaderView + let fontSize = SettingsViewModel.fontSizeForHeaderView let text = NSAttributedString(string: infoText.text ?? "", attributes: [ NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize) ]) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index e603b23c24..56065623f2 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -937,4 +937,9 @@ But if you *do* want a peek under the hood, you can find more information about static let networkProtectionNotificationPromptTitle = NSLocalizedString("network-protection.waitlist.notification-prompt-title", value: "Know the instant you're invited", comment: "Title for the alert to confirm enabling notifications") static let networkProtectionNotificationPromptDescription = NSLocalizedString("network-protection.waitlist.notification-prompt-description", value: "Get a notification when your copy of Network Protection early access is ready.", comment: "Subtitle for the alert to confirm enabling notifications") + + // Settings + public static let settingsTitle = NSLocalizedString("settings.title", value: "Settings", comment: "Title for the Settings View") + + public static let settingsAddWidgetTitle = NSLocalizedString("settings.general.add.widget.title", value: "Add Widget to Home Screen", comment: "Title for the Add Widget to home screen view") } diff --git a/DuckDuckGo/View+onChageSkippingFirst.swift b/DuckDuckGo/View+onChageSkippingFirst.swift new file mode 100644 index 0000000000..a726115b59 --- /dev/null +++ b/DuckDuckGo/View+onChageSkippingFirst.swift @@ -0,0 +1,44 @@ +// +// View+onChageSkippingFirst.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 + +/// Implements a custom modifier to implement .onChange events that skip the first update + +@propertyWrapper +struct SkipFirstUpdate where Value: Equatable { + private var value: Value? + private var isInitialUpdate = true + + var wrappedValue: Value? { + didSet { + guard !isInitialUpdate, oldValue != wrappedValue else { + isInitialUpdate = false + return + } + projectedValue?(wrappedValue) + } + } + + var projectedValue: ((Value?) -> Void)? + + init(wrappedValue: Value?) { + self.value = wrappedValue + } +} diff --git a/DuckDuckGo/WidgetEducationView.swift b/DuckDuckGo/WidgetEducationView.swift index 0d0bfbf2ea..6dce4e57d6 100644 --- a/DuckDuckGo/WidgetEducationView.swift +++ b/DuckDuckGo/WidgetEducationView.swift @@ -46,7 +46,7 @@ struct WidgetEducationView: View { .padding(.horizontal) .padding(.top, Const.Padding.top) } - } + }.navigationBarTitle("Add Widget to Home Screen", displayMode: .inline) } private var secondParagraphText: Text { diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index b9cf565057..b6a79dedf5 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1780,6 +1780,12 @@ Our privacy protections work without having to know anything about the technical But if you *do* want a peek under the hood, you can find more information about how DuckDuckGo privacy protections work on our [help pages](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Title for the Add Widget to home screen view */ +"settings.general.add.widget.title" = "Add Widget to Home Screen"; + +/* Title for the Settings View */ +"settings.title" = "Settings"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Submit Report"; From f85324f7dc7caeb73525adef3e56fe00336fe200 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 7 Dec 2023 03:14:47 +0100 Subject: [PATCH 02/99] Extract Model and State to separate classes --- DuckDuckGo.xcodeproj/project.pbxproj | 28 ++- .../AppIconSettingsViewController.swift | 24 ++ ...ofillLoginSettingsListViewController.swift | 41 +++- DuckDuckGo/Base.lproj/Settings.storyboard | 124 +---------- DuckDuckGo/FireButtonAnimator.swift | 6 +- DuckDuckGo/MainViewController+Segues.swift | 71 +++++- DuckDuckGo/NavigationLink+Empty.swift | 29 +++ DuckDuckGo/SettingsAppeareanceView.swift | 91 +++----- DuckDuckGo/SettingsCell.swift | 176 +++++++++------ DuckDuckGo/SettingsGeneralView.swift | 55 ++--- DuckDuckGo/SettingsHostingController.swift | 56 +++++ DuckDuckGo/SettingsLoginsView.swift | 63 +----- DuckDuckGo/SettingsModel.swift | 106 ++++++++- DuckDuckGo/SettingsState.swift | 33 +++ DuckDuckGo/SettingsSyncView.swift | 48 +--- DuckDuckGo/SettingsView.swift | 25 ++- DuckDuckGo/SettingsViewController.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 209 +++++++----------- DuckDuckGo/SyncSettingsViewController.swift | 27 +++ .../TextSizeSettingsViewController.swift | 2 + DuckDuckGo/Theme.swift | 3 +- 21 files changed, 680 insertions(+), 539 deletions(-) create mode 100644 DuckDuckGo/NavigationLink+Empty.swift create mode 100644 DuckDuckGo/SettingsHostingController.swift create mode 100644 DuckDuckGo/SettingsState.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8ee2304dff..3bcc6b9af4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -775,6 +775,10 @@ D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */; }; D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */; }; D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */; }; + D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */; }; + D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; + D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; + D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2406,6 +2410,10 @@ D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSyncView.swift; sourceTree = ""; }; D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLoginsView.swift; sourceTree = ""; }; D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppeareanceView.swift; sourceTree = ""; }; + D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHostingController.swift; sourceTree = ""; }; + D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; + D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; + D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4497,10 +4505,11 @@ D6E83C3B2B1F27BA006C8AFB /* Views */ = { isa = PBXGroup; children = ( - D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, + D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */, D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */, D6E83C302B1EA309006C8AFB /* SettingsCell.swift */, D6E83C322B1F1279006C8AFB /* Sections */, + D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */, ); name = Views; sourceTree = ""; @@ -4512,6 +4521,16 @@ name = Extensions; sourceTree = ""; }; + D6E83C492B20C883006C8AFB /* Model */ = { + isa = PBXGroup; + children = ( + D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */, + D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */, + D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, + ); + name = Model; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5140,8 +5159,9 @@ F1AB2B401E3F75A000868554 /* Settings */ = { isa = PBXGroup; children = ( - D6E83C442B1FFCF9006C8AFB /* Extensions */, + D6E83C492B20C883006C8AFB /* Model */, D6E83C3B2B1F27BA006C8AFB /* Views */, + D6E83C442B1FFCF9006C8AFB /* Extensions */, 858566F1252E55AE007501B8 /* Debug */, 85449EF723FDA03D00512AAF /* Model */, 85449EF623FDA03100512AAF /* UI */, @@ -6334,8 +6354,10 @@ 3151F0F02735802800226F58 /* VoiceSearchViewController.swift in Sources */, 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */, F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, + D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, B6BA95C328891E33004ABA20 /* BrowsingMenuAnimator.swift in Sources */, + D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, @@ -6482,6 +6504,7 @@ 85374D3C21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift in Sources */, 85DFEDF124C7EEA400973FE7 /* LargeOmniBarState.swift in Sources */, 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */, + D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, @@ -6587,6 +6610,7 @@ F1CA3C391F045885005FADB3 /* PrivacyUserDefaults.swift in Sources */, AA4D6A6A23DB87B1007E8790 /* AppIconManager.swift in Sources */, 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */, + D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */, 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, diff --git a/DuckDuckGo/AppIconSettingsViewController.swift b/DuckDuckGo/AppIconSettingsViewController.swift index e80e9c69f6..f9ead3ea68 100644 --- a/DuckDuckGo/AppIconSettingsViewController.swift +++ b/DuckDuckGo/AppIconSettingsViewController.swift @@ -19,6 +19,30 @@ import UIKit import Core +import SwiftUI + +// MARK: App Icon Settings Representable +struct AppIconSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = AppIconSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> AppIconSettingsViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "AppIcon") as! AppIconSettingsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: AppIconSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} class AppIconSettingsViewController: UICollectionViewController { diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index 4ead6d37fe..f3765d11a8 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -24,9 +24,48 @@ import BrowserServicesKit import Common import DDGSync import DesignResourcesKit +import SwiftUI // swiftlint:disable file_length type_body_length +struct AutofillLoginSettingsListViewControllerRepresentable: UIViewControllerRepresentable { + + let appSettings: AppSettings + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + let delegate: AutofillLoginSettingsListViewControllerDelegate + let selectedAccount: SecureVaultModels.WebsiteAccount? + + typealias UIViewControllerType = AutofillLoginSettingsListViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> AutofillLoginSettingsListViewController { + let autofillController = AutofillLoginSettingsListViewController( + appSettings: appSettings, + syncService: syncService, + syncDataProviders: syncDataProviders, + selectedAccount: selectedAccount + ) + + context.coordinator.parentObserver = autofillController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems + + }) + + return autofillController + } + + func updateUIViewController(_ uiViewController: AutofillLoginSettingsListViewController, context: Self.Context) { + + } + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} + protocol AutofillLoginSettingsListViewControllerDelegate: AnyObject { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) } @@ -302,7 +341,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { private func showSelectedAccountIfRequired() { if let account = selectedAccount { - showAccountDetails(account, animated: false) + showAccountDetails(account) selectedAccount = nil } } diff --git a/DuckDuckGo/Base.lproj/Settings.storyboard b/DuckDuckGo/Base.lproj/Settings.storyboard index 1532ebc469..e15aba85b6 100644 --- a/DuckDuckGo/Base.lproj/Settings.storyboard +++ b/DuckDuckGo/Base.lproj/Settings.storyboard @@ -171,9 +171,6 @@ - - - @@ -256,9 +253,6 @@ - - - @@ -1107,7 +1101,7 @@ - + @@ -1723,62 +1717,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2230,66 +2168,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/FireButtonAnimator.swift b/DuckDuckGo/FireButtonAnimator.swift index adaa4742c1..e153dcc30c 100644 --- a/DuckDuckGo/FireButtonAnimator.swift +++ b/DuckDuckGo/FireButtonAnimator.swift @@ -20,7 +20,11 @@ import UIKit import Lottie -enum FireButtonAnimationType: String, CaseIterable, Identifiable { +enum FireButtonAnimationType: String, CaseIterable, Identifiable, CustomStringConvertible { + + var description: String { + return descriptionText + } case fireRising case waterSwirl diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index c272747966..2da24ee150 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -204,24 +204,56 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.setIsPresentingSyncView(true) + $0.isPresentingSyncView = true } } + + class HostingControllerCommunicator: ObservableObject { + var pushView: (() -> Void)? + + func requestPush() { + pushView?() + } + } + + private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { + let model = SettingsModel(bookmarksDatabase: self.bookmarksDatabase, + syncService: self.syncService, + syncDataProviders: self.syncDataProviders, + internalUserDecider: AppDependencyProvider.shared.internalUserDecider) + let settingsViewModel = SettingsViewModel(model: model) + let settingsController = SettingsHostingController(viewModel: settingsViewModel) + + // We are still presenting legacy views, so use a Navcontroller + let navController = UINavigationController(rootViewController: settingsController) + settingsController.modalPresentationStyle = .automatic + present(navController, animated: true) { + completion?(settingsViewModel) + } + } + /* private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { os_log(#function, log: .generalLog, type: .debug) let settingsModel = SettingsViewModel(bookmarksDatabase: self.bookmarksDatabase, syncService: self.syncService, syncDataProviders: self.syncDataProviders, internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - let settingsView = SettingsView(viewModel: settingsModel) - let settingsController = UIHostingController(rootView: settingsView) + + let settingsController = SettingsHostingController(viewModel: settingsModel, rootView: AnyView(EmptyView())) + settingsController.viewModel = settingsModel + + let settingsView = SettingsView(viewModel: settingsModel) { [weak settingsController] legacyVC in + settingsController?.pushLegacyViewController(legacyVC) + } + settingsController.rootView = AnyView(settingsView) settingsController.modalPresentationStyle = .automatic present(settingsController, animated: true) { completion?(settingsModel) } } + */ private func hideAllHighlightsIfNeeded() { os_log(#function, log: .generalLog, type: .debug) @@ -229,4 +261,37 @@ extension MainViewController { ViewHighlighter.hideAll() } } + + + func presentTextSizeSettings() { + if let presentingVC = self.presentedViewController { + presentingVC.dismiss(animated: true) { [weak self] in + self?.presentTextSizeSettingsViewController() + } + } else { + presentTextSizeSettingsViewController() + } + } + + private func presentTextSizeSettingsViewController() { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController + Pixel.fire(pixel: .textSizeSettingsShown) + presentationController?.delegate = viewController + + if #available(iOS 15.0, *) { + // Configure settingsVC as a sheet + viewController.modalPresentationStyle = .pageSheet + viewController.modalTransitionStyle = .coverVertical + + let presentationController = viewController.presentationController as? UISheetPresentationController + presentationController?.detents = [.medium(), .large()] + presentationController?.preferredCornerRadius = 16 + presentationController?.largestUndimmedDetentIdentifier = .medium + presentationController?.prefersScrollingExpandsWhenScrolledToEdge = false + + } + + self.present(viewController, animated: true, completion: nil) + } } diff --git a/DuckDuckGo/NavigationLink+Empty.swift b/DuckDuckGo/NavigationLink+Empty.swift new file mode 100644 index 0000000000..2d089f7a40 --- /dev/null +++ b/DuckDuckGo/NavigationLink+Empty.swift @@ -0,0 +1,29 @@ +// +// NavigationLink+Empty.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 + +extension NavigationLink where Label == EmptyView, Destination == EmptyView { + + /// Useful in cases where a `NavigationLink` is needed but there should not be + /// a destination. e.g. for programmatic navigation. + static var empty: NavigationLink { + self.init(destination: EmptyView(), label: { EmptyView() }) + } +} diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index 5b5c65eef6..2757296db5 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -21,76 +21,49 @@ import SwiftUI import UIKit struct SettingsAppeareanceView: View { - + @EnvironmentObject var viewModel: SettingsViewModel - @State var selectedTheme: ThemeName = .systemDefault + @State var setIsPresentingAppIconView: Bool = false - @State var selectedFireButtonAnimation: FireButtonAnimationType = .fireRising - @State private var isFirstUpdate = true + + @State var selectedTheme: ThemeName = .systemDefault + @State var selectedTextSize: Int = 100 + var body: some View { Section(header: Text("Appeareance")) { - Picker("Theme", selection: $selectedTheme) { - ForEach(ThemeName.allCases) { option in - Text(option.rawValue).tag(option) - } - } + SettingsPickerCellView(label: "Theme", + options: ThemeName.allCases, + selectedOption: Binding( + get: { viewModel.state.general.appTheme }, + set: { viewModel.setTheme($0) } + )) + NavigationLink(destination: AppIconSettingsViewControllerRepresentable(), isActive: $setIsPresentingAppIconView) { - ImageCell(label: "App Icon", image: Image(uiImage: viewModel.state.appIcon.smallImage ?? UIImage())) + let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) + SettingsCellView(label: "App Icon", + accesory: .image(image)) } - Picker("Fire Button Animation", selection: $selectedFireButtonAnimation) { - ForEach(FireButtonAnimationType.allCases) { option in - Text(option.descriptionText).tag(option) - } - } + SettingsPickerCellView(label: "Fire Button Animation", + options: FireButtonAnimationType.allCases, + selectedOption: Binding( + get: { viewModel.state.general.fireButtonAnimation }, + set: { viewModel.setFireButtonAnimation($0) } + )) - /* - RightDetailCell(label: "Text Size", value: "100%", action: viewModel.selectTextSize) - RightDetailCell(label: "Address Bar Position", value: "Top", action: viewModel.selectBarPosition) - */ - } - - .onAppear { - selectedTheme = viewModel.state.appTheme - selectedFireButtonAnimation = viewModel.state.fireButtonAnimation - isFirstUpdate = true - } - - .onChange(of: selectedTheme) { newValue in - viewModel.setTheme(theme: newValue) - } - - .onChange(of: selectedFireButtonAnimation) { newValue in - if isFirstUpdate { - isFirstUpdate = false - viewModel.setFireButtonAnimation(newValue, showAnimation: false) - } else { - viewModel.setFireButtonAnimation(newValue) + // The textsize settings view has a special behavior (detent adjustment) that requires access to a navigation controller + // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container + if viewModel.shouldShowTextSizeCell { + SettingsCellView(label: "Text Size", + action: { viewModel.shouldPresentTextSettingsView() }, + accesory: .rightDetail("\(selectedTextSize)%"), + asLink: true) } + + // RightDetailCell(label: "Address Bar Position", value: "Top", action: viewModel.selectBarPosition) } - - } -} - -struct AppIconSettingsViewControllerRepresentable: UIViewControllerRepresentable { - typealias UIViewControllerType = AppIconSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> AppIconSettingsViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "AppIcon") as! AppIconSettingsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController + } - - func updateUIViewController(_ uiViewController: AppIconSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } } diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 85899b58d1..80fee2f55c 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -19,97 +19,139 @@ import SwiftUI -protocol SettingsCell { - var label: String { get set } - var action: () -> Void { get set } - var enabled: Bool { get set } -} - -struct PlainCell: View, SettingsCell { +/// Encapsulates a View representing a Cell with different configurations +struct SettingsCellView: View { + enum Accesory { + case none + case rightDetail(String) + case toggle(isOn: Binding) + case image(Image) + case subtitle(String) + case custom(AnyView) + } var label: String var action: () -> Void = {} - var disclosureIndicator: Bool = true var enabled: Bool = true + var accesory: Accesory + var asLink: Bool + var disclosureIndicator: Bool - var body: some View { - Button(action: action) { - Text(label) - }.disabled(!enabled) + /// Initializes a `SettingsCellView` with the specified label and accesory. + /// + /// Use this initializer for standard cell types that require a label. + /// - Parameters: + /// - label: The text to display in the cell. + /// - action: The closure to execute when the cell is tapped. + /// - accesory: The type of cell to display. Excludes the custom cell type. + /// - enabled: A Boolean value that determines whether the cell is enabled. + /// - asLink: Wraps the view inside a Button. Used for views not wrapped in a NavigationLink + /// - disclosureIndicator: Forces Adds a disclosure indicator on the right (chevron) + init(label: String, action: @escaping () -> Void = {}, accesory: Accesory = .none, enabled: Bool = true, asLink: Bool = false, disclosureIndicator: Bool = false) { + self.label = label + self.action = action + self.enabled = enabled + self.accesory = accesory + self.asLink = asLink + self.disclosureIndicator = disclosureIndicator } -} -struct RightDetailCell: View, SettingsCell { - - var label: String - var value: String - var action: () -> Void = {} - var disclosureIndicator: Bool = true - var enabled: Bool = true + /// Initializes a `SettingsCellView` for custom content. + /// + /// Use this initializer for creating a cell that displays custom content. + /// This initializer does not require a label, as the content is entirely custom. + /// - Parameters: + /// - action: The closure to execute when the cell is tapped. + /// - customView: A closure that returns the custom view (`AnyView`) to be displayed in the cell. + /// - enabled: A Boolean value that determines whether the cell is enabled. + init(action: @escaping () -> Void = {}, @ViewBuilder customView: () -> AnyView, enabled: Bool = true) { + self.label = "" // Not used for custom cell + self.action = action + self.enabled = enabled + self.accesory = .custom(customView()) + self.asLink = false + self.disclosureIndicator = false + } var body: some View { - Button(action: action) { + Group { + switch accesory { + case .custom(let customView): + Button(action: action) { customView } + .buttonStyle(.plain) + .disabled(!enabled) + + default: + if asLink { + Button(action: action) { defaultView } + .buttonStyle(.plain) + } else { + defaultView + } + } + } + } + + private var defaultView: some View { + Group { HStack { Text(label) Spacer() - Text(value) + cellView() + if disclosureIndicator { + Image(systemName: "chevron.forward") + .font(Font.system(.footnote).weight(.bold)) + .foregroundColor(Color(UIColor.tertiaryLabel)) + } } - }.disabled(!enabled) + }.contentShape(Rectangle()) } -} - -struct ToggleCell: View, SettingsCell { - - var label: String - var action: () -> Void = {} - var disclosureIndicator: Bool = true - var enabled: Bool = true - @State var value: Bool = false - var body: some View { - Toggle(isOn: $value) { - Text(label) + @ViewBuilder + private func cellView() -> some View { + switch accesory { + case .none: + EmptyView() + case .rightDetail(let value): + Text(value).foregroundColor(Color(UIColor.tertiaryLabel)) + case .toggle(let isOn): + Toggle("", isOn: isOn) + case .image(let image): + image + .resizable() + .scaledToFit() + .frame(width: 25, height: 25) + case .subtitle(let subtitle): + Text(subtitle).font(.subheadline) + case .custom(let customView): + customView } } } -struct ImageCell: View, SettingsCell { - - var label: String - var image: Image - var action: () -> Void = {} - var disclosureIndicator: Bool = true - var enabled: Bool = true +/// Encapsulates a Picker with options derived from a generic type that conforms to CustomStringConvertible. +struct SettingsPickerCellView: View { + let label: String + let options: [T] + @Binding var selectedOption: T - var body: some View { - Button(action: action) { - HStack { - Text(label) - Spacer() - image - .resizable() - .scaledToFit() - .frame(width: 25, height: 25) // Adjust the size as needed - } - }.disabled(!enabled) + /// Initializes a SettingsPickerCellView. + /// - Parameters: + /// - label: The label to display above the Picker. + /// - options: An array of options of generic type `T` that conforms to CustomStringConvertible. + /// - selectedOption: A binding to a local state variable that represents the selected option. + init(label: String, options: [T], selectedOption: Binding) { + self.label = label + self.options = options + self._selectedOption = selectedOption } -} -struct SubtitleCell: View, SettingsCell { - - var label: String - var subtitle: String - var action: () -> Void = {} - var disclosureIndicator: Bool = true - var enabled: Bool - var body: some View { - Button(action: action) { - HStack { - Text(label) - Text(subtitle).font(.subheadline) + Picker(label, selection: $selectedOption) { + ForEach(options, id: \.self) { option in + Text(option.description).tag(option) } - .disabled(!enabled) + .pickerStyle(MenuPickerStyle()) } } } diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index fb600e4c85..e688cc9103 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -23,58 +23,31 @@ import UIKit struct SettingsGeneralView: View { @EnvironmentObject var viewModel: SettingsViewModel - @State var isPresentingAddToDockView: Bool = false @State var isPresentingAddWidgetView: Bool = false var body: some View { Section { - PlainCell(label: "Set as Default Browser", action: viewModel.setAsDefaultBrowser) - PlainCell(label: "Add App to Your Dock", action: { - { viewModel.setIsPresentingAddToDockView(true) }() - }) + // The homeRow view controller has + // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container + SettingsCellView(label: "Set as Default Browser", + action: { viewModel.setAsDefaultBrowser() }, + asLink: true) + + SettingsCellView(label: "Add App to Your Dock", + action: { viewModel.shouldPresentAddToDockView() }, + asLink: true) + NavigationLink(destination: WidgetEducationView(), isActive: $isPresentingAddWidgetView) { - PlainCell(label: "Add Widget to Home Screen", action: { viewModel.setIsPresentingAddWidgetView(true) }) + SettingsCellView(label: "Add Widget to Home Screen", + action: { viewModel.isPresentingAddWidgetView = true }) } } - .onChange(of: viewModel.state.isPresentingAddToDockView) { newValue in - isPresentingAddToDockView = newValue - } - - .onChange(of: isPresentingAddToDockView) { newValue in - viewModel.setIsPresentingAddToDockView(newValue) - } - .onChange(of: isPresentingAddWidgetView) { newValue in - viewModel.setIsPresentingAddWidgetView(newValue) + viewModel.isPresentingAddWidgetView = newValue } - // Modal View - .fullScreenCover(isPresented: $isPresentingAddToDockView) { - HomeRowInstructionsViewControllerRepresentable() - } - } - -} -struct HomeRowInstructionsViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = HomeRowInstructionsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> HomeRowInstructionsViewController { - let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController } - - func updateUIViewController(_ uiViewController: HomeRowInstructionsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } + } diff --git a/DuckDuckGo/SettingsHostingController.swift b/DuckDuckGo/SettingsHostingController.swift new file mode 100644 index 0000000000..7882436a2c --- /dev/null +++ b/DuckDuckGo/SettingsHostingController.swift @@ -0,0 +1,56 @@ +// +// SettingsHostingController.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 + +class SettingsHostingController: UIHostingController { + var viewModel: SettingsViewModel + + init(viewModel: SettingsViewModel) { + self.viewModel = viewModel + super.init(rootView: AnyView(EmptyView())) + + viewModel.onRequestPushLegacyView = { [weak self] vc in + self?.pushLegacyViewController(vc) + } + + viewModel.onRequestPresentLegacyView = { [weak self] vc, modal in + self?.presentLegacyViewCOntroller(vc, modal: modal) + } + + let settingsView = SettingsView(viewModel: viewModel) + self.rootView = AnyView(settingsView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func pushLegacyViewController(_ vc: UIViewController) { + navigationController?.pushViewController(vc, animated: true) + } + + func presentLegacyViewCOntroller(_ vc: UIViewController, modal: Bool = false) { + if modal { + vc.modalPresentationStyle = .fullScreen + } + navigationController?.present(vc, animated: true) + } +} diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index 7cd39a9b73..5478e3cb38 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -29,72 +29,17 @@ struct SettingsLoginsView: View { @State var isPresentingLoginsView: Bool = false var body: some View { - if viewModel.state.shouldShowLoginsCell { - let autofillController = AutofillLoginSettingsListViewControllerRepresentable(appSettings: viewModel.appSettings, - syncService: viewModel.syncService, - syncDataProviders: viewModel.syncDataProviders, - delegate: viewModel, - selectedAccount: viewModel.state.loginsViewSelectedAccount) + if viewModel.shouldShowLoginsCell { Section { // TODO: Remove transition animation if showing a selected account - NavigationLink(destination: autofillController, isActive: $isPresentingLoginsView) { - PlainCell(label: UserText.autofillLoginListTitle, action: { viewModel.setIsPresentingLoginsView(true) }) + NavigationLink(destination: viewModel.autofillControllerRepresentable, isActive: $isPresentingLoginsView) { + SettingsCellView(label: UserText.autofillLoginListTitle, + action: { viewModel.isPresentingLoginsView = true }) } } - - .onAppear { - isPresentingLoginsView = viewModel.state.isPresentingLoginsView - } - - .onChange(of: isPresentingLoginsView) { _ in - viewModel.setIsPresentingLoginsView(true) - } - - .onChange(of: viewModel.state.isPresentingLoginsView) { newValue in - isPresentingLoginsView = newValue - } } } } - - -struct AutofillLoginSettingsListViewControllerRepresentable: UIViewControllerRepresentable { - - let appSettings: AppSettings - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - let delegate: AutofillLoginSettingsListViewControllerDelegate - let selectedAccount: SecureVaultModels.WebsiteAccount? - - typealias UIViewControllerType = AutofillLoginSettingsListViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> AutofillLoginSettingsListViewController { - let autofillController = AutofillLoginSettingsListViewController( - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, - selectedAccount: selectedAccount - ) - - context.coordinator.parentObserver = autofillController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems - - }) - - return autofillController - } - - func updateUIViewController(_ uiViewController: AutofillLoginSettingsListViewController, context: Self.Context) { - - } - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index f1de1c53d2..65c4eac7b4 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -1,5 +1,5 @@ // -// SettingsModel.swift +// SettingsState.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -17,4 +17,106 @@ // limitations under the License. // -import SwiftUI +import Foundation +import BrowserServicesKit +import Persistence +import Common +import DDGSync +import Combine +import UIKit + +#if APP_TRACKING_PROTECTION +import NetworkExtension +#endif + +#if NETWORK_PROTECTION +import NetworkProtection +import Core +#endif + +class SettingsModel { + + // MARK: Dependencies + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + let appIconManager = AppIconManager.shared + + private let bookmarksDatabase: CoreDataDatabase + private let internalUserDecider: InternalUserDecider + private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger + private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) + private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings + + // MARK: Other Properties + private lazy var isPad = UIDevice.current.userInterfaceIdiom != .pad + #if NETWORK_PROTECTION + private let connectionObserver = ConnectionStatusObserverThroughSession() + #endif + private var cancellables: Set = [] + + init(bookmarksDatabase: CoreDataDatabase, + syncService: DDGSyncing, + syncDataProviders: SyncDataProviders, + internalUserDecider: InternalUserDecider) { + self.bookmarksDatabase = bookmarksDatabase + self.syncService = syncService + self.syncDataProviders = syncDataProviders + self.internalUserDecider = internalUserDecider + } + + enum Features { + case sync + case autofillAccessCredentialManagement + case textSize + #if NETWORK_PROTECTION + case networkProtection + #endif + case voiceSearch + case addressbarPosition + + } + + func isFeatureAvailable(_ feature: Features) -> Bool { + switch feature { + case .sync: + return featureFlagger.isFeatureOn(.sync) + case .autofillAccessCredentialManagement: + return featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) + case .textSize: + return !isPad + + #if NETWORK_PROTECTION + case .networkProtection: + if #available(iOS 15, *) { + return featureFlagger.isFeatureOn(.networkProtection) + } else { + return false + } + #endif + + case .voiceSearch: + return AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable + case .addressbarPosition: + return !isPad + } + } + + func setTheme(theme: ThemeName) { + ThemeManager.shared.enableTheme(with: theme) + ThemeManager.shared.updateUserInterfaceStyle() + } + + func setFireButtonAnimetion(_ value: FireButtonAnimationType) { + appSettings.currentFireButtonAnimation = value + NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) + + animator.animate { + // no op + } onTransitionCompleted: { + // no op + } completion: { + // no op + } + } + +} diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift new file mode 100644 index 0000000000..392b6f8546 --- /dev/null +++ b/DuckDuckGo/SettingsState.swift @@ -0,0 +1,33 @@ +// +// SettingsState.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 + +struct SettingsState { + var general: SettingsStateGeneral + // Add state for other sections here... +} + +struct SettingsStateGeneral { + var appTheme: ThemeName = ThemeManager.shared.currentTheme.name + var appIcon: AppIcon = AppIconManager.shared.appIcon + var fireButtonAnimation: FireButtonAnimationType = .fireRising + var textSize: Int = 100 + var activeWebsiteAccount: SecureVaultModels.WebsiteAccount? +} diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index 8d0ee40433..e72c7c674b 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -28,53 +28,13 @@ struct SettingsSyncView: View { @State var isPresentingSyncView: Bool = false var body: some View { - let syncSettingsController = SyncSettingsViewControllerRepresentable(syncService: viewModel.syncService, - syncDataProviders: viewModel.syncDataProviders) - if viewModel.state.shouldShowSyncCell { + if viewModel.shouldShowSyncCell { Section { - NavigationLink(destination: syncSettingsController, isActive: $isPresentingSyncView) { - PlainCell(label: UserText.syncTitle, action: { viewModel.setIsPresentingSyncView(true) }) + NavigationLink(destination: viewModel.syncSettingsControllerRepresentable, isActive: $isPresentingSyncView) { + SettingsCellView(label: UserText.syncTitle, + action: { viewModel.isPresentingSyncView = true }) } } - - .onAppear { - isPresentingSyncView = viewModel.state.isPresentingSyncView - } - - .onChange(of: viewModel.state.isPresentingSyncView) { newValue in - isPresentingSyncView = newValue - } - - .onChange(of: isPresentingSyncView) { newValue in - viewModel.setIsPresentingSyncView(newValue) - } } } } - -// TODO: Sync is already SwiftUI - This should be migrated to a SwiftUI View -struct SyncSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - - typealias UIViewControllerType = SyncSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Context) -> SyncSettingsViewController { - let viewController = SyncSettingsViewController(syncService: syncService, - syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems - }) - return viewController - } - - func updateUIViewController(_ uiViewController: SyncSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index f010ac6737..8e7972cd5f 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -25,19 +25,20 @@ struct SettingsView: View { @StateObject var viewModel: SettingsViewModel var body: some View { - NavigationView { - List { - SettingsGeneralView() - SettingsSyncView() - SettingsLoginsView() - SettingsAppeareanceView() - } - .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) - .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { - }) - .environmentObject(viewModel) + List { + SettingsGeneralView() + SettingsSyncView() + SettingsLoginsView() + SettingsAppeareanceView() + } + .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) + .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { + }) + .environmentObject(viewModel) + + .onAppear { + viewModel.configureView() } - } } diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 18bc4c8f44..6c343f4d11 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -292,7 +292,7 @@ class SettingsViewController: UITableViewController { private func configureTextSizeCell() { textSizeCell.isHidden = !shouldShowTextSizeCell - textSizeAccessoryText.text = "\(appSettings.textSize)%" + textSizeAccessoryText.text = "\(appSettings.textSize)s" } private func configureIconViews() { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 5898615ada..8e0e8d56cf 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -33,101 +33,52 @@ import NetworkExtension import NetworkProtection #endif -struct SettingsState { - var isPresentingAddToDockView = false - var isPresentingAddWidgetView = false - var isPresentingSyncView = false - var isPresentingLoginsView = false - var loginsViewSelectedAccount: SecureVaultModels.WebsiteAccount? - var isPresentingAppIconView = false - - var shouldShowSyncCell = false - var shouldShowLoginsCell = false - var appTheme: ThemeName = .systemDefault - var appIcon: AppIcon = .defaultAppIcon - var fireButtonAnimation: FireButtonAnimationType = .fireRising -} - final class SettingsViewModel: ObservableObject { - - private let bookmarksDatabase: CoreDataDatabase - private lazy var emailManager = EmailManager() - private lazy var versionProvider: AppVersion = AppVersion.shared - fileprivate lazy var privacyStore = PrivacyUserDefaults() - private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) - fileprivate lazy var variantManager = AppDependencyProvider.shared.variantManager - fileprivate lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger - - private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - - fileprivate let internalUserDecider: InternalUserDecider - -#if NETWORK_PROTECTION - private let connectionObserver = ConnectionStatusObserverThroughSession() -#endif - private var cancellables: Set = [] - - private var shouldShowDebugCell: Bool { - return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild - } - - private var shouldShowVoiceSearchCell: Bool { - AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable - } - - private var shouldShowTextSizeCell: Bool { - return UIDevice.current.userInterfaceIdiom != .pad - } - - private var shouldShowAddressBarPositionCell: Bool { - return UIDevice.current.userInterfaceIdiom != .pad - } - - private lazy var shouldShowNetPCell: Bool = { -#if NETWORK_PROTECTION - if #available(iOS 15, *) { - return featureFlagger.isFeatureOn(.networkProtection) - } else { - return false - } -#else - return false -#endif - }() // MARK: var appIconSubscription: AnyCancellable? + var stateSubscriber: AnyCancellable? + + // Closure to request a legacy view controller presentation + var onRequestPushLegacyView: ((UIViewController) -> Void)? + var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? + private(set) var model: SettingsModel @Published private(set) var state: SettingsState - init(bookmarksDatabase: CoreDataDatabase, - syncService: DDGSyncing, - syncDataProviders: SyncDataProviders, - internalUserDecider: InternalUserDecider, - state: SettingsState = SettingsState()) { - self.bookmarksDatabase = bookmarksDatabase - self.syncService = syncService - self.syncDataProviders = syncDataProviders - self.internalUserDecider = internalUserDecider + // MARK: Presentation + var isPresentingAddToDockView: Bool = false + var isPresentingAddWidgetView = false + var isPresentingSyncView = false + var isPresentingLoginsView = false + var isPresentingAppIconView = false + var isPresentingTextSettingsView = false + + var shouldShowSyncCell: Bool { model.isFeatureAvailable(.sync) } + var shouldShowLoginsCell: Bool { model.isFeatureAvailable(.autofillAccessCredentialManagement) } + var shouldShowTextSizeCell: Bool { model.isFeatureAvailable(.textSize) } + var shouldShowDebugCell: Bool { model.isFeatureAvailable(.networkProtection) } + var shouldShowVoiceSearchCell: Bool { model.isFeatureAvailable(.voiceSearch) } + var shouldShowAddressBarPositionCell: Bool { model.isFeatureAvailable(.addressbarPosition) } + var shouldShowNetworkProtectionCell: Bool { model.isFeatureAvailable(.networkProtection) } + + init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral())) { + self.model = model self.state = state - configureView() } func configureView() { - state.shouldShowSyncCell = featureFlagger.isFeatureOn(.sync) - state.shouldShowLoginsCell = featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) - state.appTheme = appSettings.currentThemeName - state.appIcon = AppIconManager.shared.appIcon - createAppIconSubscriber() - state.fireButtonAnimation = appSettings.currentFireButtonAnimation + state.general.appIcon = model.appIconManager.appIcon + state.general.fireButtonAnimation = model.appSettings.currentFireButtonAnimation + state.general.appTheme = model.appSettings.currentThemeName + setupSubscribers() } - private func createAppIconSubscriber() { - appIconSubscription = AppIconManager.shared.$appIcon + private func setupSubscribers() { + + appIconSubscription = model.appIconManager.$appIcon .sink { newIcon in - self.state.appIcon = newIcon + self.state.general.appIcon = newIcon } } @@ -137,70 +88,82 @@ final class SettingsViewModel: ObservableObject { } -// MARK: User Actions +// MARK: Legacy View Presentation +// These UIKit views have issues when presented via UIHostingController so +// we fall back to UIKit navigation extension SettingsViewModel { - func setAsDefaultBrowser() { - guard let url = URL(string: UIApplication.openSettingsURLString) else { return } - UIApplication.shared.open(url) + private func pushLegacyView(_ view: UIViewController) { + onRequestPushLegacyView?(view) } - func setIsPresentingAddToDockView(_ value: Bool) { - state.isPresentingAddToDockView = value + private func presentLegacyView(_ view: UIViewController, modal: Bool) { + onRequestPresentLegacyView?(view, modal) } - - func setIsPresentingAddWidgetView(_ value: Bool) { - state.isPresentingAddWidgetView = value + + func shouldPresentAddToDockView() { + let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController + presentLegacyView(viewController, modal: true) + isPresentingAddToDockView = false } - func setIsPresentingSyncView(_ value: Bool) { - state.isPresentingSyncView = value + func shouldPresentTextSettingsView() { + Pixel.fire(pixel: .textSizeSettingsShown) + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let controller = storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController + pushLegacyView(controller) + isPresentingTextSettingsView = false } - func setIsPresentingLoginsView(_ value: Bool) { - state.isPresentingLoginsView = value - if !state.isPresentingLoginsView { - Pixel.fire(pixel: .autofillSettingsOpened) - } +} + +// MARK: User Actions +extension SettingsViewModel { + + func setAsDefaultBrowser() { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) } func setIsPresentingLoginsViewWithAccount(accountDetails: SecureVaultModels.WebsiteAccount) { - state.loginsViewSelectedAccount = accountDetails - setIsPresentingLoginsView(true) + state.general.activeWebsiteAccount = accountDetails + isPresentingLoginsView = true } - func setTheme(theme: ThemeName) { - appSettings.currentThemeName = theme - state.appTheme = theme - ThemeManager.shared.enableTheme(with: theme) - ThemeManager.shared.updateUserInterfaceStyle() + func setTheme(_ theme: ThemeName) { + model.setTheme(theme: theme) + state.general.appTheme = theme } func setIsPresentingAppIconView(_ value: Bool) { - state.isPresentingAppIconView = value + isPresentingAppIconView = value } - - func setFireButtonAnimation(_ value: FireButtonAnimationType, showAnimation: Bool = true) { - appSettings.currentFireButtonAnimation = value - NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) - - if showAnimation { - animator.animate { - // no op - } onTransitionCompleted: { - // no op - } completion: { - // no op - } - } - + + func setFireButtonAnimation(_ value: FireButtonAnimationType) { + model.setFireButtonAnimetion(value) + state.general.fireButtonAnimation = value } - func selectFireAnimation() {} - func selectTextSize() {} func selectBarPosition() {} } +extension SettingsViewModel { + + var autofillControllerRepresentable: AutofillLoginSettingsListViewControllerRepresentable { + return AutofillLoginSettingsListViewControllerRepresentable(appSettings: model.appSettings, + syncService: model.syncService, + syncDataProviders: model.syncDataProviders, + delegate: self, + selectedAccount: state.general.activeWebsiteAccount) + } + + var syncSettingsControllerRepresentable: SyncSettingsViewControllerRepresentable { return SyncSettingsViewControllerRepresentable(syncService: model.syncService, + syncDataProviders: model.syncDataProviders) + } +} + + extension SettingsViewModel { static var fontSizeForHeaderView: CGFloat { let contentSize = UIApplication.shared.preferredContentSizeCategory @@ -237,6 +200,6 @@ extension SettingsViewModel { extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { - state.isPresentingLoginsView = false + isPresentingLoginsView = false } } diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index f523ca0990..47a5547556 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -23,6 +23,33 @@ import Combine import SyncUI import DDGSync +// TODO: Sync is already SwiftUI and we should not need a representale +struct SyncSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + + typealias UIViewControllerType = SyncSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Context) -> SyncSettingsViewController { + let viewController = SyncSettingsViewController(syncService: syncService, + syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems + }) + return viewController + } + + func updateUIViewController(_ uiViewController: SyncSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} + @MainActor class SyncSettingsViewController: UIHostingController { diff --git a/DuckDuckGo/TextSizeSettingsViewController.swift b/DuckDuckGo/TextSizeSettingsViewController.swift index 21d58d0a6b..1fe5a82ec2 100644 --- a/DuckDuckGo/TextSizeSettingsViewController.swift +++ b/DuckDuckGo/TextSizeSettingsViewController.swift @@ -62,6 +62,8 @@ class TextSizeSettingsViewController: UITableViewController { if !hasAdjustedDetent, let sheetController = navigationController?.presentationController as? UISheetPresentationController { sheetController.detents = [.medium(), .large()] sheetController.delegate = self + sheetController.largestUndimmedDetentIdentifier = .medium + sheetController.preferredCornerRadius = 16 sheetController.animateChanges { sheetController.selectedDetentIdentifier = .medium diff --git a/DuckDuckGo/Theme.swift b/DuckDuckGo/Theme.swift index 61a5cce0af..70da3e70b9 100644 --- a/DuckDuckGo/Theme.swift +++ b/DuckDuckGo/Theme.swift @@ -19,12 +19,13 @@ import UIKit -enum ThemeName: String, CaseIterable, Identifiable { +enum ThemeName: String, CaseIterable, Identifiable, CustomStringConvertible { case systemDefault = "System Default" case light = "Light" case dark = "Dark" var id: String { self.rawValue } + var description: String { self.rawValue } } protocol Theme { From 2060dc1066c0bea8cf4b1eb2a3b9a3203e532db5 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 7 Dec 2023 13:37:58 +0100 Subject: [PATCH 03/99] Improve separation of concerns/responsibilities --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +++ DuckDuckGo/LazyView.swift | 35 ++++++++++++ DuckDuckGo/MainViewController+Segues.swift | 10 +++- DuckDuckGo/SettingsAppeareanceView.swift | 11 +--- DuckDuckGo/SettingsGeneralView.swift | 2 +- DuckDuckGo/SettingsLegacyViewProvider.swift | 62 +++++++++++++++++++++ DuckDuckGo/SettingsLoginsView.swift | 7 ++- DuckDuckGo/SettingsModel.swift | 44 ++++++++------- DuckDuckGo/SettingsSyncView.swift | 2 +- DuckDuckGo/SettingsView.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 59 ++++++++------------ 11 files changed, 171 insertions(+), 71 deletions(-) create mode 100644 DuckDuckGo/LazyView.swift create mode 100644 DuckDuckGo/SettingsLegacyViewProvider.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3bcc6b9af4..0aec0451cb 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -779,6 +779,8 @@ D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; + D6E83C522B21CDC1006C8AFB /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C512B21CDC1006C8AFB /* LazyView.swift */; }; + D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2414,6 +2416,8 @@ D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; + D6E83C512B21CDC1006C8AFB /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; + D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4517,6 +4521,7 @@ D6E83C442B1FFCF9006C8AFB /* Extensions */ = { isa = PBXGroup; children = ( + D6E83C512B21CDC1006C8AFB /* LazyView.swift */, ); name = Extensions; sourceTree = ""; @@ -4527,6 +4532,7 @@ D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */, D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */, D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, + D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, ); name = Model; sourceTree = ""; @@ -6551,6 +6557,7 @@ 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, + D6E83C522B21CDC1006C8AFB /* LazyView.swift in Sources */, 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, @@ -6633,6 +6640,7 @@ 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, + D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, diff --git a/DuckDuckGo/LazyView.swift b/DuckDuckGo/LazyView.swift new file mode 100644 index 0000000000..8175319376 --- /dev/null +++ b/DuckDuckGo/LazyView.swift @@ -0,0 +1,35 @@ +// +// LazyView.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 + +/// `Conforms to the `View` protocol +/// Delays the instantiation of its content view until necessary. +/// This prevents SwiftUI from preloading views that are not immediately +/// visible, sucn as in NavigationLink + +struct LazyView: View { + let build: () -> Content + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + var body: some View { + build() + } +} diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 2da24ee150..b4640bddf2 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -217,11 +217,15 @@ extension MainViewController { } private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { + let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, + syncDataProviders: syncDataProviders, + appSettings: appSettings) + let model = SettingsModel(bookmarksDatabase: self.bookmarksDatabase, - syncService: self.syncService, - syncDataProviders: self.syncDataProviders, internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - let settingsViewModel = SettingsViewModel(model: model) + + let settingsViewModel = SettingsViewModel(model: model, legacyViewProvider: legacyViewProvider) + let settingsController = SettingsHostingController(viewModel: settingsViewModel) // We are still presenting legacy views, so use a Navcontroller diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index 2757296db5..a8a550b8b2 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -23,13 +23,8 @@ import UIKit struct SettingsAppeareanceView: View { @EnvironmentObject var viewModel: SettingsViewModel - @State var setIsPresentingAppIconView: Bool = false - - @State var selectedTheme: ThemeName = .systemDefault - @State var selectedTextSize: Int = 100 - - + var body: some View { Section(header: Text("Appeareance")) { SettingsPickerCellView(label: "Theme", @@ -39,7 +34,7 @@ struct SettingsAppeareanceView: View { set: { viewModel.setTheme($0) } )) - NavigationLink(destination: AppIconSettingsViewControllerRepresentable(), isActive: $setIsPresentingAppIconView) { + NavigationLink(destination: LazyView(AppIconSettingsViewControllerRepresentable()), isActive: $setIsPresentingAppIconView) { let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) SettingsCellView(label: "App Icon", accesory: .image(image)) @@ -57,7 +52,7 @@ struct SettingsAppeareanceView: View { if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", action: { viewModel.shouldPresentTextSettingsView() }, - accesory: .rightDetail("\(selectedTextSize)%"), + accesory: .rightDetail("\(viewModel.state.general.textSize)%"), asLink: true) } diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index e688cc9103..068263664a 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -37,7 +37,7 @@ struct SettingsGeneralView: View { action: { viewModel.shouldPresentAddToDockView() }, asLink: true) - NavigationLink(destination: WidgetEducationView(), isActive: $isPresentingAddWidgetView) { + NavigationLink(destination: LazyView(WidgetEducationView()), isActive: $isPresentingAddWidgetView) { SettingsCellView(label: "Add Widget to Home Screen", action: { viewModel.isPresentingAddWidgetView = true }) } diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift new file mode 100644 index 0000000000..589a165596 --- /dev/null +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -0,0 +1,62 @@ +// +// LegacyViewProvider.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 DDGSync +import Core +import BrowserServicesKit + +class SettingsLegacyViewProvider { + + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + let appSettings: AppSettings + + init(syncService: DDGSyncing, syncDataProviders: SyncDataProviders, appSettings: AppSettings) { + self.syncService = syncService + self.syncDataProviders = syncDataProviders + self.appSettings = appSettings + } + + func createAddToDockViewController() -> UIViewController { + let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) + return storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController + } + + func createTextSizeSettingsViewController() -> UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController + } + + func createAutofillLoginSettingsListViewControllerRepresentable(delegate: AutofillLoginSettingsListViewControllerDelegate, + selectedAccount: SecureVaultModels.WebsiteAccount?) -> AutofillLoginSettingsListViewControllerRepresentable { + AutofillLoginSettingsListViewControllerRepresentable(appSettings: appSettings, + syncService: syncService, + syncDataProviders: syncDataProviders, + delegate: delegate, + selectedAccount: selectedAccount) + } + + func createSyncSettingsControllerRepresentable() -> SyncSettingsViewControllerRepresentable { + return SyncSettingsViewControllerRepresentable(syncService: syncService, syncDataProviders: syncDataProviders) + } + + +} diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index 5478e3cb38..d23efc8c2a 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -31,15 +31,16 @@ struct SettingsLoginsView: View { var body: some View { if viewModel.shouldShowLoginsCell { Section { - // TODO: Remove transition animation if showing a selected account - NavigationLink(destination: viewModel.autofillControllerRepresentable, isActive: $isPresentingLoginsView) { + NavigationLink(destination: LazyView(viewModel.autofillControllerRepresentable), + isActive: $viewModel.isPresentingLoginsView) { SettingsCellView(label: UserText.autofillLoginListTitle, action: { viewModel.isPresentingLoginsView = true }) } } - + } + } } diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index 65c4eac7b4..4f65f61c29 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -20,7 +20,6 @@ import Foundation import BrowserServicesKit import Persistence -import Common import DDGSync import Combine import UIKit @@ -35,45 +34,52 @@ import Core #endif class SettingsModel { - - // MARK: Dependencies - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - let appIconManager = AppIconManager.shared - + + private let appIconManager = AppIconManager.shared private let bookmarksDatabase: CoreDataDatabase private let internalUserDecider: InternalUserDecider private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings + private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad + + var appIcon: AppIcon = AppIcon.defaultAppIcon + var fireButtonAnimation: FireButtonAnimationType { appSettings.currentFireButtonAnimation } + var appTheme: ThemeName { appSettings.currentThemeName } + var textSize: Int { appSettings.textSize } + - // MARK: Other Properties - private lazy var isPad = UIDevice.current.userInterfaceIdiom != .pad - #if NETWORK_PROTECTION +#if NETWORK_PROTECTION private let connectionObserver = ConnectionStatusObserverThroughSession() - #endif +#endif private var cancellables: Set = [] init(bookmarksDatabase: CoreDataDatabase, - syncService: DDGSyncing, - syncDataProviders: SyncDataProviders, internalUserDecider: InternalUserDecider) { self.bookmarksDatabase = bookmarksDatabase - self.syncService = syncService - self.syncDataProviders = syncDataProviders self.internalUserDecider = internalUserDecider + setupSubscribers() } enum Features { case sync case autofillAccessCredentialManagement case textSize - #if NETWORK_PROTECTION - case networkProtection - #endif case voiceSearch case addressbarPosition - + +#if NETWORK_PROTECTION + case networkProtection +#endif + } + + func setupSubscribers() { + + appIconManager.$appIcon + .sink { [weak self] newIcon in + self?.appIcon = newIcon + } + .store(in: &cancellables) } func isFeatureAvailable(_ feature: Features) -> Bool { diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index e72c7c674b..1db12380e1 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -30,7 +30,7 @@ struct SettingsSyncView: View { var body: some View { if viewModel.shouldShowSyncCell { Section { - NavigationLink(destination: viewModel.syncSettingsControllerRepresentable, isActive: $isPresentingSyncView) { + NavigationLink(destination: LazyView(viewModel.syncSettingsControllerRepresentable), isActive: $isPresentingSyncView) { SettingsCellView(label: UserText.syncTitle, action: { viewModel.isPresentingSyncView = true }) } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 8e7972cd5f..9ebffdcab7 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -37,7 +37,7 @@ struct SettingsView: View { .environmentObject(viewModel) .onAppear { - viewModel.configureView() + viewModel.initializeState() } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 8e0e8d56cf..004267985d 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -22,7 +22,6 @@ import BrowserServicesKit import Persistence import SwiftUI import Common -import DDGSync import Combine #if APP_TRACKING_PROTECTION @@ -44,6 +43,7 @@ final class SettingsViewModel: ObservableObject { var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? private(set) var model: SettingsModel + private let legacyViewProvider: SettingsLegacyViewProvider @Published private(set) var state: SettingsState // MARK: Presentation @@ -61,25 +61,31 @@ final class SettingsViewModel: ObservableObject { var shouldShowVoiceSearchCell: Bool { model.isFeatureAvailable(.voiceSearch) } var shouldShowAddressBarPositionCell: Bool { model.isFeatureAvailable(.addressbarPosition) } var shouldShowNetworkProtectionCell: Bool { model.isFeatureAvailable(.networkProtection) } + + var autofillControllerRepresentable: AutofillLoginSettingsListViewControllerRepresentable { + legacyViewProvider.createAutofillLoginSettingsListViewControllerRepresentable(delegate: self, + selectedAccount: state.general.activeWebsiteAccount) + } + + var syncSettingsControllerRepresentable: SyncSettingsViewControllerRepresentable { + legacyViewProvider.createSyncSettingsControllerRepresentable() + } + - init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral())) { + init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral()), + legacyViewProvider: SettingsLegacyViewProvider) { self.model = model self.state = state + self.legacyViewProvider = legacyViewProvider + initializeState() } - func configureView() { - state.general.appIcon = model.appIconManager.appIcon - state.general.fireButtonAnimation = model.appSettings.currentFireButtonAnimation - state.general.appTheme = model.appSettings.currentThemeName - setupSubscribers() - } - - private func setupSubscribers() { - - appIconSubscription = model.appIconManager.$appIcon - .sink { newIcon in - self.state.general.appIcon = newIcon - } + func initializeState() { + // Model should eventually be Observable + state.general.appIcon = model.appIcon + state.general.fireButtonAnimation = model.fireButtonAnimation + state.general.appTheme = model.appTheme + state.general.textSize = model.textSize } func openCookiePopupManagement() { @@ -102,17 +108,15 @@ extension SettingsViewModel { } func shouldPresentAddToDockView() { - let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController + let viewController = legacyViewProvider.createAddToDockViewController() presentLegacyView(viewController, modal: true) isPresentingAddToDockView = false } func shouldPresentTextSettingsView() { Pixel.fire(pixel: .textSizeSettingsShown) - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let controller = storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController - pushLegacyView(controller) + let viewController = legacyViewProvider.createTextSizeSettingsViewController() + pushLegacyView(viewController) isPresentingTextSettingsView = false } @@ -148,21 +152,6 @@ extension SettingsViewModel { func selectBarPosition() {} } -extension SettingsViewModel { - - var autofillControllerRepresentable: AutofillLoginSettingsListViewControllerRepresentable { - return AutofillLoginSettingsListViewControllerRepresentable(appSettings: model.appSettings, - syncService: model.syncService, - syncDataProviders: model.syncDataProviders, - delegate: self, - selectedAccount: state.general.activeWebsiteAccount) - } - - var syncSettingsControllerRepresentable: SyncSettingsViewControllerRepresentable { return SyncSettingsViewControllerRepresentable(syncService: model.syncService, - syncDataProviders: model.syncDataProviders) - } -} - extension SettingsViewModel { static var fontSizeForHeaderView: CGFloat { From 317ca72acbcba42ae241c2c972ffe5d914db613b Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 7 Dec 2023 14:38:38 +0100 Subject: [PATCH 04/99] Use LazyViews --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +-- ...essBarPositionSettingsViewController.swift | 6 ++- DuckDuckGo/SettingsAppeareanceView.swift | 23 ++++++--- DuckDuckGo/SettingsGeneralView.swift | 10 ++-- DuckDuckGo/SettingsLoginsView.swift | 5 +- DuckDuckGo/SettingsModel.swift | 5 ++ DuckDuckGo/SettingsState.swift | 1 + DuckDuckGo/SettingsSyncView.swift | 5 ++ DuckDuckGo/SettingsViewModel.swift | 49 ++++++++++++------- 9 files changed, 77 insertions(+), 35 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0aec0451cb..256087deda 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -779,8 +779,8 @@ D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; - D6E83C522B21CDC1006C8AFB /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C512B21CDC1006C8AFB /* LazyView.swift */; }; D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; + D6E83C582B22020D006C8AFB /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C572B22020D006C8AFB /* LazyView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2416,8 +2416,8 @@ D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; - D6E83C512B21CDC1006C8AFB /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; + D6E83C572B22020D006C8AFB /* LazyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4521,7 +4521,6 @@ D6E83C442B1FFCF9006C8AFB /* Extensions */ = { isa = PBXGroup; children = ( - D6E83C512B21CDC1006C8AFB /* LazyView.swift */, ); name = Extensions; sourceTree = ""; @@ -5165,6 +5164,7 @@ F1AB2B401E3F75A000868554 /* Settings */ = { isa = PBXGroup; children = ( + D6E83C572B22020D006C8AFB /* LazyView.swift */, D6E83C492B20C883006C8AFB /* Model */, D6E83C3B2B1F27BA006C8AFB /* Views */, D6E83C442B1FFCF9006C8AFB /* Extensions */, @@ -6379,6 +6379,7 @@ 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */, 4BBBBA8F2B031B4200D965DA /* VPNWaitlistView.swift in Sources */, CB258D1229A4F24900DEBA24 /* ConfigurationManager.swift in Sources */, + D6E83C582B22020D006C8AFB /* LazyView.swift in Sources */, 8546A54A2A672959003929BF /* MainViewController+Email.swift in Sources */, F4F6DFB226E6AEC100ED7E12 /* AddOrEditBookmarkViewController.swift in Sources */, EE458D0D2AB1DA4600FC651A /* EventMapping+NetworkProtectionError.swift in Sources */, @@ -6557,7 +6558,6 @@ 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, - D6E83C522B21CDC1006C8AFB /* LazyView.swift in Sources */, 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, diff --git a/DuckDuckGo/AddressBarPositionSettingsViewController.swift b/DuckDuckGo/AddressBarPositionSettingsViewController.swift index 34d8a466f4..26b0fb375a 100644 --- a/DuckDuckGo/AddressBarPositionSettingsViewController.swift +++ b/DuckDuckGo/AddressBarPositionSettingsViewController.swift @@ -79,13 +79,17 @@ extension AddressBarPositionSettingsViewController: Themable { } } -enum AddressBarPosition: String, CaseIterable { +enum AddressBarPosition: String, CaseIterable, CustomStringConvertible { case top case bottom var isBottom: Bool { self == .bottom } + + var description: String { + return descriptionText + } var descriptionText: String { switch self { diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index a8a550b8b2..dce704b837 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -23,7 +23,6 @@ import UIKit struct SettingsAppeareanceView: View { @EnvironmentObject var viewModel: SettingsViewModel - @State var setIsPresentingAppIconView: Bool = false var body: some View { Section(header: Text("Appeareance")) { @@ -33,30 +32,40 @@ struct SettingsAppeareanceView: View { get: { viewModel.state.general.appTheme }, set: { viewModel.setTheme($0) } )) - - NavigationLink(destination: LazyView(AppIconSettingsViewControllerRepresentable()), isActive: $setIsPresentingAppIconView) { + + NavigationLink(destination: LazyView(AppIconSettingsViewControllerRepresentable()), + isActive: $viewModel.isPresentingAppIconView) { let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) SettingsCellView(label: "App Icon", accesory: .image(image)) } - + SettingsPickerCellView(label: "Fire Button Animation", options: FireButtonAnimationType.allCases, selectedOption: Binding( get: { viewModel.state.general.fireButtonAnimation }, set: { viewModel.setFireButtonAnimation($0) } )) - + // The textsize settings view has a special behavior (detent adjustment) that requires access to a navigation controller // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", - action: { viewModel.shouldPresentTextSettingsView() }, + action: { viewModel.isPresentingTextSettingsView = true }, accesory: .rightDetail("\(viewModel.state.general.textSize)%"), asLink: true) } - // RightDetailCell(label: "Address Bar Position", value: "Top", action: viewModel.selectBarPosition) + if viewModel.shouldShowAddressBarPositionCell { + SettingsPickerCellView(label: "Address Bar Position", + options: AddressBarPosition.allCases, + selectedOption: Binding( + get: { viewModel.state.general.addressBarPosition }, + set: { viewModel.setAddressBarPosition($0) } + )) + } + + } diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index 068263664a..7b7a30c3af 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -27,14 +27,14 @@ struct SettingsGeneralView: View { var body: some View { Section { - // The homeRow view controller has - // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container SettingsCellView(label: "Set as Default Browser", action: { viewModel.setAsDefaultBrowser() }, asLink: true) + // This old VC has a special behavior does not work as expected when presented in the SwiftUI Stack + // so we need to push it via the UIKit Containe SettingsCellView(label: "Add App to Your Dock", - action: { viewModel.shouldPresentAddToDockView() }, + action: { viewModel.isPresentingAddToDockView = true }, asLink: true) NavigationLink(destination: LazyView(WidgetEducationView()), isActive: $isPresentingAddWidgetView) { @@ -43,8 +43,8 @@ struct SettingsGeneralView: View { } } - .onChange(of: isPresentingAddWidgetView) { newValue in - viewModel.isPresentingAddWidgetView = newValue + .onChange(of: viewModel.isPresentingAddWidgetView) { newValue in + isPresentingAddWidgetView = newValue } diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index d23efc8c2a..ff12ad7637 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -32,12 +32,15 @@ struct SettingsLoginsView: View { if viewModel.shouldShowLoginsCell { Section { NavigationLink(destination: LazyView(viewModel.autofillControllerRepresentable), - isActive: $viewModel.isPresentingLoginsView) { + isActive: $isPresentingLoginsView) { SettingsCellView(label: UserText.autofillLoginListTitle, action: { viewModel.isPresentingLoginsView = true }) } } + .onChange(of: viewModel.isPresentingLoginsView) { newValue in + isPresentingLoginsView = newValue + } } diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index 4f65f61c29..a511c3e0be 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -47,6 +47,7 @@ class SettingsModel { var fireButtonAnimation: FireButtonAnimationType { appSettings.currentFireButtonAnimation } var appTheme: ThemeName { appSettings.currentThemeName } var textSize: Int { appSettings.textSize } + var addressBarPosition: AddressBarPosition { appSettings.currentAddressBarPosition } #if NETWORK_PROTECTION @@ -124,5 +125,9 @@ class SettingsModel { // no op } } + + func setAddressBarPosition(_ position: AddressBarPosition) { + appSettings.currentAddressBarPosition = position + } } diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 392b6f8546..df546b774b 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -30,4 +30,5 @@ struct SettingsStateGeneral { var fireButtonAnimation: FireButtonAnimationType = .fireRising var textSize: Int = 100 var activeWebsiteAccount: SecureVaultModels.WebsiteAccount? + var addressBarPosition: AddressBarPosition = .top } diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index 1db12380e1..d21bce9a0f 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -26,6 +26,7 @@ struct SettingsSyncView: View { @EnvironmentObject var viewModel: SettingsViewModel @State var isPresentingSyncView: Bool = false + var body: some View { if viewModel.shouldShowSyncCell { @@ -35,6 +36,10 @@ struct SettingsSyncView: View { action: { viewModel.isPresentingSyncView = true }) } } + + .onChange(of: viewModel.isPresentingSyncView) { newValue in + isPresentingSyncView = newValue + } } } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 004267985d..323910cb63 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -47,12 +47,34 @@ final class SettingsViewModel: ObservableObject { @Published private(set) var state: SettingsState // MARK: Presentation - var isPresentingAddToDockView: Bool = false + var isPresentingAddToDockView = false { + didSet { + if isPresentingAddToDockView == true { + let viewController = legacyViewProvider.createAddToDockViewController() + presentLegacyView(viewController, modal: true) + isPresentingAddToDockView = false + } + } + } var isPresentingAddWidgetView = false var isPresentingSyncView = false - var isPresentingLoginsView = false + var isPresentingLoginsView = false { + didSet { + if isPresentingLoginsView == true { + Pixel.fire(pixel: .autofillSettingsOpened) + } + } + } var isPresentingAppIconView = false - var isPresentingTextSettingsView = false + var isPresentingTextSettingsView = false { + didSet { + if isPresentingTextSettingsView == true { + Pixel.fire(pixel: .textSizeSettingsShown) + let viewController = legacyViewProvider.createTextSizeSettingsViewController() + pushLegacyView(viewController) + } + } + } var shouldShowSyncCell: Bool { model.isFeatureAvailable(.sync) } var shouldShowLoginsCell: Bool { model.isFeatureAvailable(.autofillAccessCredentialManagement) } @@ -86,6 +108,7 @@ final class SettingsViewModel: ObservableObject { state.general.fireButtonAnimation = model.fireButtonAnimation state.general.appTheme = model.appTheme state.general.textSize = model.textSize + state.general.addressBarPosition = model.addressBarPosition } func openCookiePopupManagement() { @@ -106,19 +129,6 @@ extension SettingsViewModel { private func presentLegacyView(_ view: UIViewController, modal: Bool) { onRequestPresentLegacyView?(view, modal) } - - func shouldPresentAddToDockView() { - let viewController = legacyViewProvider.createAddToDockViewController() - presentLegacyView(viewController, modal: true) - isPresentingAddToDockView = false - } - - func shouldPresentTextSettingsView() { - Pixel.fire(pixel: .textSizeSettingsShown) - let viewController = legacyViewProvider.createTextSizeSettingsViewController() - pushLegacyView(viewController) - isPresentingTextSettingsView = false - } } @@ -126,6 +136,7 @@ extension SettingsViewModel { extension SettingsViewModel { func setAsDefaultBrowser() { + Pixel.fire(pixel: .defaultBrowserButtonPressedSettings) guard let url = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(url) } @@ -149,7 +160,11 @@ extension SettingsViewModel { state.general.fireButtonAnimation = value } - func selectBarPosition() {} + func setAddressBarPosition(_ value: AddressBarPosition) { + model.setAddressBarPosition(value) + state.general.addressBarPosition = value + } + } From 4a8946ad37d9a389c1ab2386e03c9c5c79abb35c Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 7 Dec 2023 19:11:37 +0100 Subject: [PATCH 05/99] Removed unnecesary viewcontrollers --- DuckDuckGo.xcodeproj/project.pbxproj | 32 +--- ...essBarPositionSettingsViewController.swift | 102 ------------- DuckDuckGo/AppSettings.swift | 22 +++ .../AutoClearSettingsViewController.swift | 24 +++ .../AutoconsentSettingsViewController.swift | 24 +++ DuckDuckGo/Base.lproj/Settings.storyboard | 53 +------ .../DoNotSellSettingsViewController.swift | 24 +++ ...uttonAnimationSettingsViewController.swift | 105 ------------- DuckDuckGo/LazyView.swift | 5 +- DuckDuckGo/MainViewController+Segues.swift | 8 +- ...PreserveLoginsSettingsViewController.swift | 24 +++ DuckDuckGo/SettingsAppeareanceView.swift | 23 +-- DuckDuckGo/SettingsCell.swift | 6 +- DuckDuckGo/SettingsGeneralView.swift | 16 +- DuckDuckGo/SettingsHostingController.swift | 6 +- DuckDuckGo/SettingsLoginsView.swift | 22 ++- DuckDuckGo/SettingsModel.swift | 10 ++ DuckDuckGo/SettingsPrivacyView.swift | 72 +++++++++ DuckDuckGo/SettingsState.swift | 3 + DuckDuckGo/SettingsSyncView.swift | 15 +- DuckDuckGo/SettingsView.swift | 3 + DuckDuckGo/SettingsViewModel.swift | 144 +++++++++--------- ...vider.swift => SettingsViewProvider.swift} | 44 +++++- DuckDuckGo/ThemeSettingsViewController.swift | 104 ------------- .../UnprotectedSitesViewController.swift | 24 +++ 25 files changed, 401 insertions(+), 514 deletions(-) delete mode 100644 DuckDuckGo/AddressBarPositionSettingsViewController.swift delete mode 100644 DuckDuckGo/FireButtonAnimationSettingsViewController.swift create mode 100644 DuckDuckGo/SettingsPrivacyView.swift rename DuckDuckGo/{SettingsLegacyViewProvider.swift => SettingsViewProvider.swift} (67%) delete mode 100644 DuckDuckGo/ThemeSettingsViewController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 256087deda..8f60ed0e32 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -429,7 +429,6 @@ 85514FFD2372DA0100DBC528 /* ios13-home-row.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 85514FFC2372DA0000DBC528 /* ios13-home-row.mp4 */; }; 8551912724746EDC0010FDD0 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */; }; 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */; }; - 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */; }; 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */; }; 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */; }; 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */; }; @@ -582,7 +581,6 @@ 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9880722925FA497B0039EF4B /* MenuButton.swift */; }; 9880723725FA4E450039EF4B /* menu_dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 9880723525FA4E440039EF4B /* menu_dark.json */; }; 9880723825FA4E450039EF4B /* menu_light.json in Resources */ = {isa = PBXBuildFile; fileRef = 9880723625FA4E450039EF4B /* menu_light.json */; }; - 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */; }; 9887DC252354D2AA005C85F5 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9887DC242354D2AA005C85F5 /* Database.swift */; }; 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9888F77A2224980500C46159 /* FeedbackViewController.swift */; }; 988AC355257E47C100793C64 /* RequeryLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988AC354257E47C100793C64 /* RequeryLogic.swift */; }; @@ -779,8 +777,9 @@ D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; - D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; + D6E83C562B21ECC1006C8AFB /* SettingsViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */; }; D6E83C582B22020D006C8AFB /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C572B22020D006C8AFB /* LazyView.swift */; }; + D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -912,7 +911,6 @@ F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */; }; F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */; }; F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */; }; - F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */; }; F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */; }; F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47E53D8250A97330037C686 /* OnboardingDefaultBroswerViewController.swift */; }; F47E53DB250A9A1C0037C686 /* Onboarding.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */; }; @@ -1481,7 +1479,6 @@ 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerRadarIntegrationTests.swift; sourceTree = ""; }; 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+PDFRendering.swift"; sourceTree = ""; }; - 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarPositionSettingsViewController.swift; sourceTree = ""; }; 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherTransition.swift; sourceTree = ""; }; 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserChromeManager.swift; sourceTree = ""; }; 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotification.swift; sourceTree = ""; }; @@ -2157,7 +2154,6 @@ 9880722925FA497B0039EF4B /* MenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuButton.swift; sourceTree = ""; }; 9880723525FA4E440039EF4B /* menu_dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = menu_dark.json; sourceTree = ""; }; 9880723625FA4E450039EF4B /* menu_light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = menu_light.json; sourceTree = ""; }; - 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsViewController.swift; sourceTree = ""; }; 9887DC242354D2AA005C85F5 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 9888F77A2224980500C46159 /* FeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewController.swift; sourceTree = ""; }; 988AC354257E47C100793C64 /* RequeryLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequeryLogic.swift; sourceTree = ""; }; @@ -2416,8 +2412,9 @@ D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; - D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; + D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewProvider.swift; sourceTree = ""; }; D6E83C572B22020D006C8AFB /* LazyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; + D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -2582,7 +2579,6 @@ F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptView.swift; sourceTree = ""; }; F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModel.swift; sourceTree = ""; }; F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewController.swift; sourceTree = ""; }; - F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireButtonAnimationSettingsViewController.swift; sourceTree = ""; }; F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainItemsDebugViewController.swift; sourceTree = ""; }; F47E53D8250A97330037C686 /* OnboardingDefaultBroswerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDefaultBroswerViewController.swift; sourceTree = ""; }; F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Onboarding.xcassets; sourceTree = ""; }; @@ -3850,19 +3846,16 @@ isa = PBXGroup; children = ( F1CDD3F11F16911700BE0581 /* AboutViewController.swift */, - 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */, AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */, AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */, 98F0FC1F21FF18E700CE77AB /* AutoClearSettingsViewController.swift */, 1EE7C298294227EC0026C8CB /* AutoconsentSettingsViewController.swift */, 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */, - F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */, 85449EF423FDA02800512AAF /* KeyboardSettingsViewController.swift */, 8540BD5523D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift */, F176699D1E40BC86003D3222 /* Settings.storyboard */, F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */, 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */, - 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */, 8531A08D1F9950E6000484F0 /* UnprotectedSitesViewController.swift */, ); name = UI; @@ -4502,6 +4495,7 @@ D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */, D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */, D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, + D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, ); name = Sections; sourceTree = ""; @@ -4518,20 +4512,13 @@ name = Views; sourceTree = ""; }; - D6E83C442B1FFCF9006C8AFB /* Extensions */ = { - isa = PBXGroup; - children = ( - ); - name = Extensions; - sourceTree = ""; - }; D6E83C492B20C883006C8AFB /* Model */ = { isa = PBXGroup; children = ( D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */, D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */, D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, - D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, + D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */, ); name = Model; sourceTree = ""; @@ -5167,7 +5154,6 @@ D6E83C572B22020D006C8AFB /* LazyView.swift */, D6E83C492B20C883006C8AFB /* Model */, D6E83C3B2B1F27BA006C8AFB /* Views */, - D6E83C442B1FFCF9006C8AFB /* Extensions */, 858566F1252E55AE007501B8 /* Debug */, 85449EF723FDA03D00512AAF /* Model */, 85449EF623FDA03100512AAF /* UI */, @@ -6339,7 +6325,6 @@ 31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */, EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 0290472029E708B70008FE3C /* AppTPManageTrackersViewModel.swift in Sources */, - 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */, @@ -6472,6 +6457,7 @@ F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */, 1EEC460627A9499600E75FCB /* DownloadsList.swift in Sources */, + D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, @@ -6624,7 +6610,6 @@ D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, - F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */, 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */, 1EEF387D285B1A1100383393 /* TrackerImageCache.swift in Sources */, @@ -6640,7 +6625,7 @@ 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, - D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, + D6E83C562B21ECC1006C8AFB /* SettingsViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, @@ -6686,7 +6671,6 @@ D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, - 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, 1E7A711C2934EEBC00B7EA19 /* OmniBarNotification.swift in Sources */, diff --git a/DuckDuckGo/AddressBarPositionSettingsViewController.swift b/DuckDuckGo/AddressBarPositionSettingsViewController.swift deleted file mode 100644 index 26b0fb375a..0000000000 --- a/DuckDuckGo/AddressBarPositionSettingsViewController.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// AddressBarPositionSettingsViewController.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 Core - -class AddressBarPositionSettingsViewController: UITableViewController { - - private lazy var appSettings = AppDependencyProvider.shared.appSettings - - private lazy var options = AddressBarPosition.allCases - - override func viewDidLoad() { - super.viewDidLoad() - - applyTheme(ThemeManager.shared.currentTheme) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return options.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) - } - - override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - let theme = ThemeManager.shared.currentTheme - cell.backgroundColor = theme.tableCellBackgroundColor - - cell.tintColor = theme.buttonTintColor - cell.textLabel?.textColor = theme.tableCellTextColor - - cell.textLabel?.text = options[indexPath.row].descriptionText - cell.accessoryType = appSettings.currentAddressBarPosition.descriptionText == cell.textLabel?.text ? .checkmark : .none - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - appSettings.currentAddressBarPosition = AddressBarPosition.allCases[indexPath.row] - - switch appSettings.currentAddressBarPosition { - case .top: - Pixel.fire(pixel: .navigationBarPositionTop) - case .bottom: - Pixel.fire(pixel: .navigationbarPositionBottom) - } - - tableView.performBatchUpdates { - tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - tableView.deselectRow(at: indexPath, animated: true) - } - } -} - -extension AddressBarPositionSettingsViewController: Themable { - - func decorate(with theme: Theme) { - - tableView.backgroundColor = theme.backgroundColor - tableView.separatorColor = theme.tableCellSeparatorColor - - tableView.reloadData() - } -} - -enum AddressBarPosition: String, CaseIterable, CustomStringConvertible { - case top - case bottom - - var isBottom: Bool { - self == .bottom - } - - var description: String { - return descriptionText - } - - var descriptionText: String { - switch self { - case .top: - return UserText.addressBarPositionTop - case .bottom: - return UserText.addressBarPositionBottom - } - } -} diff --git a/DuckDuckGo/AppSettings.swift b/DuckDuckGo/AppSettings.swift index 0c3f6dd748..790e10f995 100644 --- a/DuckDuckGo/AppSettings.swift +++ b/DuckDuckGo/AppSettings.swift @@ -19,6 +19,28 @@ import Bookmarks +enum AddressBarPosition: String, CaseIterable, CustomStringConvertible { + case top + case bottom + + var isBottom: Bool { + self == .bottom + } + + var description: String { + return descriptionText + } + + var descriptionText: String { + switch self { + case .top: + return UserText.addressBarPositionTop + case .bottom: + return UserText.addressBarPositionBottom + } + } +} + protocol AppSettings: AnyObject { var autocomplete: Bool { get set } var currentThemeName: ThemeName { get set } diff --git a/DuckDuckGo/AutoClearSettingsViewController.swift b/DuckDuckGo/AutoClearSettingsViewController.swift index 6f4363ff2d..6c17cde96c 100644 --- a/DuckDuckGo/AutoClearSettingsViewController.swift +++ b/DuckDuckGo/AutoClearSettingsViewController.swift @@ -20,6 +20,30 @@ import UIKit import MessageUI import Core +import SwiftUI + +// MARK: AutoClearSettingsViewController Representable +struct AutoClearSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = AutoClearSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> AutoClearSettingsViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "AutoClearSettingsViewController") as! AutoClearSettingsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: AutoClearSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} class AutoClearSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/AutoconsentSettingsViewController.swift b/DuckDuckGo/AutoconsentSettingsViewController.swift index 470ba711d4..188b63a587 100644 --- a/DuckDuckGo/AutoconsentSettingsViewController.swift +++ b/DuckDuckGo/AutoconsentSettingsViewController.swift @@ -19,6 +19,30 @@ import UIKit import Core +import SwiftUI + +// MARK: AutoconsentSettingsViewController Representable +struct AutoconsentSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = AutoconsentSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> AutoconsentSettingsViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "AutoconsentSettingsViewController") as! AutoconsentSettingsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: AutoconsentSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} final class AutoconsentSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/Base.lproj/Settings.storyboard b/DuckDuckGo/Base.lproj/Settings.storyboard index e15aba85b6..844049a51c 100644 --- a/DuckDuckGo/Base.lproj/Settings.storyboard +++ b/DuckDuckGo/Base.lproj/Settings.storyboard @@ -319,9 +319,6 @@ - - - @@ -1581,7 +1578,7 @@ - + @@ -1720,7 +1717,7 @@ - + @@ -1879,7 +1876,7 @@ - + @@ -2167,50 +2164,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/DoNotSellSettingsViewController.swift b/DuckDuckGo/DoNotSellSettingsViewController.swift index 735c66b652..52bae399c0 100644 --- a/DuckDuckGo/DoNotSellSettingsViewController.swift +++ b/DuckDuckGo/DoNotSellSettingsViewController.swift @@ -19,6 +19,30 @@ import UIKit import Core +import SwiftUI + +// MARK: DoNotSellSettingsView Settings Representable +struct DoNotSellSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = DoNotSellSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> DoNotSellSettingsViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "DoNotSell") as! DoNotSellSettingsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: DoNotSellSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} class DoNotSellSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/FireButtonAnimationSettingsViewController.swift b/DuckDuckGo/FireButtonAnimationSettingsViewController.swift deleted file mode 100644 index 94b2ae8cd9..0000000000 --- a/DuckDuckGo/FireButtonAnimationSettingsViewController.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// FireButtonAnimationSettingsViewController.swift -// DuckDuckGo -// -// Copyright © 2020 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit -import Core - -class FireButtonAnimationSettingsViewController: UITableViewController { - - private lazy var appSettings = AppDependencyProvider.shared.appSettings - - private lazy var availableAnimations = FireButtonAnimationType.allCases - - private var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) - - override func viewDidLoad() { - super.viewDidLoad() - - applyTheme(ThemeManager.shared.currentTheme) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return availableAnimations.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return tableView.dequeueReusableCell(withIdentifier: "FireAnimationTypeCell", for: indexPath) - } - - override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - guard let cell = cell as? FireAnimationTypeCell else { - fatalError("Expected FireAnimationTypeCell") - } - - let theme = ThemeManager.shared.currentTheme - cell.backgroundColor = theme.tableCellBackgroundColor - - // Checkmark color - cell.tintColor = theme.buttonTintColor - cell.nameLabel.textColor = theme.tableCellTextColor - - let animationType = availableAnimations[indexPath.row] - cell.name = animationType.descriptionText - - cell.accessoryType = animationType == appSettings.currentFireButtonAnimation ? .checkmark : .none - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let type = availableAnimations[indexPath.row] - appSettings.currentFireButtonAnimation = type - NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) - tableView.reloadData() - - animator.animate { - // no op - } onTransitionCompleted: { - // no op - } completion: { - // no op - } - - } -} - -class FireAnimationTypeCell: UITableViewCell { - - @IBOutlet weak var nameLabel: UILabel! - - var name: String? { - get { - return nameLabel.text - } - set { - nameLabel.text = newValue - } - } -} - -extension FireButtonAnimationSettingsViewController: Themable { - - func decorate(with theme: Theme) { - - tableView.backgroundColor = theme.backgroundColor - tableView.separatorColor = theme.tableCellSeparatorColor - - tableView.reloadData() - } -} diff --git a/DuckDuckGo/LazyView.swift b/DuckDuckGo/LazyView.swift index 8175319376..72e03217c5 100644 --- a/DuckDuckGo/LazyView.swift +++ b/DuckDuckGo/LazyView.swift @@ -19,9 +19,8 @@ import SwiftUI -/// `Conforms to the `View` protocol -/// Delays the instantiation of its content view until necessary. -/// This prevents SwiftUI from preloading views that are not immediately +/// Delays the instantiation of a view content until necessary. +/// This prevents SwiftUI from preloading views that are not immediately /// visible, sucn as in NavigationLink struct LazyView: View { diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index b4640bddf2..5b0cb82e66 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -196,7 +196,7 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.setIsPresentingLoginsViewWithAccount(accountDetails: account) + $0.shouldPresentLoginsViewWithAccount(accountDetails: account) } } @@ -217,16 +217,16 @@ extension MainViewController { } private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { - let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, + let legacyViewProvider = SettingsViewProvider(syncService: syncService, syncDataProviders: syncDataProviders, appSettings: appSettings) let model = SettingsModel(bookmarksDatabase: self.bookmarksDatabase, internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - let settingsViewModel = SettingsViewModel(model: model, legacyViewProvider: legacyViewProvider) + let settingsViewModel = SettingsViewModel(model: model) - let settingsController = SettingsHostingController(viewModel: settingsViewModel) + let settingsController = SettingsHostingController(viewModel: settingsViewModel, viewProvider: legacyViewProvider) // We are still presenting legacy views, so use a Navcontroller let navController = UINavigationController(rootViewController: settingsController) diff --git a/DuckDuckGo/PreserveLoginsSettingsViewController.swift b/DuckDuckGo/PreserveLoginsSettingsViewController.swift index 35cc331f12..3b2067c83b 100644 --- a/DuckDuckGo/PreserveLoginsSettingsViewController.swift +++ b/DuckDuckGo/PreserveLoginsSettingsViewController.swift @@ -19,6 +19,30 @@ import UIKit import Core +import SwiftUI + +// MARK: PreserveLoginsSettingsViewController Representable +struct PreserveLoginsSettingsViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = PreserveLoginsSettingsViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> PreserveLoginsSettingsViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "FireProofSites") as! PreserveLoginsSettingsViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: PreserveLoginsSettingsViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} class PreserveLoginsSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index dce704b837..b9e758c851 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -23,18 +23,15 @@ import UIKit struct SettingsAppeareanceView: View { @EnvironmentObject var viewModel: SettingsViewModel + @EnvironmentObject var viewProvider: SettingsViewProvider var body: some View { Section(header: Text("Appeareance")) { SettingsPickerCellView(label: "Theme", options: ThemeName.allCases, - selectedOption: Binding( - get: { viewModel.state.general.appTheme }, - set: { viewModel.setTheme($0) } - )) + selectedOption: viewModel.themeBinding) - NavigationLink(destination: LazyView(AppIconSettingsViewControllerRepresentable()), - isActive: $viewModel.isPresentingAppIconView) { + NavigationLink(destination: viewProvider.appIcon) { let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) SettingsCellView(label: "App Icon", accesory: .image(image)) @@ -42,16 +39,15 @@ struct SettingsAppeareanceView: View { SettingsPickerCellView(label: "Fire Button Animation", options: FireButtonAnimationType.allCases, - selectedOption: Binding( - get: { viewModel.state.general.fireButtonAnimation }, - set: { viewModel.setFireButtonAnimation($0) } - )) + selectedOption: viewModel.fireButtonAnimationBinding) // The textsize settings view has a special behavior (detent adjustment) that requires access to a navigation controller // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", - action: { viewModel.isPresentingTextSettingsView = true }, + action: { + viewModel.presentTextSettingsView(viewProvider.textSettings) + }, accesory: .rightDetail("\(viewModel.state.general.textSize)%"), asLink: true) } @@ -59,10 +55,7 @@ struct SettingsAppeareanceView: View { if viewModel.shouldShowAddressBarPositionCell { SettingsPickerCellView(label: "Address Bar Position", options: AddressBarPosition.allCases, - selectedOption: Binding( - get: { viewModel.state.general.addressBarPosition }, - set: { viewModel.setAddressBarPosition($0) } - )) + selectedOption: viewModel.addressBarPositionBinding) } diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 80fee2f55c..35393ba42e 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -42,7 +42,7 @@ struct SettingsCellView: View { /// Use this initializer for standard cell types that require a label. /// - Parameters: /// - label: The text to display in the cell. - /// - action: The closure to execute when the cell is tapped. + /// - action: The closure to execute when the view is tapped. (If not embedded in a NavigationLink) /// - accesory: The type of cell to display. Excludes the custom cell type. /// - enabled: A Boolean value that determines whether the cell is enabled. /// - asLink: Wraps the view inside a Button. Used for views not wrapped in a NavigationLink @@ -61,7 +61,7 @@ struct SettingsCellView: View { /// Use this initializer for creating a cell that displays custom content. /// This initializer does not require a label, as the content is entirely custom. /// - Parameters: - /// - action: The closure to execute when the cell is tapped. + /// - action: The closure to execute when the view is tapped. (If not embedded in a NavigationLink) /// - customView: A closure that returns the custom view (`AnyView`) to be displayed in the cell. /// - enabled: A Boolean value that determines whether the cell is enabled. init(action: @escaping () -> Void = {}, @ViewBuilder customView: () -> AnyView, enabled: Bool = true) { @@ -139,7 +139,7 @@ struct SettingsPickerCellView) { self.label = label self.options = options diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index 7b7a30c3af..df8abf0466 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -23,7 +23,7 @@ import UIKit struct SettingsGeneralView: View { @EnvironmentObject var viewModel: SettingsViewModel - @State var isPresentingAddWidgetView: Bool = false + @EnvironmentObject var viewProvider: SettingsViewProvider var body: some View { Section { @@ -34,19 +34,15 @@ struct SettingsGeneralView: View { // This old VC has a special behavior does not work as expected when presented in the SwiftUI Stack // so we need to push it via the UIKit Containe SettingsCellView(label: "Add App to Your Dock", - action: { viewModel.isPresentingAddToDockView = true }, + action: { + viewModel.presentLegacyView(viewProvider.addToDock, modal: true) + }, asLink: true) - NavigationLink(destination: LazyView(WidgetEducationView()), isActive: $isPresentingAddWidgetView) { - SettingsCellView(label: "Add Widget to Home Screen", - action: { viewModel.isPresentingAddWidgetView = true }) + NavigationLink(destination: viewProvider.addWidget) { + SettingsCellView(label: "Add Widget to Home Screen") } } - - .onChange(of: viewModel.isPresentingAddWidgetView) { newValue in - isPresentingAddWidgetView = newValue - } - } diff --git a/DuckDuckGo/SettingsHostingController.swift b/DuckDuckGo/SettingsHostingController.swift index 7882436a2c..2f801e9dc5 100644 --- a/DuckDuckGo/SettingsHostingController.swift +++ b/DuckDuckGo/SettingsHostingController.swift @@ -22,9 +22,11 @@ import SwiftUI class SettingsHostingController: UIHostingController { var viewModel: SettingsViewModel + var viewProvider: SettingsViewProvider - init(viewModel: SettingsViewModel) { + init(viewModel: SettingsViewModel, viewProvider: SettingsViewProvider) { self.viewModel = viewModel + self.viewProvider = viewProvider super.init(rootView: AnyView(EmptyView())) viewModel.onRequestPushLegacyView = { [weak self] vc in @@ -35,7 +37,7 @@ class SettingsHostingController: UIHostingController { self?.presentLegacyViewCOntroller(vc, modal: modal) } - let settingsView = SettingsView(viewModel: viewModel) + let settingsView = SettingsView(viewModel: viewModel, viewProvider: viewProvider) self.rootView = AnyView(settingsView) } diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index ff12ad7637..88d8b714c8 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -26,22 +26,30 @@ import BrowserServicesKit struct SettingsLoginsView: View { @EnvironmentObject var viewModel: SettingsViewModel + @EnvironmentObject var viewProvider: SettingsViewProvider @State var isPresentingLoginsView: Bool = false var body: some View { if viewModel.shouldShowLoginsCell { Section { - NavigationLink(destination: LazyView(viewModel.autofillControllerRepresentable), + NavigationLink(destination: viewProvider.loginSettings(delegate: viewModel, selectedAccount: viewModel.state.general.activeWebsiteAccount), isActive: $isPresentingLoginsView) { - SettingsCellView(label: UserText.autofillLoginListTitle, - action: { viewModel.isPresentingLoginsView = true }) + SettingsCellView(label: UserText.autofillLoginListTitle ) } + .onChange(of: viewModel.isPresentingLoginsView) { newValue in + isPresentingLoginsView = newValue + } + + .onChange(of: isPresentingLoginsView) { isActive in + if isActive { + viewModel.autofillViewPresentationAction() + } else { + viewModel.isPresentingLoginsView = false + } + } + } - .onChange(of: viewModel.isPresentingLoginsView) { newValue in - isPresentingLoginsView = newValue - } - } } diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index a511c3e0be..99987d3f02 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -48,6 +48,15 @@ class SettingsModel { var appTheme: ThemeName { appSettings.currentThemeName } var textSize: Int { appSettings.textSize } var addressBarPosition: AddressBarPosition { appSettings.currentAddressBarPosition } + var sendDoNotSell: Bool { appSettings.sendDoNotSell } + var autoconsentEnabled: Bool { appSettings.autoconsentEnabled } + var autoclearDataEnabled: Bool { + if AutoClearSettingsModel(settings: appSettings) != nil { + return true + } else { + return false + } + } #if NETWORK_PROTECTION @@ -109,6 +118,7 @@ class SettingsModel { } func setTheme(theme: ThemeName) { + appSettings.currentThemeName = theme ThemeManager.shared.enableTheme(with: theme) ThemeManager.shared.updateUserInterfaceStyle() } diff --git a/DuckDuckGo/SettingsPrivacyView.swift b/DuckDuckGo/SettingsPrivacyView.swift new file mode 100644 index 0000000000..6080a21f4b --- /dev/null +++ b/DuckDuckGo/SettingsPrivacyView.swift @@ -0,0 +1,72 @@ +// TODO: Remove transition animation if showing a selected account// +// GeneralSection.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 UIKit + +struct SettingsPrivacyView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + @EnvironmentObject var viewProvider: SettingsViewProvider + @State var isPresentingGPCView = false + + var body: some View { + Section(header: Text("Privacy"), + footer: Text("If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening.")) { + + NavigationLink(destination: viewProvider.doNotSell, isActive: $isPresentingGPCView) { + SettingsCellView(label: "Global Privacy Control (GPC)", + accesory: .rightDetail(viewModel.state.general.sendDoNotSell + ? UserText.doNotSellEnabled + : UserText.doNotSellDisabled)) + } + + NavigationLink(destination: viewProvider.autoConsent) { + SettingsCellView(label: "Manage Cookie Popups", + accesory: .rightDetail(viewModel.state.general.autoconsentEnabled + ? UserText.autoconsentEnabled + : UserText.autoconsentDisabled)) + } + + NavigationLink(destination: viewProvider.unprotectedSites) { + SettingsCellView(label: "Unprotected SItes") + } + + NavigationLink(destination: viewProvider.fireproofSites) { + SettingsCellView(label: "Fireproof SItes") + } + + NavigationLink(destination: viewProvider.autoclearData) { + SettingsCellView(label: "Automatically Clear Data", + accesory: .rightDetail(viewModel.state.general.autoclearDataEnabled + ? UserText.autoClearAccessoryOn + : UserText.autoClearAccessoryOff)) + } + + } + + .onChange(of: isPresentingGPCView) { isActive in + if isActive { + viewModel.gpcViewPresentationAction() + } + } + + + } +} diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index df546b774b..306b61ce1c 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -31,4 +31,7 @@ struct SettingsStateGeneral { var textSize: Int = 100 var activeWebsiteAccount: SecureVaultModels.WebsiteAccount? var addressBarPosition: AddressBarPosition = .top + var sendDoNotSell: Bool = true + var autoconsentEnabled: Bool = true + var autoclearDataEnabled: Bool = true } diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index d21bce9a0f..0ed3a15557 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -25,21 +25,28 @@ import DDGSync struct SettingsSyncView: View { @EnvironmentObject var viewModel: SettingsViewModel + @EnvironmentObject var viewProvider: SettingsViewProvider + @State var isPresentingSyncView: Bool = false var body: some View { if viewModel.shouldShowSyncCell { Section { - NavigationLink(destination: LazyView(viewModel.syncSettingsControllerRepresentable), isActive: $isPresentingSyncView) { - SettingsCellView(label: UserText.syncTitle, - action: { viewModel.isPresentingSyncView = true }) + NavigationLink(destination: viewProvider.syncSettings, isActive: $isPresentingSyncView) { + SettingsCellView(label: UserText.syncTitle) } } - + .onChange(of: viewModel.isPresentingSyncView) { newValue in isPresentingSyncView = newValue } + + .onChange(of: isPresentingSyncView) { isActive in + if !isActive { + viewModel.isPresentingSyncView = false + } + } } } } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 9ebffdcab7..c61ede6c9c 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -23,6 +23,7 @@ import UIKit struct SettingsView: View { @StateObject var viewModel: SettingsViewModel + @StateObject var viewProvider: SettingsViewProvider var body: some View { List { @@ -30,11 +31,13 @@ struct SettingsView: View { SettingsSyncView() SettingsLoginsView() SettingsAppeareanceView() + SettingsPrivacyView() } .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { }) .environmentObject(viewModel) + .environmentObject(viewProvider) .onAppear { viewModel.initializeState() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 323910cb63..5c7addfcdf 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -43,39 +43,13 @@ final class SettingsViewModel: ObservableObject { var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? private(set) var model: SettingsModel - private let legacyViewProvider: SettingsLegacyViewProvider @Published private(set) var state: SettingsState - // MARK: Presentation - var isPresentingAddToDockView = false { - didSet { - if isPresentingAddToDockView == true { - let viewController = legacyViewProvider.createAddToDockViewController() - presentLegacyView(viewController, modal: true) - isPresentingAddToDockView = false - } - } - } - var isPresentingAddWidgetView = false + // Support Programatic Navigation var isPresentingSyncView = false - var isPresentingLoginsView = false { - didSet { - if isPresentingLoginsView == true { - Pixel.fire(pixel: .autofillSettingsOpened) - } - } - } - var isPresentingAppIconView = false - var isPresentingTextSettingsView = false { - didSet { - if isPresentingTextSettingsView == true { - Pixel.fire(pixel: .textSizeSettingsShown) - let viewController = legacyViewProvider.createTextSizeSettingsViewController() - pushLegacyView(viewController) - } - } - } + var isPresentingLoginsView = false + // Cell Visibility var shouldShowSyncCell: Bool { model.isFeatureAvailable(.sync) } var shouldShowLoginsCell: Bool { model.isFeatureAvailable(.autofillAccessCredentialManagement) } var shouldShowTextSizeCell: Bool { model.isFeatureAvailable(.textSize) } @@ -84,85 +58,109 @@ final class SettingsViewModel: ObservableObject { var shouldShowAddressBarPositionCell: Bool { model.isFeatureAvailable(.addressbarPosition) } var shouldShowNetworkProtectionCell: Bool { model.isFeatureAvailable(.networkProtection) } - var autofillControllerRepresentable: AutofillLoginSettingsListViewControllerRepresentable { - legacyViewProvider.createAutofillLoginSettingsListViewControllerRepresentable(delegate: self, - selectedAccount: state.general.activeWebsiteAccount) - } - - var syncSettingsControllerRepresentable: SyncSettingsViewControllerRepresentable { - legacyViewProvider.createSyncSettingsControllerRepresentable() + // Bindings + var themeBinding: Binding { + Binding( + get: { self.state.general.appTheme }, + set: { + self.model.setTheme(theme: $0) + self.state.general.appTheme = $0 + } + ) + } + var fireButtonAnimationBinding: Binding { + Binding( + get: { self.state.general.fireButtonAnimation }, + set: { + self.model.setFireButtonAnimetion($0) + self.state.general.fireButtonAnimation = $0 + } + ) + } + var addressBarPositionBinding: Binding { + Binding( + get: { + self.state.general.addressBarPosition + }, + set: { + self.state.general.addressBarPosition = $0 + self.model.setAddressBarPosition($0) + } + ) } - - init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral()), - legacyViewProvider: SettingsLegacyViewProvider) { + init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral())) { self.model = model self.state = state - self.legacyViewProvider = legacyViewProvider initializeState() } func initializeState() { - // Model should eventually be Observable + // Model should eventually be Observable, but that Requires appSettings to be update state.general.appIcon = model.appIcon state.general.fireButtonAnimation = model.fireButtonAnimation state.general.appTheme = model.appTheme state.general.textSize = model.textSize state.general.addressBarPosition = model.addressBarPosition + state.general.sendDoNotSell = model.sendDoNotSell + state.general.autoconsentEnabled = model.autoconsentEnabled + state.general.autoclearDataEnabled = model.autoclearDataEnabled } - func openCookiePopupManagement() { - // showCookiePopupManagement(animated: true) - } - -} - -// MARK: Legacy View Presentation -// These UIKit views have issues when presented via UIHostingController so -// we fall back to UIKit navigation -extension SettingsViewModel { - - private func pushLegacyView(_ view: UIViewController) { - onRequestPushLegacyView?(view) - } - - private func presentLegacyView(_ view: UIViewController, modal: Bool) { - onRequestPresentLegacyView?(view, modal) - } } -// MARK: User Actions extension SettingsViewModel { func setAsDefaultBrowser() { - Pixel.fire(pixel: .defaultBrowserButtonPressedSettings) + firePixel(.defaultBrowserButtonPressedSettings) guard let url = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(url) } - func setIsPresentingLoginsViewWithAccount(accountDetails: SecureVaultModels.WebsiteAccount) { + func shouldPresentLoginsViewWithAccount(accountDetails: SecureVaultModels.WebsiteAccount) { state.general.activeWebsiteAccount = accountDetails isPresentingLoginsView = true } - func setTheme(_ theme: ThemeName) { - model.setTheme(theme: theme) - state.general.appTheme = theme + func autofillViewPresentationAction() { + firePixel(.autofillSettingsOpened) } - func setIsPresentingAppIconView(_ value: Bool) { - isPresentingAppIconView = value + func gpcViewPresentationAction() { + firePixel(.settingsDoNotSellShown) + } + + func autoConsentPresentationAction() { + firePixel(.settingsAutoconsentShown) + } + + func openCookiePopupManagement() { + // showCookiePopupManagement(animated: true) } - func setFireButtonAnimation(_ value: FireButtonAnimationType) { - model.setFireButtonAnimetion(value) - state.general.fireButtonAnimation = value +} + +// MARK: Legacy View Presentation +// These UIKit views have issues when presented via UIHostingController so +// we fall back to UIKit navigation +extension SettingsViewModel { + + func presentTextSettingsView(_ view: UIViewController) { + firePixel(.textSizeSettingsShown) + pushLegacyView(view) + } + + func pushLegacyView(_ view: UIViewController) { + onRequestPushLegacyView?(view) + } + + func presentLegacyView(_ view: UIViewController, modal: Bool) { + onRequestPresentLegacyView?(view, modal) } - func setAddressBarPosition(_ value: AddressBarPosition) { - model.setAddressBarPosition(value) - state.general.addressBarPosition = value + private func firePixel(_ event: Pixel.Event) { + Pixel.fire(pixel: event) } } diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsViewProvider.swift similarity index 67% rename from DuckDuckGo/SettingsLegacyViewProvider.swift rename to DuckDuckGo/SettingsViewProvider.swift index 589a165596..62b767f7c3 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsViewProvider.swift @@ -23,7 +23,7 @@ import DDGSync import Core import BrowserServicesKit -class SettingsLegacyViewProvider { +class SettingsViewProvider: ObservableObject { let syncService: DDGSyncing let syncDataProviders: SyncDataProviders @@ -35,18 +35,26 @@ class SettingsLegacyViewProvider { self.appSettings = appSettings } - func createAddToDockViewController() -> UIViewController { + var addToDock: UIViewController { let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) return storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController } + + var addWidget: some View { + return WidgetEducationView() + } - func createTextSizeSettingsViewController() -> UIViewController { + var textSettings: UIViewController { let storyboard = UIStoryboard(name: "Settings", bundle: nil) return storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController } - func createAutofillLoginSettingsListViewControllerRepresentable(delegate: AutofillLoginSettingsListViewControllerDelegate, - selectedAccount: SecureVaultModels.WebsiteAccount?) -> AutofillLoginSettingsListViewControllerRepresentable { + var syncSettings: some View { + return SyncSettingsViewControllerRepresentable(syncService: syncService, syncDataProviders: syncDataProviders) + } + + func loginSettings(delegate: AutofillLoginSettingsListViewControllerDelegate, + selectedAccount: SecureVaultModels.WebsiteAccount?) -> some View { AutofillLoginSettingsListViewControllerRepresentable(appSettings: appSettings, syncService: syncService, syncDataProviders: syncDataProviders, @@ -54,9 +62,29 @@ class SettingsLegacyViewProvider { selectedAccount: selectedAccount) } - func createSyncSettingsControllerRepresentable() -> SyncSettingsViewControllerRepresentable { - return SyncSettingsViewControllerRepresentable(syncService: syncService, syncDataProviders: syncDataProviders) + var appIcon: some View { + AppIconSettingsViewControllerRepresentable() } - + + var doNotSell: some View { + DoNotSellSettingsViewControllerRepresentable() + } + + var autoConsent: some View { + AutoconsentSettingsViewControllerRepresentable() + } + + var unprotectedSites: some View { + UnprotectedSitesViewControllerRepresentable() + } + + var fireproofSites: some View { + PreserveLoginsSettingsViewControllerRepresentable() + } + + var autoclearData: some View { + AutoClearSettingsViewControllerRepresentable() + } + } diff --git a/DuckDuckGo/ThemeSettingsViewController.swift b/DuckDuckGo/ThemeSettingsViewController.swift deleted file mode 100644 index 81a0a874e1..0000000000 --- a/DuckDuckGo/ThemeSettingsViewController.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// ThemeSettingsViewController.swift -// DuckDuckGo -// -// Copyright © 2019 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit -import Core - -class ThemeSettingsViewController: UITableViewController { - - private typealias ThemeEntry = (themeName: ThemeName, displayName: String) - - private lazy var appSettings = AppDependencyProvider.shared.appSettings - - private let previousTheme = AppDependencyProvider.shared.appSettings.currentThemeName - - private lazy var availableThemes: [ThemeEntry] = { - return [(ThemeName.systemDefault, UserText.themeNameDefault), - (ThemeName.light, UserText.themeNameLight), - (ThemeName.dark, UserText.themeNameDark)] - }() - - override func viewDidLoad() { - super.viewDidLoad() - - applyTheme(ThemeManager.shared.currentTheme) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return availableThemes.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return tableView.dequeueReusableCell(withIdentifier: "ThemeItemCell", for: indexPath) - } - - override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - guard let cell = cell as? ThemeItemCell else { - fatalError("Expected ThemeItemCell") - } - - let theme = ThemeManager.shared.currentTheme - cell.backgroundColor = theme.tableCellBackgroundColor - - // Checkmark color - cell.tintColor = theme.buttonTintColor - cell.themeNameLabel.textColor = theme.tableCellTextColor - - cell.themeName = availableThemes[indexPath.row].displayName - - let themeName = availableThemes[indexPath.row].themeName - cell.accessoryType = themeName == appSettings.currentThemeName ? .checkmark : .none - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let theme = availableThemes[indexPath.row].themeName - appSettings.currentThemeName = theme - - ThemeManager.shared.enableTheme(with: theme) - - ThemeManager.shared.updateUserInterfaceStyle() - } -} - -class ThemeItemCell: UITableViewCell { - - @IBOutlet weak var themeNameLabel: UILabel! - - var themeName: String? { - get { - return themeNameLabel.text - } - set { - themeNameLabel.text = newValue - } - } -} - -extension ThemeSettingsViewController: Themable { - - func decorate(with theme: Theme) { - - tableView.backgroundColor = theme.backgroundColor - tableView.separatorColor = theme.tableCellSeparatorColor - - tableView.reloadData() - } -} diff --git a/DuckDuckGo/UnprotectedSitesViewController.swift b/DuckDuckGo/UnprotectedSitesViewController.swift index 4fc53ac499..08645ed7c3 100644 --- a/DuckDuckGo/UnprotectedSitesViewController.swift +++ b/DuckDuckGo/UnprotectedSitesViewController.swift @@ -20,6 +20,30 @@ import UIKit import Core import BrowserServicesKit +import SwiftUI + +// MARK: UnprotectedSitesViewController Representable +struct UnprotectedSitesViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = UnprotectedSitesViewController + + class Coordinator { + var parentObserver: NSKeyValueObservation? + } + + func makeUIViewController(context: Self.Context) -> UnprotectedSitesViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + let viewController = storyboard.instantiateViewController(identifier: "UnprotectedSites") as! UnprotectedSitesViewController + context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in + vc.parent?.title = vc.title + }) + return viewController + } + + func updateUIViewController(_ uiViewController: UnprotectedSitesViewController, context: Context) {} + + func makeCoordinator() -> Self.Coordinator { Coordinator() } +} class UnprotectedSitesViewController: UITableViewController { From a63bb5ade15d12115d8084319c5a47bdfc9c5012 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 7 Dec 2023 19:26:09 +0100 Subject: [PATCH 06/99] Application Lock --- DuckDuckGo.xcodeproj/project.pbxproj | 4 --- DuckDuckGo/LazyView.swift | 34 -------------------------- DuckDuckGo/SettingsModel.swift | 6 +++++ DuckDuckGo/SettingsPrivacyView.swift | 2 ++ DuckDuckGo/SettingsState.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 10 ++++++++ DuckDuckGo/SettingsViewProvider.swift | 35 ++++++++++++++++++--------- 7 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 DuckDuckGo/LazyView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8f60ed0e32..b0cdf88c5b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -778,7 +778,6 @@ D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; D6E83C562B21ECC1006C8AFB /* SettingsViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */; }; - D6E83C582B22020D006C8AFB /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C572B22020D006C8AFB /* LazyView.swift */; }; D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; @@ -2413,7 +2412,6 @@ D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewProvider.swift; sourceTree = ""; }; - D6E83C572B22020D006C8AFB /* LazyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; @@ -5151,7 +5149,6 @@ F1AB2B401E3F75A000868554 /* Settings */ = { isa = PBXGroup; children = ( - D6E83C572B22020D006C8AFB /* LazyView.swift */, D6E83C492B20C883006C8AFB /* Model */, D6E83C3B2B1F27BA006C8AFB /* Views */, 858566F1252E55AE007501B8 /* Debug */, @@ -6364,7 +6361,6 @@ 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */, 4BBBBA8F2B031B4200D965DA /* VPNWaitlistView.swift in Sources */, CB258D1229A4F24900DEBA24 /* ConfigurationManager.swift in Sources */, - D6E83C582B22020D006C8AFB /* LazyView.swift in Sources */, 8546A54A2A672959003929BF /* MainViewController+Email.swift in Sources */, F4F6DFB226E6AEC100ED7E12 /* AddOrEditBookmarkViewController.swift in Sources */, EE458D0D2AB1DA4600FC651A /* EventMapping+NetworkProtectionError.swift in Sources */, diff --git a/DuckDuckGo/LazyView.swift b/DuckDuckGo/LazyView.swift deleted file mode 100644 index 72e03217c5..0000000000 --- a/DuckDuckGo/LazyView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// LazyView.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 - -/// Delays the instantiation of a view content until necessary. -/// This prevents SwiftUI from preloading views that are not immediately -/// visible, sucn as in NavigationLink - -struct LazyView: View { - let build: () -> Content - init(_ build: @autoclosure @escaping () -> Content) { - self.build = build - } - var body: some View { - build() - } -} diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index 99987d3f02..abd596e7f1 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -42,6 +42,7 @@ class SettingsModel { private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad + private var privacyStore = PrivacyUserDefaults() var appIcon: AppIcon = AppIcon.defaultAppIcon var fireButtonAnimation: FireButtonAnimationType { appSettings.currentFireButtonAnimation } @@ -57,6 +58,7 @@ class SettingsModel { return false } } + var applicationLock: Bool { privacyStore.authenticationEnabled } #if NETWORK_PROTECTION @@ -139,5 +141,9 @@ class SettingsModel { func setAddressBarPosition(_ position: AddressBarPosition) { appSettings.currentAddressBarPosition = position } + + func setApplicationLock(_ value: Bool) { + privacyStore.authenticationEnabled = value + } } diff --git a/DuckDuckGo/SettingsPrivacyView.swift b/DuckDuckGo/SettingsPrivacyView.swift index 6080a21f4b..4fe3b5e8fd 100644 --- a/DuckDuckGo/SettingsPrivacyView.swift +++ b/DuckDuckGo/SettingsPrivacyView.swift @@ -59,6 +59,8 @@ struct SettingsPrivacyView: View { : UserText.autoClearAccessoryOff)) } + SettingsCellView(label: "Application Lock", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + } .onChange(of: isPresentingGPCView) { isActive in diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 306b61ce1c..53635cb3b1 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -34,4 +34,5 @@ struct SettingsStateGeneral { var sendDoNotSell: Bool = true var autoconsentEnabled: Bool = true var autoclearDataEnabled: Bool = true + var applicationLock: Bool = true } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 5c7addfcdf..f80b73c033 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -88,6 +88,15 @@ final class SettingsViewModel: ObservableObject { } ) } + var applicationLockBinding: Binding { + Binding( + get: { self.state.general.applicationLock }, + set: { + self.state.general.applicationLock = $0 + self.model.setApplicationLock($0) + } + ) + } init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral())) { self.model = model @@ -105,6 +114,7 @@ final class SettingsViewModel: ObservableObject { state.general.sendDoNotSell = model.sendDoNotSell state.general.autoconsentEnabled = model.autoconsentEnabled state.general.autoclearDataEnabled = model.autoclearDataEnabled + state.general.applicationLock = model.applicationLock } diff --git a/DuckDuckGo/SettingsViewProvider.swift b/DuckDuckGo/SettingsViewProvider.swift index 62b767f7c3..017e626973 100644 --- a/DuckDuckGo/SettingsViewProvider.swift +++ b/DuckDuckGo/SettingsViewProvider.swift @@ -23,6 +23,16 @@ import DDGSync import Core import BrowserServicesKit +struct LazyView: View { + let build: () -> Content + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + var body: some View { + build() + } +} + class SettingsViewProvider: ObservableObject { let syncService: DDGSyncing @@ -41,7 +51,7 @@ class SettingsViewProvider: ObservableObject { } var addWidget: some View { - return WidgetEducationView() + return LazyView(WidgetEducationView()) } var textSettings: UIViewController { @@ -50,40 +60,41 @@ class SettingsViewProvider: ObservableObject { } var syncSettings: some View { - return SyncSettingsViewControllerRepresentable(syncService: syncService, syncDataProviders: syncDataProviders) + return LazyView(SyncSettingsViewControllerRepresentable(syncService: self.syncService, + syncDataProviders: self.syncDataProviders)) } func loginSettings(delegate: AutofillLoginSettingsListViewControllerDelegate, selectedAccount: SecureVaultModels.WebsiteAccount?) -> some View { - AutofillLoginSettingsListViewControllerRepresentable(appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, + LazyView(AutofillLoginSettingsListViewControllerRepresentable(appSettings: self.appSettings, + syncService: self.syncService, + syncDataProviders: self.syncDataProviders, delegate: delegate, - selectedAccount: selectedAccount) + selectedAccount: selectedAccount)) } var appIcon: some View { - AppIconSettingsViewControllerRepresentable() + LazyView(AppIconSettingsViewControllerRepresentable()) } var doNotSell: some View { - DoNotSellSettingsViewControllerRepresentable() + LazyView(DoNotSellSettingsViewControllerRepresentable()) } var autoConsent: some View { - AutoconsentSettingsViewControllerRepresentable() + LazyView(AutoconsentSettingsViewControllerRepresentable()) } var unprotectedSites: some View { - UnprotectedSitesViewControllerRepresentable() + LazyView(UnprotectedSitesViewControllerRepresentable()) } var fireproofSites: some View { - PreserveLoginsSettingsViewControllerRepresentable() + LazyView(PreserveLoginsSettingsViewControllerRepresentable()) } var autoclearData: some View { - AutoClearSettingsViewControllerRepresentable() + LazyView(AutoClearSettingsViewControllerRepresentable()) } From 1779ecce0dcd9f2f64c3e7c8524f04f896832555 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 7 Dec 2023 23:35:09 +0100 Subject: [PATCH 07/99] Present legacy views from the Root Navigation Controller --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +- .../AppIconSettingsViewController.swift | 24 ---- .../AutoClearSettingsViewController.swift | 23 ---- .../AutoconsentSettingsViewController.swift | 24 ---- ...ofillLoginSettingsListViewController.swift | 39 ------ DuckDuckGo/Base.lproj/Settings.storyboard | 2 +- .../DoNotSellSettingsViewController.swift | 24 ---- DuckDuckGo/MainViewController+Segues.swift | 13 +- ...PreserveLoginsSettingsViewController.swift | 24 ---- DuckDuckGo/SettingsAppeareanceView.swift | 24 ++-- DuckDuckGo/SettingsCell.swift | 4 +- DuckDuckGo/SettingsCustomizeView.swift | 45 +++++++ DuckDuckGo/SettingsGeneralView.swift | 9 +- DuckDuckGo/SettingsHostingController.swift | 11 +- DuckDuckGo/SettingsLegacyViewProvider.swift | 109 +++++++++++++++ DuckDuckGo/SettingsLoginsView.swift | 26 +--- DuckDuckGo/SettingsModel.swift | 75 ++++++----- DuckDuckGo/SettingsPrivacyView.swift | 70 +++++----- DuckDuckGo/SettingsState.swift | 4 + DuckDuckGo/SettingsSyncView.swift | 22 +-- DuckDuckGo/SettingsView.swift | 3 +- DuckDuckGo/SettingsViewModel.swift | 126 +++++++++++++----- DuckDuckGo/SettingsViewProvider.swift | 101 -------------- DuckDuckGo/SyncSettingsViewController.swift | 27 ---- .../UnprotectedSitesViewController.swift | 24 ---- 25 files changed, 382 insertions(+), 483 deletions(-) create mode 100644 DuckDuckGo/SettingsCustomizeView.swift create mode 100644 DuckDuckGo/SettingsLegacyViewProvider.swift delete mode 100644 DuckDuckGo/SettingsViewProvider.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b0cdf88c5b..939bfb45ad 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -777,8 +777,9 @@ D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; - D6E83C562B21ECC1006C8AFB /* SettingsViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */; }; + D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; + D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2411,8 +2412,9 @@ D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; - D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewProvider.swift; sourceTree = ""; }; + D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; + D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCustomizeView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4494,6 +4496,7 @@ D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */, D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, + D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, ); name = Sections; sourceTree = ""; @@ -4516,7 +4519,7 @@ D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */, D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */, D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, - D6E83C552B21ECC1006C8AFB /* SettingsViewProvider.swift */, + D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, ); name = Model; sourceTree = ""; @@ -6368,6 +6371,7 @@ F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */, + D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, @@ -6621,7 +6625,7 @@ 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, - D6E83C562B21ECC1006C8AFB /* SettingsViewProvider.swift in Sources */, + D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, diff --git a/DuckDuckGo/AppIconSettingsViewController.swift b/DuckDuckGo/AppIconSettingsViewController.swift index f9ead3ea68..e80e9c69f6 100644 --- a/DuckDuckGo/AppIconSettingsViewController.swift +++ b/DuckDuckGo/AppIconSettingsViewController.swift @@ -19,30 +19,6 @@ import UIKit import Core -import SwiftUI - -// MARK: App Icon Settings Representable -struct AppIconSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = AppIconSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> AppIconSettingsViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "AppIcon") as! AppIconSettingsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController - } - - func updateUIViewController(_ uiViewController: AppIconSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} class AppIconSettingsViewController: UICollectionViewController { diff --git a/DuckDuckGo/AutoClearSettingsViewController.swift b/DuckDuckGo/AutoClearSettingsViewController.swift index 6c17cde96c..77fb3173ce 100644 --- a/DuckDuckGo/AutoClearSettingsViewController.swift +++ b/DuckDuckGo/AutoClearSettingsViewController.swift @@ -22,29 +22,6 @@ import MessageUI import Core import SwiftUI -// MARK: AutoClearSettingsViewController Representable -struct AutoClearSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = AutoClearSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> AutoClearSettingsViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "AutoClearSettingsViewController") as! AutoClearSettingsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController - } - - func updateUIViewController(_ uiViewController: AutoClearSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} - class AutoClearSettingsViewController: UITableViewController { enum Sections: Int, CaseIterable { diff --git a/DuckDuckGo/AutoconsentSettingsViewController.swift b/DuckDuckGo/AutoconsentSettingsViewController.swift index 188b63a587..470ba711d4 100644 --- a/DuckDuckGo/AutoconsentSettingsViewController.swift +++ b/DuckDuckGo/AutoconsentSettingsViewController.swift @@ -19,30 +19,6 @@ import UIKit import Core -import SwiftUI - -// MARK: AutoconsentSettingsViewController Representable -struct AutoconsentSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = AutoconsentSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> AutoconsentSettingsViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "AutoconsentSettingsViewController") as! AutoconsentSettingsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController - } - - func updateUIViewController(_ uiViewController: AutoconsentSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} final class AutoconsentSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index f3765d11a8..7e29c142d6 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -27,45 +27,6 @@ import DesignResourcesKit import SwiftUI // swiftlint:disable file_length type_body_length - -struct AutofillLoginSettingsListViewControllerRepresentable: UIViewControllerRepresentable { - - let appSettings: AppSettings - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - let delegate: AutofillLoginSettingsListViewControllerDelegate - let selectedAccount: SecureVaultModels.WebsiteAccount? - - typealias UIViewControllerType = AutofillLoginSettingsListViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> AutofillLoginSettingsListViewController { - let autofillController = AutofillLoginSettingsListViewController( - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, - selectedAccount: selectedAccount - ) - - context.coordinator.parentObserver = autofillController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems - - }) - - return autofillController - } - - func updateUIViewController(_ uiViewController: AutofillLoginSettingsListViewController, context: Self.Context) { - - } - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} - protocol AutofillLoginSettingsListViewControllerDelegate: AnyObject { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) } diff --git a/DuckDuckGo/Base.lproj/Settings.storyboard b/DuckDuckGo/Base.lproj/Settings.storyboard index 844049a51c..52538150b8 100644 --- a/DuckDuckGo/Base.lproj/Settings.storyboard +++ b/DuckDuckGo/Base.lproj/Settings.storyboard @@ -2069,7 +2069,7 @@ - + diff --git a/DuckDuckGo/DoNotSellSettingsViewController.swift b/DuckDuckGo/DoNotSellSettingsViewController.swift index 52bae399c0..735c66b652 100644 --- a/DuckDuckGo/DoNotSellSettingsViewController.swift +++ b/DuckDuckGo/DoNotSellSettingsViewController.swift @@ -19,30 +19,6 @@ import UIKit import Core -import SwiftUI - -// MARK: DoNotSellSettingsView Settings Representable -struct DoNotSellSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = DoNotSellSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> DoNotSellSettingsViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "DoNotSell") as! DoNotSellSettingsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController - } - - func updateUIViewController(_ uiViewController: DoNotSellSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} class DoNotSellSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 5b0cb82e66..4add5846e7 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -204,7 +204,7 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.isPresentingSyncView = true + $0.presentView(.sync) } } @@ -217,19 +217,22 @@ extension MainViewController { } private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { - let legacyViewProvider = SettingsViewProvider(syncService: syncService, - syncDataProviders: syncDataProviders, - appSettings: appSettings) + let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, + syncDataProviders: syncDataProviders, + appSettings: appSettings) let model = SettingsModel(bookmarksDatabase: self.bookmarksDatabase, internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - let settingsViewModel = SettingsViewModel(model: model) + let settingsViewModel = SettingsViewModel(model: model, + legacyViewProvider: legacyViewProvider) let settingsController = SettingsHostingController(viewModel: settingsViewModel, viewProvider: legacyViewProvider) + settingsController.applyTheme(ThemeManager.shared.currentTheme) // We are still presenting legacy views, so use a Navcontroller let navController = UINavigationController(rootViewController: settingsController) + navController.applyTheme(ThemeManager.shared.currentTheme) settingsController.modalPresentationStyle = .automatic present(navController, animated: true) { diff --git a/DuckDuckGo/PreserveLoginsSettingsViewController.swift b/DuckDuckGo/PreserveLoginsSettingsViewController.swift index 3b2067c83b..35cc331f12 100644 --- a/DuckDuckGo/PreserveLoginsSettingsViewController.swift +++ b/DuckDuckGo/PreserveLoginsSettingsViewController.swift @@ -19,30 +19,6 @@ import UIKit import Core -import SwiftUI - -// MARK: PreserveLoginsSettingsViewController Representable -struct PreserveLoginsSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = PreserveLoginsSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> PreserveLoginsSettingsViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "FireProofSites") as! PreserveLoginsSettingsViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController - } - - func updateUIViewController(_ uiViewController: PreserveLoginsSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} class PreserveLoginsSettingsViewController: UITableViewController { diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index b9e758c851..fedfd55f15 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -1,5 +1,4 @@ -// TODO: Remove transition animation if showing a selected account// -// GeneralSection.swift +// SettingsAppeareanceView.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -23,20 +22,21 @@ import UIKit struct SettingsAppeareanceView: View { @EnvironmentObject var viewModel: SettingsViewModel - @EnvironmentObject var viewProvider: SettingsViewProvider var body: some View { + Section(header: Text("Appeareance")) { SettingsPickerCellView(label: "Theme", options: ThemeName.allCases, selectedOption: viewModel.themeBinding) - - NavigationLink(destination: viewProvider.appIcon) { - let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) - SettingsCellView(label: "App Icon", - accesory: .image(image)) - } - + + let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) + SettingsCellView(label: "App Icon", + action: { viewModel.presentView(.appIcon ) }, + accesory: .image(image), + asLink: true, + disclosureIndicator: true) + SettingsPickerCellView(label: "Fire Button Animation", options: FireButtonAnimationType.allCases, selectedOption: viewModel.fireButtonAnimationBinding) @@ -45,9 +45,7 @@ struct SettingsAppeareanceView: View { // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", - action: { - viewModel.presentTextSettingsView(viewProvider.textSettings) - }, + action: { viewModel.presentView(.textSize) }, accesory: .rightDetail("\(viewModel.state.general.textSize)%"), asLink: true) } diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 35393ba42e..adfd7345a3 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -20,7 +20,8 @@ import SwiftUI /// Encapsulates a View representing a Cell with different configurations -struct SettingsCellView: View { +struct SettingsCellView: View, Identifiable { + enum Accesory { case none case rightDetail(String) @@ -36,6 +37,7 @@ struct SettingsCellView: View { var accesory: Accesory var asLink: Bool var disclosureIndicator: Bool + var id: UUID = UUID() /// Initializes a `SettingsCellView` with the specified label and accesory. /// diff --git a/DuckDuckGo/SettingsCustomizeView.swift b/DuckDuckGo/SettingsCustomizeView.swift new file mode 100644 index 0000000000..cb29b88c5a --- /dev/null +++ b/DuckDuckGo/SettingsCustomizeView.swift @@ -0,0 +1,45 @@ +// SettingsPrivacyView.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 UIKit + +struct SettingsCustomizeView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + @EnvironmentObject var viewProvider: SettingsLegacyViewProvider + + var body: some View { + Section(header: Text("Customize"), + footer: Text("Disable to prevent links from automatically opening in other installed apps")) { + + SettingsCellView(label: "Keyboard", + action: { viewModel.presentView(.keyboard) }, + asLink: true, + disclosureIndicator: true) + + SettingsCellView(label: "Autocomplete Suggestions", accesory: .toggle(isOn: viewModel.autocompleteBinding)) + if viewModel.shouldShowSpeechRecognitionCell { + SettingsCellView(label: "Private Voice Search", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + } + SettingsCellView(label: "Long-Press Previews", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + SettingsCellView(label: "Open Links in Associated Apps", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + + } + } +} diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index df8abf0466..3f32e9de40 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -1,5 +1,5 @@ // -// GeneralSection.swift +// SettingsGeneralView.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -23,7 +23,6 @@ import UIKit struct SettingsGeneralView: View { @EnvironmentObject var viewModel: SettingsViewModel - @EnvironmentObject var viewProvider: SettingsViewProvider var body: some View { Section { @@ -34,12 +33,10 @@ struct SettingsGeneralView: View { // This old VC has a special behavior does not work as expected when presented in the SwiftUI Stack // so we need to push it via the UIKit Containe SettingsCellView(label: "Add App to Your Dock", - action: { - viewModel.presentLegacyView(viewProvider.addToDock, modal: true) - }, + action: { viewModel.presentView(.addToDock) }, asLink: true) - NavigationLink(destination: viewProvider.addWidget) { + NavigationLink(destination: WidgetEducationView()) { SettingsCellView(label: "Add Widget to Home Screen") } } diff --git a/DuckDuckGo/SettingsHostingController.swift b/DuckDuckGo/SettingsHostingController.swift index 2f801e9dc5..899bf29ff5 100644 --- a/DuckDuckGo/SettingsHostingController.swift +++ b/DuckDuckGo/SettingsHostingController.swift @@ -22,9 +22,14 @@ import SwiftUI class SettingsHostingController: UIHostingController { var viewModel: SettingsViewModel - var viewProvider: SettingsViewProvider + var viewProvider: SettingsLegacyViewProvider - init(viewModel: SettingsViewModel, viewProvider: SettingsViewProvider) { + override func viewDidLoad() { + super.viewDidLoad() + applyTheme(ThemeManager.shared.currentTheme) + } + + init(viewModel: SettingsViewModel, viewProvider: SettingsLegacyViewProvider) { self.viewModel = viewModel self.viewProvider = viewProvider super.init(rootView: AnyView(EmptyView())) @@ -37,7 +42,7 @@ class SettingsHostingController: UIHostingController { self?.presentLegacyViewCOntroller(vc, modal: modal) } - let settingsView = SettingsView(viewModel: viewModel, viewProvider: viewProvider) + let settingsView = SettingsView(viewModel: viewModel) self.rootView = AnyView(settingsView) } diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift new file mode 100644 index 0000000000..39bb345a79 --- /dev/null +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -0,0 +1,109 @@ +// +// LegacyViewProvider.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 DDGSync +import Core +import BrowserServicesKit +import SyncUI + +struct LazyView: View { + let build: () -> Content + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + var body: some View { + build() + } +} + +class SettingsLegacyViewProvider: ObservableObject { + + let syncService: DDGSyncing + let syncDataProviders: SyncDataProviders + let appSettings: AppSettings + + init(syncService: DDGSyncing, syncDataProviders: SyncDataProviders, appSettings: AppSettings) { + self.syncService = syncService + self.syncDataProviders = syncDataProviders + self.appSettings = appSettings + } + + // Legacy UIKit Views (Pushed unmodified) + var addToDock: UIViewController { + let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) + return storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController + } + + @MainActor + var syncSettings: UIViewController { + return SyncSettingsViewController(syncService: self.syncService, + syncBookmarksAdapter: self.syncDataProviders.bookmarksAdapter, + appSettings: self.appSettings) + } + + func loginSettings(delegate: AutofillLoginSettingsListViewControllerDelegate, + selectedAccount: SecureVaultModels.WebsiteAccount?) -> AutofillLoginSettingsListViewController { + return AutofillLoginSettingsListViewController(appSettings: self.appSettings, + syncService: self.syncService, + syncDataProviders: self.syncDataProviders, + selectedAccount: selectedAccount) + } + + var textSettings: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController + } + + var appIcon: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "AppIcon") as! AppIconSettingsViewController + } + + var gpc: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "DoNotSell") as! DoNotSellSettingsViewController + } + + var autoConsent: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "AutoconsentSettingsViewController") as! AutoconsentSettingsViewController + } + + var unprotectedSites: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "UnprotectedSites") as! UnprotectedSitesViewController + } + + var fireproofSites: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "FireProofSites") as! PreserveLoginsSettingsViewController + } + + var autoclearData: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "AutoClearSettingsViewController") as! AutoClearSettingsViewController + } + + var keyboard: UIViewController { + let storyboard = UIStoryboard(name: "Settings", bundle: nil) + return storyboard.instantiateViewController(identifier: "Keyboard") as! KeyboardSettingsViewController + } +} diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index 88d8b714c8..fbbca587c4 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -1,5 +1,4 @@ -// TODO: Remove transition animation if showing a selected account// -// GeneralSection.swift +// SettingsLoginsView.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -26,29 +25,14 @@ import BrowserServicesKit struct SettingsLoginsView: View { @EnvironmentObject var viewModel: SettingsViewModel - @EnvironmentObject var viewProvider: SettingsViewProvider - @State var isPresentingLoginsView: Bool = false var body: some View { if viewModel.shouldShowLoginsCell { Section { - NavigationLink(destination: viewProvider.loginSettings(delegate: viewModel, selectedAccount: viewModel.state.general.activeWebsiteAccount), - isActive: $isPresentingLoginsView) { - SettingsCellView(label: UserText.autofillLoginListTitle ) - - } - .onChange(of: viewModel.isPresentingLoginsView) { newValue in - isPresentingLoginsView = newValue - } - - .onChange(of: isPresentingLoginsView) { isActive in - if isActive { - viewModel.autofillViewPresentationAction() - } else { - viewModel.isPresentingLoginsView = false - } - } - + SettingsCellView(label: "Logins", + action: { viewModel.presentView(.logins) }, + asLink: true, + disclosureIndicator: true) } } diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index abd596e7f1..5c02246a92 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -45,10 +45,34 @@ class SettingsModel { private var privacyStore = PrivacyUserDefaults() var appIcon: AppIcon = AppIcon.defaultAppIcon - var fireButtonAnimation: FireButtonAnimationType { appSettings.currentFireButtonAnimation } - var appTheme: ThemeName { appSettings.currentThemeName } + var fireButtonAnimation: FireButtonAnimationType { + get { appSettings.currentFireButtonAnimation } + set { + appSettings.currentFireButtonAnimation = newValue + NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) + + animator.animate { + // no op + } onTransitionCompleted: { + // no op + } completion: { + // no op + } + } + } + var appTheme: ThemeName { + get { appSettings.currentThemeName } + set { + appSettings.currentThemeName = newValue + ThemeManager.shared.enableTheme(with: newValue) + ThemeManager.shared.updateUserInterfaceStyle() + } + } var textSize: Int { appSettings.textSize } - var addressBarPosition: AddressBarPosition { appSettings.currentAddressBarPosition } + var addressBarPosition: AddressBarPosition { + get { appSettings.currentAddressBarPosition } + set { appSettings.currentAddressBarPosition = newValue } + } var sendDoNotSell: Bool { appSettings.sendDoNotSell } var autoconsentEnabled: Bool { appSettings.autoconsentEnabled } var autoclearDataEnabled: Bool { @@ -58,7 +82,20 @@ class SettingsModel { return false } } - var applicationLock: Bool { privacyStore.authenticationEnabled } + var applicationLock: Bool { + get { privacyStore.authenticationEnabled } + set { privacyStore.authenticationEnabled = newValue } + } + var autocomplete: Bool { + get { appSettings.autocomplete } + set { appSettings.autocomplete = newValue } + } + var voiceSearchEnabled: Bool { + get { appSettings.voiceSearchEnabled } + set { appSettings.autocomplete = newValue } + } + var longPressPreviews: Bool { appSettings.longPressPreviews } + var allowUniversalLinks: Bool { appSettings.allowUniversalLinks } #if NETWORK_PROTECTION @@ -79,6 +116,7 @@ class SettingsModel { case textSize case voiceSearch case addressbarPosition + case speechRecognition #if NETWORK_PROTECTION case networkProtection @@ -116,34 +154,9 @@ class SettingsModel { return AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable case .addressbarPosition: return !isPad + case .speechRecognition: + return AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } } - func setTheme(theme: ThemeName) { - appSettings.currentThemeName = theme - ThemeManager.shared.enableTheme(with: theme) - ThemeManager.shared.updateUserInterfaceStyle() - } - - func setFireButtonAnimetion(_ value: FireButtonAnimationType) { - appSettings.currentFireButtonAnimation = value - NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) - - animator.animate { - // no op - } onTransitionCompleted: { - // no op - } completion: { - // no op - } - } - - func setAddressBarPosition(_ position: AddressBarPosition) { - appSettings.currentAddressBarPosition = position - } - - func setApplicationLock(_ value: Bool) { - privacyStore.authenticationEnabled = value - } - } diff --git a/DuckDuckGo/SettingsPrivacyView.swift b/DuckDuckGo/SettingsPrivacyView.swift index 4fe3b5e8fd..238d4e87bb 100644 --- a/DuckDuckGo/SettingsPrivacyView.swift +++ b/DuckDuckGo/SettingsPrivacyView.swift @@ -1,5 +1,4 @@ -// TODO: Remove transition animation if showing a selected account// -// GeneralSection.swift +// SettingsPrivacyView.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -23,52 +22,47 @@ import UIKit struct SettingsPrivacyView: View { @EnvironmentObject var viewModel: SettingsViewModel - @EnvironmentObject var viewProvider: SettingsViewProvider - @State var isPresentingGPCView = false var body: some View { Section(header: Text("Privacy"), footer: Text("If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening.")) { - NavigationLink(destination: viewProvider.doNotSell, isActive: $isPresentingGPCView) { - SettingsCellView(label: "Global Privacy Control (GPC)", - accesory: .rightDetail(viewModel.state.general.sendDoNotSell - ? UserText.doNotSellEnabled - : UserText.doNotSellDisabled)) - } - - NavigationLink(destination: viewProvider.autoConsent) { - SettingsCellView(label: "Manage Cookie Popups", - accesory: .rightDetail(viewModel.state.general.autoconsentEnabled - ? UserText.autoconsentEnabled - : UserText.autoconsentDisabled)) - } - - NavigationLink(destination: viewProvider.unprotectedSites) { - SettingsCellView(label: "Unprotected SItes") - } + SettingsCellView(label: "Global Privacy Control (GPC)", + action: { viewModel.presentView(.gpc) }, + accesory: .rightDetail(viewModel.state.general.sendDoNotSell + ? UserText.doNotSellEnabled + : UserText.doNotSellDisabled), + asLink: true, + disclosureIndicator: true) + + SettingsCellView(label: "Manage Cookie Popups", + action: { viewModel.presentView(.autoconsent) }, + accesory: .rightDetail(viewModel.state.general.autoconsentEnabled + ? UserText.autoconsentEnabled + : UserText.autoconsentDisabled), + asLink: true, + disclosureIndicator: true) + + SettingsCellView(label: "Unprotected SItes", + action: { viewModel.presentView(.unprotectedSites) }, + asLink: true, + disclosureIndicator: true) - NavigationLink(destination: viewProvider.fireproofSites) { - SettingsCellView(label: "Fireproof SItes") - } + SettingsCellView(label: "Fireproof Sites", + action: { viewModel.presentView(.fireproofSites) }, + asLink: true, + disclosureIndicator: true) - NavigationLink(destination: viewProvider.autoclearData) { - SettingsCellView(label: "Automatically Clear Data", - accesory: .rightDetail(viewModel.state.general.autoclearDataEnabled - ? UserText.autoClearAccessoryOn - : UserText.autoClearAccessoryOff)) - } + SettingsCellView(label: "Automatically Clear Data", + action: { viewModel.presentView(.autoclearData) }, + accesory: .rightDetail(viewModel.state.general.autoclearDataEnabled + ? UserText.autoClearAccessoryOn + : UserText.autoClearAccessoryOff), + asLink: true, + disclosureIndicator: true) SettingsCellView(label: "Application Lock", accesory: .toggle(isOn: viewModel.applicationLockBinding)) } - - .onChange(of: isPresentingGPCView) { isActive in - if isActive { - viewModel.gpcViewPresentationAction() - } - } - - } } diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 53635cb3b1..23428d6c92 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -35,4 +35,8 @@ struct SettingsStateGeneral { var autoconsentEnabled: Bool = true var autoclearDataEnabled: Bool = true var applicationLock: Bool = true + var autocomplete: Bool = true + var voiceSearchEnabled = true + var longPressPreviews = true + var allowUniversalLinks = true } diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index 0ed3a15557..51c402525b 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -1,5 +1,5 @@ // -// GeneralSection.swift +// SettingsSyncView.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -25,7 +25,7 @@ import DDGSync struct SettingsSyncView: View { @EnvironmentObject var viewModel: SettingsViewModel - @EnvironmentObject var viewProvider: SettingsViewProvider + @EnvironmentObject var viewProvider: SettingsLegacyViewProvider @State var isPresentingSyncView: Bool = false @@ -33,20 +33,12 @@ struct SettingsSyncView: View { var body: some View { if viewModel.shouldShowSyncCell { Section { - NavigationLink(destination: viewProvider.syncSettings, isActive: $isPresentingSyncView) { - SettingsCellView(label: UserText.syncTitle) - } - } - - .onChange(of: viewModel.isPresentingSyncView) { newValue in - isPresentingSyncView = newValue - } - - .onChange(of: isPresentingSyncView) { isActive in - if !isActive { - viewModel.isPresentingSyncView = false - } + SettingsCellView(label: UserText.syncTitle, + action: { viewModel.presentView(.sync) }, + asLink: true, + disclosureIndicator: true) } + } } } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index c61ede6c9c..ba2a65ae37 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -23,7 +23,6 @@ import UIKit struct SettingsView: View { @StateObject var viewModel: SettingsViewModel - @StateObject var viewProvider: SettingsViewProvider var body: some View { List { @@ -32,12 +31,12 @@ struct SettingsView: View { SettingsLoginsView() SettingsAppeareanceView() SettingsPrivacyView() + SettingsCustomizeView() } .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { }) .environmentObject(viewModel) - .environmentObject(viewProvider) .onAppear { viewModel.initializeState() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index f80b73c033..a7dbd29707 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -34,9 +34,7 @@ import NetworkProtection final class SettingsViewModel: ObservableObject { - // MARK: - var appIconSubscription: AnyCancellable? - var stateSubscriber: AnyCancellable? + var legacyViewProvider: SettingsLegacyViewProvider // Closure to request a legacy view controller presentation var onRequestPushLegacyView: ((UIViewController) -> Void)? @@ -46,7 +44,6 @@ final class SettingsViewModel: ObservableObject { @Published private(set) var state: SettingsState // Support Programatic Navigation - var isPresentingSyncView = false var isPresentingLoginsView = false // Cell Visibility @@ -57,13 +54,14 @@ final class SettingsViewModel: ObservableObject { var shouldShowVoiceSearchCell: Bool { model.isFeatureAvailable(.voiceSearch) } var shouldShowAddressBarPositionCell: Bool { model.isFeatureAvailable(.addressbarPosition) } var shouldShowNetworkProtectionCell: Bool { model.isFeatureAvailable(.networkProtection) } + var shouldShowSpeechRecognitionCell: Bool { model.isFeatureAvailable(.speechRecognition) } // Bindings var themeBinding: Binding { Binding( get: { self.state.general.appTheme }, set: { - self.model.setTheme(theme: $0) + self.model.appTheme = $0 self.state.general.appTheme = $0 } ) @@ -72,7 +70,7 @@ final class SettingsViewModel: ObservableObject { Binding( get: { self.state.general.fireButtonAnimation }, set: { - self.model.setFireButtonAnimetion($0) + self.model.fireButtonAnimation = $0 self.state.general.fireButtonAnimation = $0 } ) @@ -84,7 +82,7 @@ final class SettingsViewModel: ObservableObject { }, set: { self.state.general.addressBarPosition = $0 - self.model.setAddressBarPosition($0) + self.model.addressBarPosition = $0 } ) } @@ -93,14 +91,35 @@ final class SettingsViewModel: ObservableObject { get: { self.state.general.applicationLock }, set: { self.state.general.applicationLock = $0 - self.model.setApplicationLock($0) + self.model.applicationLock = $0 + } + ) + } + var autocompleteBinding: Binding { + Binding( + get: { self.state.general.autocomplete }, + set: { + self.state.general.autocomplete = $0 + self.model.autocomplete = $0 + } + ) + } + var voiceSearchEnabledBinding: Binding { + Binding( + get: { self.state.general.voiceSearchEnabled }, + set: { + self.model.voiceSearchEnabled = $0 + self.state.general.voiceSearchEnabled = $0 } ) } - init(model: SettingsModel, state: SettingsState = SettingsState(general: SettingsStateGeneral())) { + init(model: SettingsModel, + state: SettingsState = SettingsState(general: SettingsStateGeneral()), + legacyViewProvider: SettingsLegacyViewProvider) { self.model = model self.state = state + self.legacyViewProvider = legacyViewProvider initializeState() } @@ -115,13 +134,18 @@ final class SettingsViewModel: ObservableObject { state.general.autoconsentEnabled = model.autoconsentEnabled state.general.autoclearDataEnabled = model.autoclearDataEnabled state.general.applicationLock = model.applicationLock + state.general.voiceSearchEnabled = model.voiceSearchEnabled + state.general.longPressPreviews = model.longPressPreviews + state.general.allowUniversalLinks = model.allowUniversalLinks } - - } extension SettingsViewModel { + private func firePixel(_ event: Pixel.Event) { + Pixel.fire(pixel: event) + } + func setAsDefaultBrowser() { firePixel(.defaultBrowserButtonPressedSettings) guard let url = URL(string: UIApplication.openSettingsURLString) else { return } @@ -133,18 +157,6 @@ extension SettingsViewModel { isPresentingLoginsView = true } - func autofillViewPresentationAction() { - firePixel(.autofillSettingsOpened) - } - - func gpcViewPresentationAction() { - firePixel(.settingsDoNotSellShown) - } - - func autoConsentPresentationAction() { - firePixel(.settingsAutoconsentShown) - } - func openCookiePopupManagement() { // showCookiePopupManagement(animated: true) } @@ -156,26 +168,74 @@ extension SettingsViewModel { // we fall back to UIKit navigation extension SettingsViewModel { - func presentTextSettingsView(_ view: UIViewController) { - firePixel(.textSizeSettingsShown) - pushLegacyView(view) + enum LegacyView { + case addToDock, + sync, + logins, + textSize, + appIcon, + gpc, + autoconsent, + unprotectedSites, + fireproofSites, + autoclearData, + keyboard + } + + @MainActor + func presentView(_ view: LegacyView) { + switch view { + + case .addToDock: + presentLegacyView(legacyViewProvider.addToDock, modal: true) + + case .sync: + pushLegacyView(legacyViewProvider.syncSettings) + + case .logins: + firePixel(.autofillSettingsOpened) + pushLegacyView(legacyViewProvider.loginSettings(delegate: self, + selectedAccount: state.general.activeWebsiteAccount)) + + case .textSize: + firePixel(.textSizeSettingsShown) + pushLegacyView(legacyViewProvider.textSettings) + + case .appIcon: + pushLegacyView(legacyViewProvider.appIcon) + + case .gpc: + firePixel(.settingsDoNotSellShown) + pushLegacyView(legacyViewProvider.gpc) + + case .autoconsent: + firePixel(.settingsAutoconsentShown) + pushLegacyView(legacyViewProvider.autoConsent) + + case .unprotectedSites: + pushLegacyView(legacyViewProvider.unprotectedSites) + + case .fireproofSites: + pushLegacyView(legacyViewProvider.fireproofSites) + + case .autoclearData: + pushLegacyView(legacyViewProvider.autoclearData) + + case .keyboard: + pushLegacyView(legacyViewProvider.keyboard) + } } - func pushLegacyView(_ view: UIViewController) { + private func pushLegacyView(_ view: UIViewController) { onRequestPushLegacyView?(view) } - func presentLegacyView(_ view: UIViewController, modal: Bool) { + private func presentLegacyView(_ view: UIViewController, modal: Bool) { onRequestPresentLegacyView?(view, modal) } - private func firePixel(_ event: Pixel.Event) { - Pixel.fire(pixel: event) - } - } - extension SettingsViewModel { static var fontSizeForHeaderView: CGFloat { let contentSize = UIApplication.shared.preferredContentSizeCategory diff --git a/DuckDuckGo/SettingsViewProvider.swift b/DuckDuckGo/SettingsViewProvider.swift deleted file mode 100644 index 017e626973..0000000000 --- a/DuckDuckGo/SettingsViewProvider.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// LegacyViewProvider.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 DDGSync -import Core -import BrowserServicesKit - -struct LazyView: View { - let build: () -> Content - init(_ build: @autoclosure @escaping () -> Content) { - self.build = build - } - var body: some View { - build() - } -} - -class SettingsViewProvider: ObservableObject { - - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - let appSettings: AppSettings - - init(syncService: DDGSyncing, syncDataProviders: SyncDataProviders, appSettings: AppSettings) { - self.syncService = syncService - self.syncDataProviders = syncDataProviders - self.appSettings = appSettings - } - - var addToDock: UIViewController { - let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) - return storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController - } - - var addWidget: some View { - return LazyView(WidgetEducationView()) - } - - var textSettings: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController - } - - var syncSettings: some View { - return LazyView(SyncSettingsViewControllerRepresentable(syncService: self.syncService, - syncDataProviders: self.syncDataProviders)) - } - - func loginSettings(delegate: AutofillLoginSettingsListViewControllerDelegate, - selectedAccount: SecureVaultModels.WebsiteAccount?) -> some View { - LazyView(AutofillLoginSettingsListViewControllerRepresentable(appSettings: self.appSettings, - syncService: self.syncService, - syncDataProviders: self.syncDataProviders, - delegate: delegate, - selectedAccount: selectedAccount)) - } - - var appIcon: some View { - LazyView(AppIconSettingsViewControllerRepresentable()) - } - - var doNotSell: some View { - LazyView(DoNotSellSettingsViewControllerRepresentable()) - } - - var autoConsent: some View { - LazyView(AutoconsentSettingsViewControllerRepresentable()) - } - - var unprotectedSites: some View { - LazyView(UnprotectedSitesViewControllerRepresentable()) - } - - var fireproofSites: some View { - LazyView(PreserveLoginsSettingsViewControllerRepresentable()) - } - - var autoclearData: some View { - LazyView(AutoClearSettingsViewControllerRepresentable()) - } - - -} diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index 47a5547556..f523ca0990 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -23,33 +23,6 @@ import Combine import SyncUI import DDGSync -// TODO: Sync is already SwiftUI and we should not need a representale -struct SyncSettingsViewControllerRepresentable: UIViewControllerRepresentable { - - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - - typealias UIViewControllerType = SyncSettingsViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Context) -> SyncSettingsViewController { - let viewController = SyncSettingsViewController(syncService: syncService, - syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems - }) - return viewController - } - - func updateUIViewController(_ uiViewController: SyncSettingsViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} - @MainActor class SyncSettingsViewController: UIHostingController { diff --git a/DuckDuckGo/UnprotectedSitesViewController.swift b/DuckDuckGo/UnprotectedSitesViewController.swift index 08645ed7c3..4fc53ac499 100644 --- a/DuckDuckGo/UnprotectedSitesViewController.swift +++ b/DuckDuckGo/UnprotectedSitesViewController.swift @@ -20,30 +20,6 @@ import UIKit import Core import BrowserServicesKit -import SwiftUI - -// MARK: UnprotectedSitesViewController Representable -struct UnprotectedSitesViewControllerRepresentable: UIViewControllerRepresentable { - - typealias UIViewControllerType = UnprotectedSitesViewController - - class Coordinator { - var parentObserver: NSKeyValueObservation? - } - - func makeUIViewController(context: Self.Context) -> UnprotectedSitesViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "UnprotectedSites") as! UnprotectedSitesViewController - context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in - vc.parent?.title = vc.title - }) - return viewController - } - - func updateUIViewController(_ uiViewController: UnprotectedSitesViewController, context: Context) {} - - func makeCoordinator() -> Self.Coordinator { Coordinator() } -} class UnprotectedSitesViewController: UITableViewController { From 67436beeccff23dd55fb4c1f2f480540cb7202af Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 03:17:26 +0100 Subject: [PATCH 08/99] State Management Updates --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +++----- DuckDuckGo/NavigationLink+Empty.swift | 29 ----------------- DuckDuckGo/SettingsCustomizeView.swift | 34 +++++++++++++++++--- DuckDuckGo/SettingsModel.swift | 41 +++++++++++++++++++------ DuckDuckGo/SettingsView.swift | 2 +- DuckDuckGo/SettingsViewController.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 21 +++++++++++-- 7 files changed, 84 insertions(+), 57 deletions(-) delete mode 100644 DuckDuckGo/NavigationLink+Empty.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 939bfb45ad..01f1e1fbeb 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -775,11 +775,10 @@ D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */; }; D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */; }; D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; - D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */; }; - D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */; }; D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */; }; + D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2410,11 +2409,10 @@ D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppeareanceView.swift; sourceTree = ""; }; D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHostingController.swift; sourceTree = ""; }; D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; - D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Empty.swift"; sourceTree = ""; }; - D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCustomizeView.swift; sourceTree = ""; }; + D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4508,7 +4506,6 @@ D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */, D6E83C302B1EA309006C8AFB /* SettingsCell.swift */, D6E83C322B1F1279006C8AFB /* Sections */, - D6E83C4C2B20DD51006C8AFB /* NavigationLink+Empty.swift */, ); name = Views; sourceTree = ""; @@ -4516,7 +4513,7 @@ D6E83C492B20C883006C8AFB /* Model */ = { isa = PBXGroup; children = ( - D6E83C4F2B2147B0006C8AFB /* SettingsState.swift */, + D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */, D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */, D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, @@ -6345,10 +6342,8 @@ 3151F0F02735802800226F58 /* VoiceSearchViewController.swift in Sources */, 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */, F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, - D6E83C502B2147B0006C8AFB /* SettingsState.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, B6BA95C328891E33004ABA20 /* BrowsingMenuAnimator.swift in Sources */, - D6E83C4D2B20DD51006C8AFB /* NavigationLink+Empty.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, @@ -6497,6 +6492,7 @@ 85374D3C21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift in Sources */, 85DFEDF124C7EEA400973FE7 /* LargeOmniBarState.swift in Sources */, 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */, + D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */, D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, diff --git a/DuckDuckGo/NavigationLink+Empty.swift b/DuckDuckGo/NavigationLink+Empty.swift deleted file mode 100644 index 2d089f7a40..0000000000 --- a/DuckDuckGo/NavigationLink+Empty.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// NavigationLink+Empty.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 - -extension NavigationLink where Label == EmptyView, Destination == EmptyView { - - /// Useful in cases where a `NavigationLink` is needed but there should not be - /// a destination. e.g. for programmatic navigation. - static var empty: NavigationLink { - self.init(destination: EmptyView(), label: { EmptyView() }) - } -} diff --git a/DuckDuckGo/SettingsCustomizeView.swift b/DuckDuckGo/SettingsCustomizeView.swift index cb29b88c5a..5532f780ea 100644 --- a/DuckDuckGo/SettingsCustomizeView.swift +++ b/DuckDuckGo/SettingsCustomizeView.swift @@ -22,7 +22,11 @@ import UIKit struct SettingsCustomizeView: View { @EnvironmentObject var viewModel: SettingsViewModel - @EnvironmentObject var viewProvider: SettingsLegacyViewProvider + @State var shouldShowNoMicrophonePermissionAlert = false { + didSet { + print("Set alert!!! \(shouldShowNoMicrophonePermissionAlert)") + } + } var body: some View { Section(header: Text("Customize"), @@ -33,13 +37,33 @@ struct SettingsCustomizeView: View { asLink: true, disclosureIndicator: true) - SettingsCellView(label: "Autocomplete Suggestions", accesory: .toggle(isOn: viewModel.autocompleteBinding)) + SettingsCellView(label: "Autocomplete Suggestions", + accesory: .toggle(isOn: viewModel.autocompleteBinding)) + if viewModel.shouldShowSpeechRecognitionCell { - SettingsCellView(label: "Private Voice Search", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + SettingsCellView(label: "Private Voice Search", + accesory: .toggle(isOn: viewModel.voiceSearchEnabledBinding)) } SettingsCellView(label: "Long-Press Previews", accesory: .toggle(isOn: viewModel.applicationLockBinding)) - SettingsCellView(label: "Open Links in Associated Apps", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + SettingsCellView(label: "Open Links in Associated Apps", accesory: .toggle(isOn: viewModel.applicationLockBinding)) } - } + + .alert(isPresented: $shouldShowNoMicrophonePermissionAlert) { + Alert(title: Text(UserText.noVoicePermissionAlertTitle), + message: Text(UserText.noVoicePermissionAlertMessage), + dismissButton: .default(Text("OK"), + action: { + viewModel.shouldShowNoMicrophonePermissionAlert = false + }) + ) + } + .onChange(of: viewModel.shouldShowNoMicrophonePermissionAlert) { value in + shouldShowNoMicrophonePermissionAlert = value + } + + + } + + } diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift index 5c02246a92..abd34adc96 100644 --- a/DuckDuckGo/SettingsModel.swift +++ b/DuckDuckGo/SettingsModel.swift @@ -33,7 +33,7 @@ import NetworkProtection import Core #endif -class SettingsModel { +class SettingsModel: ObservableObject { private let appIconManager = AppIconManager.shared private let bookmarksDatabase: CoreDataDatabase @@ -43,8 +43,15 @@ class SettingsModel { private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad private var privacyStore = PrivacyUserDefaults() + private var cancellables = Set() var appIcon: AppIcon = AppIcon.defaultAppIcon + var textSize: Int { appSettings.textSize } + var sendDoNotSell: Bool { appSettings.sendDoNotSell } + var autoconsentEnabled: Bool { appSettings.autoconsentEnabled } + var longPressPreviews: Bool { appSettings.longPressPreviews } + var allowUniversalLinks: Bool { appSettings.allowUniversalLinks } + var fireButtonAnimation: FireButtonAnimationType { get { appSettings.currentFireButtonAnimation } set { @@ -60,6 +67,7 @@ class SettingsModel { } } } + var appTheme: ThemeName { get { appSettings.currentThemeName } set { @@ -68,13 +76,12 @@ class SettingsModel { ThemeManager.shared.updateUserInterfaceStyle() } } - var textSize: Int { appSettings.textSize } + var addressBarPosition: AddressBarPosition { get { appSettings.currentAddressBarPosition } set { appSettings.currentAddressBarPosition = newValue } } - var sendDoNotSell: Bool { appSettings.sendDoNotSell } - var autoconsentEnabled: Bool { appSettings.autoconsentEnabled } + var autoclearDataEnabled: Bool { if AutoClearSettingsModel(settings: appSettings) != nil { return true @@ -86,22 +93,23 @@ class SettingsModel { get { privacyStore.authenticationEnabled } set { privacyStore.authenticationEnabled = newValue } } + var autocomplete: Bool { get { appSettings.autocomplete } set { appSettings.autocomplete = newValue } } + var voiceSearchEnabled: Bool { - get { appSettings.voiceSearchEnabled } - set { appSettings.autocomplete = newValue } + get { + appSettings.voiceSearchEnabled && AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } + set { + appSettings.voiceSearchEnabled = newValue + } } - var longPressPreviews: Bool { appSettings.longPressPreviews } - var allowUniversalLinks: Bool { appSettings.allowUniversalLinks } - #if NETWORK_PROTECTION private let connectionObserver = ConnectionStatusObserverThroughSession() #endif - private var cancellables: Set = [] init(bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider) { @@ -159,4 +167,17 @@ class SettingsModel { } } + func enableVoiceSearch(completion: @escaping (Bool) -> Void) { + let isFirstTimeAskingForPermission = SpeechRecognizer.recordPermission == .undetermined + + SpeechRecognizer.requestMicAccess { permission in + if !permission { + completion(false) + return + } + AppDependencyProvider.shared.voiceSearchHelper.enableVoiceSearch(true) + completion(true) + } + } + } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index ba2a65ae37..4c2ebca934 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -39,7 +39,7 @@ struct SettingsView: View { .environmentObject(viewModel) .onAppear { - viewModel.initializeState() + // viewModel.initalizeState() } } diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 6c343f4d11..a9f9f61e8d 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -154,7 +154,7 @@ class SettingsViewController: UITableViewController { configureDebugCell() configureVoiceSearchCell() configureNetPCell() - applyTheme(ThemeManager.shared.currentTheme) + (ThemeManager.shared.currentTheme) internalUserDecider.isInternalUserPublisher.dropFirst().sink(receiveValue: { [weak self] _ in self?.configureAutofillCell() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index a7dbd29707..bc3ce1f4b5 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -55,6 +55,7 @@ final class SettingsViewModel: ObservableObject { var shouldShowAddressBarPositionCell: Bool { model.isFeatureAvailable(.addressbarPosition) } var shouldShowNetworkProtectionCell: Bool { model.isFeatureAvailable(.networkProtection) } var shouldShowSpeechRecognitionCell: Bool { model.isFeatureAvailable(.speechRecognition) } + var shouldShowNoMicrophonePermissionAlert: Bool = false // Bindings var themeBinding: Binding { @@ -107,9 +108,22 @@ final class SettingsViewModel: ObservableObject { var voiceSearchEnabledBinding: Binding { Binding( get: { self.state.general.voiceSearchEnabled }, - set: { - self.model.voiceSearchEnabled = $0 - self.state.general.voiceSearchEnabled = $0 + set: { value in + if value { + self.model.enableVoiceSearch { [weak self] result in + DispatchQueue.main.async { + self?.state.general.voiceSearchEnabled = result + self?.model.voiceSearchEnabled = result + if !result { + // Permission is denied + self?.shouldShowNoMicrophonePermissionAlert = true + } + } + } + } else { + self.state.general.voiceSearchEnabled = false + self.model.voiceSearchEnabled = false + } } ) } @@ -138,6 +152,7 @@ final class SettingsViewModel: ObservableObject { state.general.longPressPreviews = model.longPressPreviews state.general.allowUniversalLinks = model.allowUniversalLinks } + } extension SettingsViewModel { From 2cc396e9dadc9a117725962c960234a75b702451 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 14:26:13 +0100 Subject: [PATCH 09/99] Email Protection and other links --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +- DuckDuckGo/MainViewController+Segues.swift | 11 +- DuckDuckGo/SettingsAppeareanceView.swift | 4 +- DuckDuckGo/SettingsCell.swift | 26 ++- DuckDuckGo/SettingsCustomizeView.swift | 16 +- DuckDuckGo/SettingsGeneralView.swift | 2 +- DuckDuckGo/SettingsLegacyViewProvider.swift | 8 + DuckDuckGo/SettingsLoginsView.swift | 2 +- DuckDuckGo/SettingsModel.swift | 183 ------------------- DuckDuckGo/SettingsMoreView.swift | 62 +++++++ DuckDuckGo/SettingsPrivacyView.swift | 10 +- DuckDuckGo/SettingsSyncView.swift | 2 +- DuckDuckGo/SettingsView.swift | 3 +- DuckDuckGo/SettingsViewModel.swift | 192 +++++++++++++++----- 14 files changed, 260 insertions(+), 269 deletions(-) delete mode 100644 DuckDuckGo/SettingsModel.swift create mode 100644 DuckDuckGo/SettingsMoreView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 01f1e1fbeb..6e00afbfd9 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -774,11 +774,11 @@ D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */; }; D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */; }; D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */; }; - D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */; }; D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */; }; D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; + D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2408,11 +2408,11 @@ D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLoginsView.swift; sourceTree = ""; }; D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppeareanceView.swift; sourceTree = ""; }; D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHostingController.swift; sourceTree = ""; }; - D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCustomizeView.swift; sourceTree = ""; }; D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; + D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsMoreView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4495,6 +4495,7 @@ D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, + D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */, ); name = Sections; sourceTree = ""; @@ -4514,7 +4515,6 @@ isa = PBXGroup; children = ( D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */, - D6E83C4A2B20C88E006C8AFB /* SettingsModel.swift */, D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, ); @@ -6599,7 +6599,6 @@ F1CA3C391F045885005FADB3 /* PrivacyUserDefaults.swift in Sources */, AA4D6A6A23DB87B1007E8790 /* AppIconManager.swift in Sources */, 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */, - D6E83C4B2B20C88E006C8AFB /* SettingsModel.swift in Sources */, 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, @@ -6699,6 +6698,7 @@ 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 9865DFF922A8220D00D27829 /* FavoritesOverlay.swift in Sources */, + D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 4add5846e7..8d4162ae84 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -204,7 +204,7 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.presentView(.sync) + $0.presentLegacyView(.sync) } } @@ -220,13 +220,8 @@ extension MainViewController { let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, syncDataProviders: syncDataProviders, appSettings: appSettings) - - let model = SettingsModel(bookmarksDatabase: self.bookmarksDatabase, - internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - - let settingsViewModel = SettingsViewModel(model: model, - legacyViewProvider: legacyViewProvider) - + + let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider) let settingsController = SettingsHostingController(viewModel: settingsViewModel, viewProvider: legacyViewProvider) settingsController.applyTheme(ThemeManager.shared.currentTheme) diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index fedfd55f15..5e36f64d56 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -32,7 +32,7 @@ struct SettingsAppeareanceView: View { let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) SettingsCellView(label: "App Icon", - action: { viewModel.presentView(.appIcon ) }, + action: { viewModel.presentLegacyView(.appIcon ) }, accesory: .image(image), asLink: true, disclosureIndicator: true) @@ -45,7 +45,7 @@ struct SettingsAppeareanceView: View { // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", - action: { viewModel.presentView(.textSize) }, + action: { viewModel.presentLegacyView(.textSize) }, accesory: .rightDetail("\(viewModel.state.general.textSize)%"), asLink: true) } diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index adfd7345a3..a3ca31dd37 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -27,11 +27,11 @@ struct SettingsCellView: View, Identifiable { case rightDetail(String) case toggle(isOn: Binding) case image(Image) - case subtitle(String) case custom(AnyView) } var label: String + var subtitle: String? var action: () -> Void = {} var enabled: Bool = true var accesory: Accesory @@ -44,13 +44,15 @@ struct SettingsCellView: View, Identifiable { /// Use this initializer for standard cell types that require a label. /// - Parameters: /// - label: The text to display in the cell. + /// - subtitle: Displayed below title (if present) /// - action: The closure to execute when the view is tapped. (If not embedded in a NavigationLink) /// - accesory: The type of cell to display. Excludes the custom cell type. /// - enabled: A Boolean value that determines whether the cell is enabled. /// - asLink: Wraps the view inside a Button. Used for views not wrapped in a NavigationLink /// - disclosureIndicator: Forces Adds a disclosure indicator on the right (chevron) - init(label: String, action: @escaping () -> Void = {}, accesory: Accesory = .none, enabled: Bool = true, asLink: Bool = false, disclosureIndicator: Bool = false) { + init(label: String, subtitle: String? = nil, action: @escaping () -> Void = {}, accesory: Accesory = .none, enabled: Bool = true, asLink: Bool = false, disclosureIndicator: Bool = false) { self.label = label + self.subtitle = subtitle self.action = action self.enabled = enabled self.accesory = accesory @@ -97,20 +99,32 @@ struct SettingsCellView: View, Identifiable { private var defaultView: some View { Group { HStack { - Text(label) + + VStack(alignment: .leading) { + Text(label) + if let subtitleText = subtitle { + Text(subtitleText).font(.subheadline) + .foregroundColor(Color(UIColor.secondaryLabel)) + } + }.fixedSize(horizontal: false, vertical: true) + .layoutPriority(1) + Spacer() - cellView() + + accesoryView() + if disclosureIndicator { Image(systemName: "chevron.forward") .font(Font.system(.footnote).weight(.bold)) .foregroundColor(Color(UIColor.tertiaryLabel)) + .padding(.leading, 8) } } }.contentShape(Rectangle()) } @ViewBuilder - private func cellView() -> some View { + private func accesoryView() -> some View { switch accesory { case .none: EmptyView() @@ -123,8 +137,6 @@ struct SettingsCellView: View, Identifiable { .resizable() .scaledToFit() .frame(width: 25, height: 25) - case .subtitle(let subtitle): - Text(subtitle).font(.subheadline) case .custom(let customView): customView } diff --git a/DuckDuckGo/SettingsCustomizeView.swift b/DuckDuckGo/SettingsCustomizeView.swift index 5532f780ea..13004f3675 100644 --- a/DuckDuckGo/SettingsCustomizeView.swift +++ b/DuckDuckGo/SettingsCustomizeView.swift @@ -1,4 +1,4 @@ -// SettingsPrivacyView.swift +// SettingsCustomizeView.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -22,18 +22,14 @@ import UIKit struct SettingsCustomizeView: View { @EnvironmentObject var viewModel: SettingsViewModel - @State var shouldShowNoMicrophonePermissionAlert = false { - didSet { - print("Set alert!!! \(shouldShowNoMicrophonePermissionAlert)") - } - } + @State var shouldShowNoMicrophonePermissionAlert = false var body: some View { Section(header: Text("Customize"), footer: Text("Disable to prevent links from automatically opening in other installed apps")) { SettingsCellView(label: "Keyboard", - action: { viewModel.presentView(.keyboard) }, + action: { viewModel.presentLegacyView(.keyboard) }, asLink: true, disclosureIndicator: true) @@ -44,9 +40,11 @@ struct SettingsCustomizeView: View { SettingsCellView(label: "Private Voice Search", accesory: .toggle(isOn: viewModel.voiceSearchEnabledBinding)) } - SettingsCellView(label: "Long-Press Previews", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + SettingsCellView(label: "Long-Press Previews", + accesory: .toggle(isOn: viewModel.longPressBinding)) - SettingsCellView(label: "Open Links in Associated Apps", accesory: .toggle(isOn: viewModel.applicationLockBinding)) + SettingsCellView(label: "Open Links in Associated Apps", + accesory: .toggle(isOn: viewModel.universalLinksBinding)) } .alert(isPresented: $shouldShowNoMicrophonePermissionAlert) { diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index 3f32e9de40..c88ab7c6dc 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -33,7 +33,7 @@ struct SettingsGeneralView: View { // This old VC has a special behavior does not work as expected when presented in the SwiftUI Stack // so we need to push it via the UIKit Containe SettingsCellView(label: "Add App to Your Dock", - action: { viewModel.presentView(.addToDock) }, + action: { viewModel.presentLegacyView(.addToDock) }, asLink: true) NavigationLink(destination: WidgetEducationView()) { diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 39bb345a79..4e84d94d85 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -106,4 +106,12 @@ class SettingsLegacyViewProvider: ObservableObject { let storyboard = UIStoryboard(name: "Settings", bundle: nil) return storyboard.instantiateViewController(identifier: "Keyboard") as! KeyboardSettingsViewController } + + var mac: UIViewController { + MacWaitlistViewController(nibName: nil, bundle: nil) + } + + var windows: UIViewController { + MacWaitlistViewController(nibName: nil, bundle: nil) + } } diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index fbbca587c4..85b221bc39 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -30,7 +30,7 @@ struct SettingsLoginsView: View { if viewModel.shouldShowLoginsCell { Section { SettingsCellView(label: "Logins", - action: { viewModel.presentView(.logins) }, + action: { viewModel.presentLegacyView(.logins) }, asLink: true, disclosureIndicator: true) } diff --git a/DuckDuckGo/SettingsModel.swift b/DuckDuckGo/SettingsModel.swift deleted file mode 100644 index abd34adc96..0000000000 --- a/DuckDuckGo/SettingsModel.swift +++ /dev/null @@ -1,183 +0,0 @@ -// -// SettingsState.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 -import Persistence -import DDGSync -import Combine -import UIKit - -#if APP_TRACKING_PROTECTION -import NetworkExtension -#endif - -#if NETWORK_PROTECTION -import NetworkProtection -import Core -#endif - -class SettingsModel: ObservableObject { - - private let appIconManager = AppIconManager.shared - private let bookmarksDatabase: CoreDataDatabase - private let internalUserDecider: InternalUserDecider - private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger - private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) - private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings - private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad - private var privacyStore = PrivacyUserDefaults() - private var cancellables = Set() - - var appIcon: AppIcon = AppIcon.defaultAppIcon - var textSize: Int { appSettings.textSize } - var sendDoNotSell: Bool { appSettings.sendDoNotSell } - var autoconsentEnabled: Bool { appSettings.autoconsentEnabled } - var longPressPreviews: Bool { appSettings.longPressPreviews } - var allowUniversalLinks: Bool { appSettings.allowUniversalLinks } - - var fireButtonAnimation: FireButtonAnimationType { - get { appSettings.currentFireButtonAnimation } - set { - appSettings.currentFireButtonAnimation = newValue - NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) - - animator.animate { - // no op - } onTransitionCompleted: { - // no op - } completion: { - // no op - } - } - } - - var appTheme: ThemeName { - get { appSettings.currentThemeName } - set { - appSettings.currentThemeName = newValue - ThemeManager.shared.enableTheme(with: newValue) - ThemeManager.shared.updateUserInterfaceStyle() - } - } - - var addressBarPosition: AddressBarPosition { - get { appSettings.currentAddressBarPosition } - set { appSettings.currentAddressBarPosition = newValue } - } - - var autoclearDataEnabled: Bool { - if AutoClearSettingsModel(settings: appSettings) != nil { - return true - } else { - return false - } - } - var applicationLock: Bool { - get { privacyStore.authenticationEnabled } - set { privacyStore.authenticationEnabled = newValue } - } - - var autocomplete: Bool { - get { appSettings.autocomplete } - set { appSettings.autocomplete = newValue } - } - - var voiceSearchEnabled: Bool { - get { - appSettings.voiceSearchEnabled && AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } - set { - appSettings.voiceSearchEnabled = newValue - } - } - -#if NETWORK_PROTECTION - private let connectionObserver = ConnectionStatusObserverThroughSession() -#endif - - init(bookmarksDatabase: CoreDataDatabase, - internalUserDecider: InternalUserDecider) { - self.bookmarksDatabase = bookmarksDatabase - self.internalUserDecider = internalUserDecider - setupSubscribers() - } - - enum Features { - case sync - case autofillAccessCredentialManagement - case textSize - case voiceSearch - case addressbarPosition - case speechRecognition - -#if NETWORK_PROTECTION - case networkProtection -#endif - } - - func setupSubscribers() { - - appIconManager.$appIcon - .sink { [weak self] newIcon in - self?.appIcon = newIcon - } - .store(in: &cancellables) - } - - func isFeatureAvailable(_ feature: Features) -> Bool { - switch feature { - case .sync: - return featureFlagger.isFeatureOn(.sync) - case .autofillAccessCredentialManagement: - return featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) - case .textSize: - return !isPad - - #if NETWORK_PROTECTION - case .networkProtection: - if #available(iOS 15, *) { - return featureFlagger.isFeatureOn(.networkProtection) - } else { - return false - } - #endif - - case .voiceSearch: - return AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable - case .addressbarPosition: - return !isPad - case .speechRecognition: - return AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable - } - } - - func enableVoiceSearch(completion: @escaping (Bool) -> Void) { - let isFirstTimeAskingForPermission = SpeechRecognizer.recordPermission == .undetermined - - SpeechRecognizer.requestMicAccess { permission in - if !permission { - completion(false) - return - } - AppDependencyProvider.shared.voiceSearchHelper.enableVoiceSearch(true) - completion(true) - } - } - -} diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift new file mode 100644 index 0000000000..0ef3fca926 --- /dev/null +++ b/DuckDuckGo/SettingsMoreView.swift @@ -0,0 +1,62 @@ +// SettingsMoreView.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 UIKit + +struct SettingsMoreView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + + + var body: some View { + Section(header: Text("More from DuckDuckGo")) { + + SettingsCellView(label: "Email Protection", + subtitle: "Block email trackers and hide your address", + action: { viewModel.openEmailProtection() }, + asLink: true, + disclosureIndicator: true) + + SettingsCellView(label: "DuckDuckGo Mac App", + subtitle: UserText.macWaitlistBrowsePrivately, + action: { viewModel.presentLegacyView(.macApp) }, + asLink: true, + disclosureIndicator: true) + + SettingsCellView(label: "DuckDuckGo Windows App", + subtitle: UserText.windowsWaitlistBrowsePrivately, + action: { viewModel.presentLegacyView(.windowsApp) }, + asLink: true, + disclosureIndicator: true) + +#if NETWORK_PROTECTION + if viewModel.shouldShowNetworkProtectionCell { + SettingsCellView(label: "Network Protection", + subtitle: "Join the private waitlist", + action: { viewModel.presentLegacyView(.keyboard) }, + asLink: true, + disclosureIndicator: true) + } +#endif + } + + } + + +} diff --git a/DuckDuckGo/SettingsPrivacyView.swift b/DuckDuckGo/SettingsPrivacyView.swift index 238d4e87bb..1c346cde50 100644 --- a/DuckDuckGo/SettingsPrivacyView.swift +++ b/DuckDuckGo/SettingsPrivacyView.swift @@ -28,7 +28,7 @@ struct SettingsPrivacyView: View { footer: Text("If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening.")) { SettingsCellView(label: "Global Privacy Control (GPC)", - action: { viewModel.presentView(.gpc) }, + action: { viewModel.presentLegacyView(.gpc) }, accesory: .rightDetail(viewModel.state.general.sendDoNotSell ? UserText.doNotSellEnabled : UserText.doNotSellDisabled), @@ -36,7 +36,7 @@ struct SettingsPrivacyView: View { disclosureIndicator: true) SettingsCellView(label: "Manage Cookie Popups", - action: { viewModel.presentView(.autoconsent) }, + action: { viewModel.presentLegacyView(.autoconsent) }, accesory: .rightDetail(viewModel.state.general.autoconsentEnabled ? UserText.autoconsentEnabled : UserText.autoconsentDisabled), @@ -44,17 +44,17 @@ struct SettingsPrivacyView: View { disclosureIndicator: true) SettingsCellView(label: "Unprotected SItes", - action: { viewModel.presentView(.unprotectedSites) }, + action: { viewModel.presentLegacyView(.unprotectedSites) }, asLink: true, disclosureIndicator: true) SettingsCellView(label: "Fireproof Sites", - action: { viewModel.presentView(.fireproofSites) }, + action: { viewModel.presentLegacyView(.fireproofSites) }, asLink: true, disclosureIndicator: true) SettingsCellView(label: "Automatically Clear Data", - action: { viewModel.presentView(.autoclearData) }, + action: { viewModel.presentLegacyView(.autoclearData) }, accesory: .rightDetail(viewModel.state.general.autoclearDataEnabled ? UserText.autoClearAccessoryOn : UserText.autoClearAccessoryOff), diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index 51c402525b..7f5da8110e 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -34,7 +34,7 @@ struct SettingsSyncView: View { if viewModel.shouldShowSyncCell { Section { SettingsCellView(label: UserText.syncTitle, - action: { viewModel.presentView(.sync) }, + action: { viewModel.presentLegacyView(.sync) }, asLink: true, disclosureIndicator: true) } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 4c2ebca934..2535e5b6a8 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -32,6 +32,7 @@ struct SettingsView: View { SettingsAppeareanceView() SettingsPrivacyView() SettingsCustomizeView() + SettingsMoreView() } .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { @@ -39,7 +40,7 @@ struct SettingsView: View { .environmentObject(viewModel) .onAppear { - // viewModel.initalizeState() + viewModel.updateState() } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index bc3ce1f4b5..cbcc86601e 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -34,36 +34,67 @@ import NetworkProtection final class SettingsViewModel: ObservableObject { - var legacyViewProvider: SettingsLegacyViewProvider + // Dependencies + private(set) lazy var appSettings = AppDependencyProvider.shared.appSettings + private(set) var privacyStore = PrivacyUserDefaults() + private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger + private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) + private var legacyViewProvider: SettingsLegacyViewProvider + + // Properties + private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad + private var cancellables = Set() // Closure to request a legacy view controller presentation var onRequestPushLegacyView: ((UIViewController) -> Void)? var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? - private(set) var model: SettingsModel + // Our View State @Published private(set) var state: SettingsState - - // Support Programatic Navigation - var isPresentingLoginsView = false - + // Cell Visibility - var shouldShowSyncCell: Bool { model.isFeatureAvailable(.sync) } - var shouldShowLoginsCell: Bool { model.isFeatureAvailable(.autofillAccessCredentialManagement) } - var shouldShowTextSizeCell: Bool { model.isFeatureAvailable(.textSize) } - var shouldShowDebugCell: Bool { model.isFeatureAvailable(.networkProtection) } - var shouldShowVoiceSearchCell: Bool { model.isFeatureAvailable(.voiceSearch) } - var shouldShowAddressBarPositionCell: Bool { model.isFeatureAvailable(.addressbarPosition) } - var shouldShowNetworkProtectionCell: Bool { model.isFeatureAvailable(.networkProtection) } - var shouldShowSpeechRecognitionCell: Bool { model.isFeatureAvailable(.speechRecognition) } + enum Features { + case sync + case autofillAccessCredentialManagement + case textSize + case voiceSearch + case addressbarPosition + case speechRecognition +#if NETWORK_PROTECTION + case networkProtection +#endif + } + + var shouldShowSyncCell: Bool { featureFlagger.isFeatureOn(.sync) } + var shouldShowLoginsCell: Bool { featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) } + var shouldShowTextSizeCell: Bool { !isPad } + var shouldShowVoiceSearchCell: Bool { AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } + var shouldShowAddressBarPositionCell: Bool { !isPad } + var shouldShowSpeechRecognitionCell: Bool { AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } var shouldShowNoMicrophonePermissionAlert: Bool = false + // var shouldShowDebugCell: Bool { isFeatureAvailable(.networkProtection) } + + var shouldShowNetworkProtectionCell: Bool { +#if NETWORK_PROTECTION + if #available(iOS 15, *) { + return featureFlagger.isFeatureOn(.networkProtection) + } else { + return false + } +#else + return false +#endif + } // Bindings var themeBinding: Binding { Binding( get: { self.state.general.appTheme }, set: { - self.model.appTheme = $0 self.state.general.appTheme = $0 + self.appSettings.currentThemeName = $0 + ThemeManager.shared.enableTheme(with: $0) + ThemeManager.shared.updateUserInterfaceStyle() } ) } @@ -71,8 +102,16 @@ final class SettingsViewModel: ObservableObject { Binding( get: { self.state.general.fireButtonAnimation }, set: { - self.model.fireButtonAnimation = $0 + self.appSettings.currentFireButtonAnimation = $0 self.state.general.fireButtonAnimation = $0 + NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) + self.animator.animate { + // no op + } onTransitionCompleted: { + // no op + } completion: { + // no op + } } ) } @@ -82,8 +121,8 @@ final class SettingsViewModel: ObservableObject { self.state.general.addressBarPosition }, set: { + self.appSettings.currentAddressBarPosition = $0 self.state.general.addressBarPosition = $0 - self.model.addressBarPosition = $0 } ) } @@ -91,8 +130,8 @@ final class SettingsViewModel: ObservableObject { Binding( get: { self.state.general.applicationLock }, set: { + self.privacyStore.authenticationEnabled = $0 self.state.general.applicationLock = $0 - self.model.applicationLock = $0 } ) } @@ -100,8 +139,8 @@ final class SettingsViewModel: ObservableObject { Binding( get: { self.state.general.autocomplete }, set: { + self.appSettings.autocomplete = $0 self.state.general.autocomplete = $0 - self.model.autocomplete = $0 } ) } @@ -110,10 +149,10 @@ final class SettingsViewModel: ObservableObject { get: { self.state.general.voiceSearchEnabled }, set: { value in if value { - self.model.enableVoiceSearch { [weak self] result in + self.enableVoiceSearch { [weak self] result in DispatchQueue.main.async { self?.state.general.voiceSearchEnabled = result - self?.model.voiceSearchEnabled = result + self?.appSettings.voiceSearchEnabled = result if !result { // Permission is denied self?.shouldShowNoMicrophonePermissionAlert = true @@ -121,44 +160,90 @@ final class SettingsViewModel: ObservableObject { } } } else { + self.appSettings.voiceSearchEnabled = false self.state.general.voiceSearchEnabled = false - self.model.voiceSearchEnabled = false } } ) } + var longPressBinding: Binding { + Binding( + get: { self.state.general.longPressPreviews }, + set: { + self.appSettings.longPressPreviews = $0 + self.state.general.longPressPreviews = $0 + } + ) + } + + var universalLinksBinding: Binding { + Binding( + get: { self.state.general.allowUniversalLinks }, + set: { + self.appSettings.allowUniversalLinks = $0 + self.state.general.allowUniversalLinks = $0 + } + ) + } - init(model: SettingsModel, - state: SettingsState = SettingsState(general: SettingsStateGeneral()), + init(state: SettingsState = SettingsState(general: SettingsStateGeneral()), legacyViewProvider: SettingsLegacyViewProvider) { - self.model = model self.state = state self.legacyViewProvider = legacyViewProvider - initializeState() + setupSubscribers() + } - func initializeState() { - // Model should eventually be Observable, but that Requires appSettings to be update - state.general.appIcon = model.appIcon - state.general.fireButtonAnimation = model.fireButtonAnimation - state.general.appTheme = model.appTheme - state.general.textSize = model.textSize - state.general.addressBarPosition = model.addressBarPosition - state.general.sendDoNotSell = model.sendDoNotSell - state.general.autoconsentEnabled = model.autoconsentEnabled - state.general.autoclearDataEnabled = model.autoclearDataEnabled - state.general.applicationLock = model.applicationLock - state.general.voiceSearchEnabled = model.voiceSearchEnabled - state.general.longPressPreviews = model.longPressPreviews - state.general.allowUniversalLinks = model.allowUniversalLinks + private func createSettingsState() -> SettingsState { + // This manual initialzation will go away once appSettings and + // other dependencies are observable (Such as AppIcon) + return SettingsState( + general: SettingsStateGeneral( + appTheme: appSettings.currentThemeName, + fireButtonAnimation: appSettings.currentFireButtonAnimation, + textSize: appSettings.textSize, + addressBarPosition: appSettings.currentAddressBarPosition, + sendDoNotSell: appSettings.sendDoNotSell, + autoconsentEnabled: appSettings.autoconsentEnabled, + autoclearDataEnabled: AutoClearSettingsModel(settings: appSettings) != nil, + applicationLock: privacyStore.authenticationEnabled, + autocomplete: appSettings.autocomplete, + voiceSearchEnabled: appSettings.voiceSearchEnabled && AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, + longPressPreviews: appSettings.longPressPreviews, + allowUniversalLinks: appSettings.allowUniversalLinks + ) + ) + } + + private func setupSubscribers() { + AppIconManager.shared.$appIcon + .sink { [weak self] newIcon in + self?.state.general.appIcon = newIcon + } + .store(in: &cancellables) + } + private func firePixel(_ event: Pixel.Event) { + Pixel.fire(pixel: event) } + private func enableVoiceSearch(completion: @escaping (Bool) -> Void) { + SpeechRecognizer.requestMicAccess { permission in + if !permission { + completion(false) + return + } + AppDependencyProvider.shared.voiceSearchHelper.enableVoiceSearch(true) + completion(true) + } + } } extension SettingsViewModel { - private func firePixel(_ event: Pixel.Event) { - Pixel.fire(pixel: event) + func updateState() { + // Eventually the model should be Observable, but that requires all + // dependencies (appSettings, privacyStore etc, to be reactive) + state = createSettingsState() } func setAsDefaultBrowser() { @@ -169,9 +254,14 @@ extension SettingsViewModel { func shouldPresentLoginsViewWithAccount(accountDetails: SecureVaultModels.WebsiteAccount) { state.general.activeWebsiteAccount = accountDetails - isPresentingLoginsView = true } + func openEmailProtection() { + UIApplication.shared.open(URL.emailProtectionQuickLink, + options: [:], + completionHandler: nil) + } + func openCookiePopupManagement() { // showCookiePopupManagement(animated: true) } @@ -194,11 +284,13 @@ extension SettingsViewModel { unprotectedSites, fireproofSites, autoclearData, - keyboard + keyboard, + macApp, + windowsApp } @MainActor - func presentView(_ view: LegacyView) { + func presentLegacyView(_ view: LegacyView) { switch view { case .addToDock: @@ -211,7 +303,7 @@ extension SettingsViewModel { firePixel(.autofillSettingsOpened) pushLegacyView(legacyViewProvider.loginSettings(delegate: self, selectedAccount: state.general.activeWebsiteAccount)) - + case .textSize: firePixel(.textSizeSettingsShown) pushLegacyView(legacyViewProvider.textSettings) @@ -238,6 +330,12 @@ extension SettingsViewModel { case .keyboard: pushLegacyView(legacyViewProvider.keyboard) + + case .windowsApp: + pushLegacyView(legacyViewProvider.mac) + + case .macApp: + pushLegacyView(legacyViewProvider.mac) } } @@ -287,6 +385,6 @@ extension SettingsViewModel { extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { - isPresentingLoginsView = false + // isPresentingLoginsView = false } } From 2102043de47a418f8fcbc8e82842e833fa606a5d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 15:28:33 +0100 Subject: [PATCH 10/99] State initialization --- DuckDuckGo/SettingsAppeareanceView.swift | 4 +- DuckDuckGo/SettingsMoreView.swift | 2 +- DuckDuckGo/SettingsPrivacyView.swift | 6 +- DuckDuckGo/SettingsState.swift | 89 +++++++++++-- DuckDuckGo/SettingsViewModel.swift | 155 +++++++++++++++-------- 5 files changed, 181 insertions(+), 75 deletions(-) diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index 5e36f64d56..1e9cc759b2 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -30,7 +30,7 @@ struct SettingsAppeareanceView: View { options: ThemeName.allCases, selectedOption: viewModel.themeBinding) - let image = Image(uiImage: viewModel.state.general.appIcon.smallImage ?? UIImage()) + let image = Image(uiImage: viewModel.state.appeareance.appIcon.smallImage ?? UIImage()) SettingsCellView(label: "App Icon", action: { viewModel.presentLegacyView(.appIcon ) }, accesory: .image(image), @@ -46,7 +46,7 @@ struct SettingsAppeareanceView: View { if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", action: { viewModel.presentLegacyView(.textSize) }, - accesory: .rightDetail("\(viewModel.state.general.textSize)%"), + accesory: .rightDetail("\(viewModel.state.appeareance.textSize)%"), asLink: true) } diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index 0ef3fca926..4c356923e8 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -48,7 +48,7 @@ struct SettingsMoreView: View { #if NETWORK_PROTECTION if viewModel.shouldShowNetworkProtectionCell { SettingsCellView(label: "Network Protection", - subtitle: "Join the private waitlist", + subtitle: viewModel.state.netP.subtitle, action: { viewModel.presentLegacyView(.keyboard) }, asLink: true, disclosureIndicator: true) diff --git a/DuckDuckGo/SettingsPrivacyView.swift b/DuckDuckGo/SettingsPrivacyView.swift index 1c346cde50..f942d4ad06 100644 --- a/DuckDuckGo/SettingsPrivacyView.swift +++ b/DuckDuckGo/SettingsPrivacyView.swift @@ -29,7 +29,7 @@ struct SettingsPrivacyView: View { SettingsCellView(label: "Global Privacy Control (GPC)", action: { viewModel.presentLegacyView(.gpc) }, - accesory: .rightDetail(viewModel.state.general.sendDoNotSell + accesory: .rightDetail(viewModel.state.privacy.sendDoNotSell ? UserText.doNotSellEnabled : UserText.doNotSellDisabled), asLink: true, @@ -37,7 +37,7 @@ struct SettingsPrivacyView: View { SettingsCellView(label: "Manage Cookie Popups", action: { viewModel.presentLegacyView(.autoconsent) }, - accesory: .rightDetail(viewModel.state.general.autoconsentEnabled + accesory: .rightDetail(viewModel.state.privacy.autoconsentEnabled ? UserText.autoconsentEnabled : UserText.autoconsentDisabled), asLink: true, @@ -55,7 +55,7 @@ struct SettingsPrivacyView: View { SettingsCellView(label: "Automatically Clear Data", action: { viewModel.presentLegacyView(.autoclearData) }, - accesory: .rightDetail(viewModel.state.general.autoclearDataEnabled + accesory: .rightDetail(viewModel.state.privacy.autoclearDataEnabled ? UserText.autoClearAccessoryOn : UserText.autoClearAccessoryOff), asLink: true, diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 23428d6c92..e6e2582385 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -20,23 +20,84 @@ import BrowserServicesKit struct SettingsState { - var general: SettingsStateGeneral + var appeareance: SettingsStateAppeareance + var privacy: SettingsStatePrivacy + var customization: SettingsStateCustomization + var logins: SettingsStateLogins + var netP: SettingsStateNetP // Add state for other sections here... + + static var defaults: SettingsState { + return SettingsState( + appeareance: SettingsStateAppeareance.defaults, + privacy: SettingsStatePrivacy.defaults, + customization: SettingsStateCustomization.defaults, + logins: SettingsStateLogins.defaults, + netP: SettingsStateNetP.defaults) + } } -struct SettingsStateGeneral { - var appTheme: ThemeName = ThemeManager.shared.currentTheme.name +struct SettingsStateAppeareance { + var appTheme: ThemeName var appIcon: AppIcon = AppIconManager.shared.appIcon - var fireButtonAnimation: FireButtonAnimationType = .fireRising - var textSize: Int = 100 + var fireButtonAnimation: FireButtonAnimationType + var textSize: Int + var addressBarPosition: AddressBarPosition + + static var defaults: SettingsStateAppeareance { + return SettingsStateAppeareance( + appTheme: .systemDefault, + fireButtonAnimation: .fireRising, + textSize: 100, + addressBarPosition: .top + ) + } +} + +struct SettingsStatePrivacy { + var sendDoNotSell: Bool + var autoconsentEnabled: Bool + var autoclearDataEnabled: Bool + var applicationLock: Bool + + static var defaults: SettingsStatePrivacy { + return SettingsStatePrivacy( + sendDoNotSell: true, + autoconsentEnabled: false, + autoclearDataEnabled: false, + applicationLock: false + ) + } +} + +struct SettingsStateCustomization { + var autocomplete: Bool + var voiceSearchEnabled: Bool + var longPressPreviews: Bool + var allowUniversalLinks: Bool + + static var defaults: SettingsStateCustomization { + return SettingsStateCustomization( + autocomplete: true, + voiceSearchEnabled: false, + longPressPreviews: true, + allowUniversalLinks: true + ) + } +} + +struct SettingsStateLogins { var activeWebsiteAccount: SecureVaultModels.WebsiteAccount? - var addressBarPosition: AddressBarPosition = .top - var sendDoNotSell: Bool = true - var autoconsentEnabled: Bool = true - var autoclearDataEnabled: Bool = true - var applicationLock: Bool = true - var autocomplete: Bool = true - var voiceSearchEnabled = true - var longPressPreviews = true - var allowUniversalLinks = true + + static var defaults: SettingsStateLogins { + return SettingsStateLogins(activeWebsiteAccount: nil) // Provide a default value + } +} + +struct SettingsStateNetP { + var subtitle: String = "" + + static var defaults: SettingsStateNetP { + return SettingsStateNetP(subtitle: "") // Provide a default value + } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index cbcc86601e..545896d3e5 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -40,6 +40,9 @@ final class SettingsViewModel: ObservableObject { private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) private var legacyViewProvider: SettingsLegacyViewProvider +#if NETWORK_PROTECTION + private let connectionObserver = ConnectionStatusObserverThroughSession() +#endif // Properties private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad @@ -51,8 +54,8 @@ final class SettingsViewModel: ObservableObject { // Our View State @Published private(set) var state: SettingsState - - // Cell Visibility + + // MARK: Cell Visibility enum Features { case sync case autofillAccessCredentialManagement @@ -86,12 +89,12 @@ final class SettingsViewModel: ObservableObject { #endif } - // Bindings + // MARK: Bindings var themeBinding: Binding { Binding( - get: { self.state.general.appTheme }, + get: { self.state.appeareance.appTheme }, set: { - self.state.general.appTheme = $0 + self.state.appeareance.appTheme = $0 self.appSettings.currentThemeName = $0 ThemeManager.shared.enableTheme(with: $0) ThemeManager.shared.updateUserInterfaceStyle() @@ -100,10 +103,10 @@ final class SettingsViewModel: ObservableObject { } var fireButtonAnimationBinding: Binding { Binding( - get: { self.state.general.fireButtonAnimation }, + get: { self.state.appeareance.fireButtonAnimation }, set: { self.appSettings.currentFireButtonAnimation = $0 - self.state.general.fireButtonAnimation = $0 + self.state.appeareance.fireButtonAnimation = $0 NotificationCenter.default.post(name: AppUserDefaults.Notifications.currentFireButtonAnimationChange, object: self) self.animator.animate { // no op @@ -118,40 +121,40 @@ final class SettingsViewModel: ObservableObject { var addressBarPositionBinding: Binding { Binding( get: { - self.state.general.addressBarPosition + self.state.appeareance.addressBarPosition }, set: { self.appSettings.currentAddressBarPosition = $0 - self.state.general.addressBarPosition = $0 + self.state.appeareance.addressBarPosition = $0 } ) } var applicationLockBinding: Binding { Binding( - get: { self.state.general.applicationLock }, + get: { self.state.privacy.applicationLock }, set: { self.privacyStore.authenticationEnabled = $0 - self.state.general.applicationLock = $0 + self.state.privacy.applicationLock = $0 } ) } var autocompleteBinding: Binding { Binding( - get: { self.state.general.autocomplete }, + get: { self.state.customization.autocomplete }, set: { self.appSettings.autocomplete = $0 - self.state.general.autocomplete = $0 + self.state.customization.autocomplete = $0 } ) } var voiceSearchEnabledBinding: Binding { Binding( - get: { self.state.general.voiceSearchEnabled }, + get: { self.state.customization.voiceSearchEnabled }, set: { value in if value { self.enableVoiceSearch { [weak self] result in DispatchQueue.main.async { - self?.state.general.voiceSearchEnabled = result + self?.state.customization.voiceSearchEnabled = result self?.appSettings.voiceSearchEnabled = result if !result { // Permission is denied @@ -161,67 +164,69 @@ final class SettingsViewModel: ObservableObject { } } else { self.appSettings.voiceSearchEnabled = false - self.state.general.voiceSearchEnabled = false + self.state.customization.voiceSearchEnabled = false } } ) } var longPressBinding: Binding { Binding( - get: { self.state.general.longPressPreviews }, + get: { self.state.customization.longPressPreviews }, set: { self.appSettings.longPressPreviews = $0 - self.state.general.longPressPreviews = $0 + self.state.customization.longPressPreviews = $0 } ) } var universalLinksBinding: Binding { Binding( - get: { self.state.general.allowUniversalLinks }, + get: { self.state.customization.allowUniversalLinks }, set: { self.appSettings.allowUniversalLinks = $0 - self.state.general.allowUniversalLinks = $0 + self.state.customization.allowUniversalLinks = $0 } ) } - - init(state: SettingsState = SettingsState(general: SettingsStateGeneral()), - legacyViewProvider: SettingsLegacyViewProvider) { - self.state = state + + // MARK: Default Init + init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider) { + self.state = SettingsState.defaults self.legacyViewProvider = legacyViewProvider + self.state = state ?? createSettingsState() setupSubscribers() - } +} + +// MARK: Private methods +extension SettingsViewModel { private func createSettingsState() -> SettingsState { - // This manual initialzation will go away once appSettings and - // other dependencies are observable (Such as AppIcon) - return SettingsState( - general: SettingsStateGeneral( - appTheme: appSettings.currentThemeName, - fireButtonAnimation: appSettings.currentFireButtonAnimation, - textSize: appSettings.textSize, - addressBarPosition: appSettings.currentAddressBarPosition, - sendDoNotSell: appSettings.sendDoNotSell, - autoconsentEnabled: appSettings.autoconsentEnabled, - autoclearDataEnabled: AutoClearSettingsModel(settings: appSettings) != nil, - applicationLock: privacyStore.authenticationEnabled, - autocomplete: appSettings.autocomplete, - voiceSearchEnabled: appSettings.voiceSearchEnabled && AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, - longPressPreviews: appSettings.longPressPreviews, - allowUniversalLinks: appSettings.allowUniversalLinks - ) - ) - } - - private func setupSubscribers() { - AppIconManager.shared.$appIcon - .sink { [weak self] newIcon in - self?.state.general.appIcon = newIcon - } - .store(in: &cancellables) + // This manual (re)initialzation will go away once appSettings and + // other dependencies are observable (Such as AppIcon and netP) + // and we can use subscribers + let appereance = SettingsStateAppeareance(appTheme: appSettings.currentThemeName, + fireButtonAnimation: appSettings.currentFireButtonAnimation, + textSize: appSettings.textSize, + addressBarPosition: appSettings.currentAddressBarPosition) + let privacy = SettingsStatePrivacy(sendDoNotSell: appSettings.sendDoNotSell, + autoconsentEnabled: appSettings.autoconsentEnabled, + autoclearDataEnabled: AutoClearSettingsModel(settings: appSettings) != nil, + applicationLock: privacyStore.authenticationEnabled) + + let customization = SettingsStateCustomization(autocomplete: appSettings.autocomplete, + voiceSearchEnabled: appSettings.voiceSearchEnabled && AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, + longPressPreviews: appSettings.longPressPreviews, + allowUniversalLinks: appSettings.allowUniversalLinks) + + return SettingsState(appeareance: appereance, + privacy: privacy, + customization: customization, + logins: SettingsStateLogins(), + netP: SettingsStateNetP()) + } + private func firePixel(_ event: Pixel.Event) { Pixel.fire(pixel: event) } @@ -236,13 +241,51 @@ final class SettingsViewModel: ObservableObject { completion(true) } } + +#if NETWORK_PROTECTION + private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { + switch NetworkProtectionAccessController().networkProtectionAccessType() { + case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: + state.netP.subtitle = VPNWaitlist.shared.settingsSubtitle + case .waitlistInvited, .inviteCodeInvited: + switch connectionStatus { + case .connected: + state.netP.subtitle = UserText.netPCellConnected + default: + state.netP.subtitle = UserText.netPCellDisconnected + } + } + } +#endif +} + +// MARK: Subscribers +extension SettingsViewModel { + + private func setupSubscribers() { + + AppIconManager.shared.$appIcon + .sink { [weak self] newIcon in + self?.state.appeareance.appIcon = newIcon + } + .store(in: &cancellables) + +#if NETWORK_PROTECTION + connectionObserver.publisher + .receive(on: DispatchQueue.main) + .sink { [weak self] status in + self?.updateNetPCellSubtitle(connectionStatus: status) + } + .store(in: &cancellables) +#endif + + } } +// MARK: Public Methods extension SettingsViewModel { func updateState() { - // Eventually the model should be Observable, but that requires all - // dependencies (appSettings, privacyStore etc, to be reactive) state = createSettingsState() } @@ -253,7 +296,7 @@ extension SettingsViewModel { } func shouldPresentLoginsViewWithAccount(accountDetails: SecureVaultModels.WebsiteAccount) { - state.general.activeWebsiteAccount = accountDetails + state.logins.activeWebsiteAccount = accountDetails } func openEmailProtection() { @@ -302,7 +345,7 @@ extension SettingsViewModel { case .logins: firePixel(.autofillSettingsOpened) pushLegacyView(legacyViewProvider.loginSettings(delegate: self, - selectedAccount: state.general.activeWebsiteAccount)) + selectedAccount: state.logins.activeWebsiteAccount)) case .textSize: firePixel(.textSizeSettingsShown) @@ -349,6 +392,7 @@ extension SettingsViewModel { } +// MARK: Old stuff from SettingsViewController extension SettingsViewModel { static var fontSizeForHeaderView: CGFloat { let contentSize = UIApplication.shared.preferredContentSizeCategory @@ -383,6 +427,7 @@ extension SettingsViewModel { } } +// MARK: AutofillLoginSettingsListViewControllerDelegate extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { // isPresentingLoginsView = false From 81d05023c0ca746c47eadbdb5a156936ac26b8bf Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 17:03:28 +0100 Subject: [PATCH 11/99] Added Required Cells --- DuckDuckGo/SettingsCell.swift | 130 +++++++++++++++++++- DuckDuckGo/SettingsLegacyViewProvider.swift | 2 +- DuckDuckGo/SettingsState.swift | 3 +- DuckDuckGo/SettingsView.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 21 ++-- 5 files changed, 141 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index a3ca31dd37..28c3054c64 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -107,7 +107,7 @@ struct SettingsCellView: View, Identifiable { .foregroundColor(Color(UIColor.secondaryLabel)) } }.fixedSize(horizontal: false, vertical: true) - .layoutPriority(1) + .layoutPriority(0.7) Spacer() @@ -169,3 +169,131 @@ struct SettingsPickerCellView: View { + var content: Content + var action: () -> Void + var asLink: Bool + var disclosureIndicator: Bool + + /// Initializes a `SettingsCustomCell`. + /// - Parameters: + /// - content: A SwiftUI View to be displayed in the cell. + /// - action: The closure to execute when the view is tapped. + /// - asLink: A Boolean value that determines if the cell behaves like a link. + /// - disclosureIndicator: A Boolean value that determines if the cell shows a disclosure indicator. + init(@ViewBuilder content: () -> Content, action: @escaping () -> Void = {}, asLink: Bool = false, disclosureIndicator: Bool = false) { + self.content = content() + self.action = action + self.asLink = asLink + self.disclosureIndicator = disclosureIndicator + } + + var body: some View { + HStack { + content + + Spacer() + + Image(systemName: "chevron.forward") + .font(Font.system(.footnote).weight(.bold)) + .foregroundColor(Color(UIColor.tertiaryLabel)) + .padding(.leading, 8) + } + .onTapGesture(perform: action) + } +} + + +struct SettingsCellView_Previews: PreviewProvider { + enum SampleOption: String, CaseIterable, Hashable, CustomStringConvertible { + case optionOne = "Lorem" + case optionTwo = "Ipsum" + case optionThree = "Dolor" + + var description: String { + return self.rawValue + } + } + + static var previews: some View { + Group { + List { + SettingsCellView(label: "Nulla commodo augue nec", + asLink: true, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Nulla commodo augue nec", + subtitle: "Curabitur erat massa, cursus sed velit", + asLink: true, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Maecenas ac purus", + accesory: .image(Image(systemName: "person.circle")), + asLink: true, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Maecenas ac purus", + subtitle: "Curabitur erat massa", + accesory: .image(Image(systemName: "person.circle")), + asLink: true, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Curabitur erat", + accesory: .rightDetail("Curabi"), + asLink: true, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Curabitur erat", + subtitle: "Nulla commodo augue", + accesory: .rightDetail("Aagittis"), + asLink: true, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Proin tempor urna", + accesory: .toggle(isOn: .constant(true)), + asLink: false, + disclosureIndicator: false) + .previewLayout(.sizeThatFits) + + SettingsCellView(label: "Proin tempor urna", + subtitle: "Fusce elementum quis", + accesory: .toggle(isOn: .constant(true)), + asLink: false, + disclosureIndicator: false) + .previewLayout(.sizeThatFits) + + @State var selectedOption: SampleOption = .optionOne + SettingsPickerCellView(label: "Proin tempor urna", options: SampleOption.allCases, selectedOption: $selectedOption) + .previewLayout(.sizeThatFits) + + SettingsCustomCell(content: { + HStack(spacing: 15) { + Image(systemName: "bell.fill") + .foregroundColor(.orange) + .imageScale(.large) + + VStack(alignment: .leading) { + Text("Notifications") + .font(.headline) + Text("Manage alerts and sounds") + .font(.subheadline) + .foregroundColor(.gray) + } + } + }, disclosureIndicator: true) + .previewLayout(.sizeThatFits) + + + } + } + } +} diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 4e84d94d85..b2826e470a 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -112,6 +112,6 @@ class SettingsLegacyViewProvider: ObservableObject { } var windows: UIViewController { - MacWaitlistViewController(nibName: nil, bundle: nil) + WindowsWaitlistViewController(nibName: nil, bundle: nil) } } diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index e6e2582385..319f625cca 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -39,7 +39,7 @@ struct SettingsState { struct SettingsStateAppeareance { var appTheme: ThemeName - var appIcon: AppIcon = AppIconManager.shared.appIcon + var appIcon: AppIcon var fireButtonAnimation: FireButtonAnimationType var textSize: Int var addressBarPosition: AddressBarPosition @@ -47,6 +47,7 @@ struct SettingsStateAppeareance { static var defaults: SettingsStateAppeareance { return SettingsStateAppeareance( appTheme: .systemDefault, + appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: .fireRising, textSize: 100, addressBarPosition: .top diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 2535e5b6a8..47757bc05c 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -40,7 +40,7 @@ struct SettingsView: View { .environmentObject(viewModel) .onAppear { - viewModel.updateState() + viewModel.onAppear() } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 545896d3e5..e23bc8b806 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -80,6 +80,7 @@ final class SettingsViewModel: ObservableObject { var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION if #available(iOS 15, *) { + print(featureFlagger.isFeatureOn(.networkProtection)) return featureFlagger.isFeatureOn(.networkProtection) } else { return false @@ -193,7 +194,6 @@ final class SettingsViewModel: ObservableObject { init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider) { self.state = SettingsState.defaults self.legacyViewProvider = legacyViewProvider - self.state = state ?? createSettingsState() setupSubscribers() } } @@ -201,11 +201,12 @@ final class SettingsViewModel: ObservableObject { // MARK: Private methods extension SettingsViewModel { - private func createSettingsState() -> SettingsState { - // This manual (re)initialzation will go away once appSettings and - // other dependencies are observable (Such as AppIcon and netP) - // and we can use subscribers + // This manual (re)initialzation will go away once appSettings and + // other dependencies are observable (Such as AppIcon and netP) + // and we can use subscribers (Currently called from the view onAppear) + private func updateState() -> SettingsState { let appereance = SettingsStateAppeareance(appTheme: appSettings.currentThemeName, + appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: appSettings.currentFireButtonAnimation, textSize: appSettings.textSize, addressBarPosition: appSettings.currentAddressBarPosition) @@ -263,12 +264,6 @@ extension SettingsViewModel { extension SettingsViewModel { private func setupSubscribers() { - - AppIconManager.shared.$appIcon - .sink { [weak self] newIcon in - self?.state.appeareance.appIcon = newIcon - } - .store(in: &cancellables) #if NETWORK_PROTECTION connectionObserver.publisher @@ -285,8 +280,8 @@ extension SettingsViewModel { // MARK: Public Methods extension SettingsViewModel { - func updateState() { - state = createSettingsState() + func onAppear() { + state = updateState() } func setAsDefaultBrowser() { From 8a64f7bf9267b9b1b45c1a7f3f81469c5d0130e8 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 17:44:32 +0100 Subject: [PATCH 12/99] Dismiss legacy ViewControllers and update settings --- DuckDuckGo/SettingsAppeareanceView.swift | 2 - DuckDuckGo/SettingsGeneralView.swift | 2 - DuckDuckGo/SettingsHostingController.swift | 4 ++ DuckDuckGo/SettingsState.swift | 6 +- DuckDuckGo/SettingsView.swift | 66 ---------------------- DuckDuckGo/SettingsViewModel.swift | 29 ++++++---- 6 files changed, 25 insertions(+), 84 deletions(-) diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index 1e9cc759b2..1993406d36 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -41,8 +41,6 @@ struct SettingsAppeareanceView: View { options: FireButtonAnimationType.allCases, selectedOption: viewModel.fireButtonAnimationBinding) - // The textsize settings view has a special behavior (detent adjustment) that requires access to a navigation controller - // The current implementation will not work on top of the SwiftUI stack, so we need to push it via the UIKit Container if viewModel.shouldShowTextSizeCell { SettingsCellView(label: "Text Size", action: { viewModel.presentLegacyView(.textSize) }, diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index c88ab7c6dc..ac2d2ab682 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -30,8 +30,6 @@ struct SettingsGeneralView: View { action: { viewModel.setAsDefaultBrowser() }, asLink: true) - // This old VC has a special behavior does not work as expected when presented in the SwiftUI Stack - // so we need to push it via the UIKit Containe SettingsCellView(label: "Add App to Your Dock", action: { viewModel.presentLegacyView(.addToDock) }, asLink: true) diff --git a/DuckDuckGo/SettingsHostingController.swift b/DuckDuckGo/SettingsHostingController.swift index 899bf29ff5..e614feb307 100644 --- a/DuckDuckGo/SettingsHostingController.swift +++ b/DuckDuckGo/SettingsHostingController.swift @@ -41,6 +41,10 @@ class SettingsHostingController: UIHostingController { viewModel.onRequestPresentLegacyView = { [weak self] vc, modal in self?.presentLegacyViewCOntroller(vc, modal: modal) } + + viewModel.onRequestDismissLegacyView = { [weak self] in + self?.navigationController?.popViewController(animated: true) + } let settingsView = SettingsView(viewModel: viewModel) self.rootView = AnyView(settingsView) diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 319f625cca..70e88bcd78 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -91,14 +91,14 @@ struct SettingsStateLogins { var activeWebsiteAccount: SecureVaultModels.WebsiteAccount? static var defaults: SettingsStateLogins { - return SettingsStateLogins(activeWebsiteAccount: nil) // Provide a default value + return SettingsStateLogins(activeWebsiteAccount: nil) } } struct SettingsStateNetP { - var subtitle: String = "" + var subtitle: String static var defaults: SettingsStateNetP { - return SettingsStateNetP(subtitle: "") // Provide a default value + return SettingsStateNetP(subtitle: "") } } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 47757bc05c..9a5bfafd97 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -45,69 +45,3 @@ struct SettingsView: View { } } - - - /* - Section(header: Text("Privacy"), - footer: Text("If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening.")) { - RightDetailCell(label: "Global Privacy Control (GMC)", value: "Enabled") - RightDetailCell(label: "Manage Cookie Popups", value: "Disabled") - PlainCell(label: "Set as Default Browser") - PlainCell(label: "Fireproof Sites") - RightDetailCell(label: "Automatically Clear Data", value: "Off") - ToggleCell(label: "Application Lock", value: false) - } - - - Section(header: Text("Customize"), - footer: Text("Disable to prevent links from automatically opening in other installed apps.")) { - PlainCell(label: "Keyboard") - ToggleCell(label: "Autocomplete Suggestions", value: false) - ToggleCell(label: "Private Voice Search", value: false) - ToggleCell(label: "Long Press Previews", value: false) - ToggleCell(label: "Open Links in Associated Apps", value: false) - } - Section(header: Text("More from DuckDuckGo")) { - SubtitleCell(label: "Email Protection", subtitle: "Block Email Trackers and hide your address") - SubtitleCell(label: "DuckDuckGo Mac App", subtitle: "Browse privaly with our app for Mac") - SubtitleCell(label: "DuckDuckGo Windows App", subtitle: "Browse privaly with our app for Windows") - SubtitleCell(label: "Network Protection", subtitle: "Join the private waitlist") - } - Section(header: Text("About")) { - PlainCell(label: "About DuckDuckGo") - RightDetailCell(label: "Version", value: "7.99.0.2") - PlainCell(label: "Share Feedback") - } - Section { - PlainCell(label: "Debug Menu") - } - */ - - -/* -struct SettingsAppeareanceView: View { - - @EnvironmentObject var viewModel: SettingsViewModel - - var body: some View { - Section(header: Text("Appeareance")) { - RightDetailCell(label: "Theme", - value: "System", - action: viewModel.showTheme) - ImageCell(label: "App Icon", - image: Image(systemName: "photo"), - action: viewModel.selectIcon) - RightDetailCell(label: "Fire Button Animation", - value: "Inferno", - action: viewModel.selectFireAnimation) - RightDetailCell(label: "Text Size", - value: "100%", - action: viewModel.selectTextSize) - - RightDetailCell(label: "Address Bar Position", - value: "Top", - action: viewModel.selectBarPosition) - } - } -} -*/ diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index e23bc8b806..b2730347aa 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -48,9 +48,10 @@ final class SettingsViewModel: ObservableObject { private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad private var cancellables = Set() - // Closure to request a legacy view controller presentation + // Closures to interact with legacy view controllers throught the container var onRequestPushLegacyView: ((UIViewController) -> Void)? var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? + var onRequestDismissLegacyView: (() -> Void)? // Our View State @Published private(set) var state: SettingsState @@ -194,7 +195,6 @@ final class SettingsViewModel: ObservableObject { init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider) { self.state = SettingsState.defaults self.legacyViewProvider = legacyViewProvider - setupSubscribers() } } @@ -204,12 +204,13 @@ extension SettingsViewModel { // This manual (re)initialzation will go away once appSettings and // other dependencies are observable (Such as AppIcon and netP) // and we can use subscribers (Currently called from the view onAppear) - private func updateState() -> SettingsState { + private func initState() { let appereance = SettingsStateAppeareance(appTheme: appSettings.currentThemeName, appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: appSettings.currentFireButtonAnimation, textSize: appSettings.textSize, addressBarPosition: appSettings.currentAddressBarPosition) + let privacy = SettingsStatePrivacy(sendDoNotSell: appSettings.sendDoNotSell, autoconsentEnabled: appSettings.autoconsentEnabled, autoclearDataEnabled: AutoClearSettingsModel(settings: appSettings) != nil, @@ -220,11 +221,17 @@ extension SettingsViewModel { longPressPreviews: appSettings.longPressPreviews, allowUniversalLinks: appSettings.allowUniversalLinks) - return SettingsState(appeareance: appereance, + self.state = SettingsState(appeareance: appereance, privacy: privacy, customization: customization, - logins: SettingsStateLogins(), - netP: SettingsStateNetP()) + logins: SettingsStateLogins.defaults, + netP: SettingsStateNetP.defaults) + + setupSubscribers() + + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.onRequestDismissLegacyView?() + } } @@ -247,13 +254,13 @@ extension SettingsViewModel { private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { switch NetworkProtectionAccessController().networkProtectionAccessType() { case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: - state.netP.subtitle = VPNWaitlist.shared.settingsSubtitle + self.state.netP.subtitle = VPNWaitlist.shared.settingsSubtitle case .waitlistInvited, .inviteCodeInvited: switch connectionStatus { case .connected: - state.netP.subtitle = UserText.netPCellConnected + self.state.netP.subtitle = UserText.netPCellConnected default: - state.netP.subtitle = UserText.netPCellDisconnected + self.state.netP.subtitle = UserText.netPCellDisconnected } } } @@ -281,7 +288,7 @@ extension SettingsViewModel { extension SettingsViewModel { func onAppear() { - state = updateState() + initState() } func setAsDefaultBrowser() { @@ -425,6 +432,6 @@ extension SettingsViewModel { // MARK: AutofillLoginSettingsListViewControllerDelegate extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { - // isPresentingLoginsView = false + onRequestDismissLegacyView?() } } From 38f07c044984d123bfdacdaa4dfa367e2606b98e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 18:00:44 +0100 Subject: [PATCH 13/99] Network Protection Status --- DuckDuckGo/SettingsLegacyViewProvider.swift | 12 ++++++++++++ DuckDuckGo/SettingsMoreView.swift | 2 +- DuckDuckGo/SettingsViewModel.swift | 19 +++++++++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index b2826e470a..5885c1fc54 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -114,4 +114,16 @@ class SettingsLegacyViewProvider: ObservableObject { var windows: UIViewController { WindowsWaitlistViewController(nibName: nil, bundle: nil) } + + @available(iOS 15.0, *) + var netPWaitlist: UIViewController { + VPNWaitlistViewController(nibName: nil, bundle: nil) + } + + @available(iOS 15, *) + var netP: UIViewController { + NetworkProtectionRootViewController() + } + + } diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index 4c356923e8..497b2bee1d 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -49,7 +49,7 @@ struct SettingsMoreView: View { if viewModel.shouldShowNetworkProtectionCell { SettingsCellView(label: "Network Protection", subtitle: viewModel.state.netP.subtitle, - action: { viewModel.presentLegacyView(.keyboard) }, + action: { viewModel.presentLegacyView(.netP) }, asLink: true, disclosureIndicator: true) } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index b2730347aa..24e43bab23 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -230,7 +230,7 @@ extension SettingsViewModel { setupSubscribers() DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - self.onRequestDismissLegacyView?() + // self.onRequestDismissLegacyView?() } } @@ -271,6 +271,7 @@ extension SettingsViewModel { extension SettingsViewModel { private func setupSubscribers() { + #if NETWORK_PROTECTION connectionObserver.publisher @@ -331,7 +332,8 @@ extension SettingsViewModel { autoclearData, keyboard, macApp, - windowsApp + windowsApp, + netP } @MainActor @@ -381,7 +383,20 @@ extension SettingsViewModel { case .macApp: pushLegacyView(legacyViewProvider.mac) + +#if NETWORK_PROTECTION + case .netP: + if #available(iOS 15, *) { + switch NetworkProtectionAccessController().networkProtectionAccessType() { + case .inviteCodeInvited, .waitlistInvited: + pushLegacyView(legacyViewProvider.netP) + default: + pushLegacyView(legacyViewProvider.netPWaitlist) + } + } +#endif } + } private func pushLegacyView(_ view: UIViewController) { From 50f5731ec91fcf15c9ebe3e9355a65d262f2dbf6 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 18:51:06 +0100 Subject: [PATCH 14/99] Remaining views and cleanup --- DuckDuckGo.xcodeproj/project.pbxproj | 4 + DuckDuckGo/SettingsAboutView.swift | 47 +++++++++ DuckDuckGo/SettingsLegacyViewProvider.swift | 101 ++++++-------------- DuckDuckGo/SettingsState.swift | 14 ++- DuckDuckGo/SettingsView.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 47 ++++----- 6 files changed, 114 insertions(+), 100 deletions(-) create mode 100644 DuckDuckGo/SettingsAboutView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6e00afbfd9..0b6853aaec 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -779,6 +779,7 @@ D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */; }; D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */; }; + D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C632B238432006C8AFB /* SettingsAboutView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2413,6 +2414,7 @@ D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCustomizeView.swift; sourceTree = ""; }; D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsMoreView.swift; sourceTree = ""; }; + D6E83C632B238432006C8AFB /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4496,6 +4498,7 @@ D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */, + D6E83C632B238432006C8AFB /* SettingsAboutView.swift */, ); name = Sections; sourceTree = ""; @@ -6319,6 +6322,7 @@ 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, + D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, 31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */, EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 0290472029E708B70008FE3C /* AppTPManageTrackersViewModel.swift in Sources */, diff --git a/DuckDuckGo/SettingsAboutView.swift b/DuckDuckGo/SettingsAboutView.swift new file mode 100644 index 0000000000..e60fbc6f56 --- /dev/null +++ b/DuckDuckGo/SettingsAboutView.swift @@ -0,0 +1,47 @@ +// +// SettingsAboutView.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 UIKit + +struct SettingsAboutView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + + var body: some View { + Section { + + SettingsCellView(label: "About DuckDuckGo", + action: { viewModel.presentLegacyView(.about) }, + asLink: true, + disclosureIndicator: true) + + SettingsCellView(label: "Version", + accesory: .rightDetail(viewModel.state.about.version), + asLink: true) + + SettingsCellView(label: "Share Feedback", + action: { viewModel.presentLegacyView(.feedback) }, + asLink: true) + + } + + } + +} diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 5885c1fc54..290b442b71 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -24,16 +24,6 @@ import Core import BrowserServicesKit import SyncUI -struct LazyView: View { - let build: () -> Content - init(_ build: @autoclosure @escaping () -> Content) { - self.build = build - } - var body: some View { - build() - } -} - class SettingsLegacyViewProvider: ObservableObject { let syncService: DDGSyncing @@ -46,12 +36,37 @@ class SettingsLegacyViewProvider: ObservableObject { self.appSettings = appSettings } - // Legacy UIKit Views (Pushed unmodified) - var addToDock: UIViewController { - let storyboard = UIStoryboard(name: "HomeRow", bundle: nil) - return storyboard.instantiateViewController(identifier: "instructions") as! HomeRowInstructionsViewController + enum LegacyView { + case addToDock, sync, logins, textSize, appIcon, gpc, autoconsent, unprotectedSites, fireproofSites, autoclearData, keyboard, macApp, windowsApp, netP, about, feedback } + private func instantiate(_ identifier: String, fromStoryboard name: String) -> UIViewController { + let storyboard = UIStoryboard(name: name, bundle: nil) + return storyboard.instantiateViewController(withIdentifier: identifier) + } + + // Legacy UIKit Views (Pushed unmodified) + var addToDock: UIViewController { instantiate( "instructions", fromStoryboard: "HomeRow") } + var textSettings: UIViewController { return instantiate("TextSize", fromStoryboard: "Settings") } + var appIcon: UIViewController { instantiate("AppIcon", fromStoryboard: "Settings") } + var gpc: UIViewController { instantiate("DoNotSell", fromStoryboard: "Settings") } + var autoConsent: UIViewController { instantiate("AutoconsentSettingsViewController", fromStoryboard: "Settings") } + var unprotectedSites: UIViewController { instantiate("UnprotectedSites", fromStoryboard: "Settings") } + var fireproofSites: UIViewController { instantiate("FireProofSites", fromStoryboard: "Settings") } + var autoclearData: UIViewController { instantiate("AutoClearSettingsViewController", fromStoryboard: "Settings") } + var keyboard: UIViewController { instantiate("Keyboard", fromStoryboard: "Settings") } + var feedback: UIViewController { instantiate("Feedback", fromStoryboard: "Feedback") } + var mac: UIViewController { MacWaitlistViewController(nibName: nil, bundle: nil) } + var windows: UIViewController { WindowsWaitlistViewController(nibName: nil, bundle: nil) } + var about: UIViewController { AboutViewController() } + + @available(iOS 15.0, *) + var netPWaitlist: UIViewController { VPNWaitlistViewController(nibName: nil, bundle: nil) } + + @available(iOS 15, *) + var netP: UIViewController { NetworkProtectionRootViewController() } + + @MainActor var syncSettings: UIViewController { return SyncSettingsViewController(syncService: self.syncService, @@ -67,63 +82,5 @@ class SettingsLegacyViewProvider: ObservableObject { selectedAccount: selectedAccount) } - var textSettings: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController - } - - var appIcon: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "AppIcon") as! AppIconSettingsViewController - } - - var gpc: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "DoNotSell") as! DoNotSellSettingsViewController - } - - var autoConsent: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "AutoconsentSettingsViewController") as! AutoconsentSettingsViewController - } - - var unprotectedSites: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "UnprotectedSites") as! UnprotectedSitesViewController - } - - var fireproofSites: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "FireProofSites") as! PreserveLoginsSettingsViewController - } - - var autoclearData: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "AutoClearSettingsViewController") as! AutoClearSettingsViewController - } - - var keyboard: UIViewController { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - return storyboard.instantiateViewController(identifier: "Keyboard") as! KeyboardSettingsViewController - } - - var mac: UIViewController { - MacWaitlistViewController(nibName: nil, bundle: nil) - } - - var windows: UIViewController { - WindowsWaitlistViewController(nibName: nil, bundle: nil) - } - - @available(iOS 15.0, *) - var netPWaitlist: UIViewController { - VPNWaitlistViewController(nibName: nil, bundle: nil) - } - - @available(iOS 15, *) - var netP: UIViewController { - NetworkProtectionRootViewController() - } - } diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 70e88bcd78..60d01990cf 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -25,6 +25,7 @@ struct SettingsState { var customization: SettingsStateCustomization var logins: SettingsStateLogins var netP: SettingsStateNetP + var about: SettingsStateAbout // Add state for other sections here... static var defaults: SettingsState { @@ -33,7 +34,9 @@ struct SettingsState { privacy: SettingsStatePrivacy.defaults, customization: SettingsStateCustomization.defaults, logins: SettingsStateLogins.defaults, - netP: SettingsStateNetP.defaults) + netP: SettingsStateNetP.defaults, + about: SettingsStateAbout.defaults + ) } } @@ -102,3 +105,12 @@ struct SettingsStateNetP { return SettingsStateNetP(subtitle: "") } } + +struct SettingsStateAbout { + var version: String + + static var defaults: SettingsStateAbout { + return SettingsStateAbout(version: "0.0.0.0") + } + +} diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 9a5bfafd97..cf8eb97199 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -33,6 +33,7 @@ struct SettingsView: View { SettingsPrivacyView() SettingsCustomizeView() SettingsMoreView() + SettingsAboutView() } .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 24e43bab23..5292b00d01 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -40,6 +40,8 @@ final class SettingsViewModel: ObservableObject { private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) private var legacyViewProvider: SettingsLegacyViewProvider + private lazy var versionProvider: AppVersion = AppVersion.shared + #if NETWORK_PROTECTION private let connectionObserver = ConnectionStatusObserverThroughSession() #endif @@ -221,18 +223,18 @@ extension SettingsViewModel { longPressPreviews: appSettings.longPressPreviews, allowUniversalLinks: appSettings.allowUniversalLinks) + let about = SettingsStateAbout(version: versionProvider.versionAndBuildNumber) + + self.state = SettingsState(appeareance: appereance, - privacy: privacy, - customization: customization, - logins: SettingsStateLogins.defaults, - netP: SettingsStateNetP.defaults) + privacy: privacy, + customization: customization, + logins: SettingsStateLogins.defaults, + netP: SettingsStateNetP.defaults, + about: about) setupSubscribers() - DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - // self.onRequestDismissLegacyView?() - } - } private func firePixel(_ event: Pixel.Event) { @@ -318,26 +320,10 @@ extension SettingsViewModel { // These UIKit views have issues when presented via UIHostingController so // we fall back to UIKit navigation extension SettingsViewModel { - - enum LegacyView { - case addToDock, - sync, - logins, - textSize, - appIcon, - gpc, - autoconsent, - unprotectedSites, - fireproofSites, - autoclearData, - keyboard, - macApp, - windowsApp, - netP - } - + @MainActor - func presentLegacyView(_ view: LegacyView) { + func presentLegacyView(_ view: SettingsLegacyViewProvider.LegacyView) { + switch view { case .addToDock: @@ -383,6 +369,12 @@ extension SettingsViewModel { case .macApp: pushLegacyView(legacyViewProvider.mac) + + case .about: + pushLegacyView(legacyViewProvider.about) + + case .feedback: + presentLegacyView(legacyViewProvider.feedback, modal: false) #if NETWORK_PROTECTION case .netP: @@ -395,6 +387,7 @@ extension SettingsViewModel { } } #endif + } } From dd5ae07e7094079cfc8999151ebfd24e04bd28e8 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 8 Dec 2023 20:18:09 +0100 Subject: [PATCH 15/99] Fix Debug Menu --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++ DuckDuckGo/Base.lproj/Settings.storyboard | 38 +++++++------- DuckDuckGo/MainViewController+Segues.swift | 3 +- DuckDuckGo/RootDebugViewController.swift | 20 ++++--- DuckDuckGo/SettingsAboutView.swift | 2 +- DuckDuckGo/SettingsDebugView.swift | 39 ++++++++++++++ DuckDuckGo/SettingsLegacyViewProvider.swift | 26 ++++++--- DuckDuckGo/SettingsView.swift | 1 + DuckDuckGo/SettingsViewController.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 58 +++++++-------------- 10 files changed, 119 insertions(+), 73 deletions(-) create mode 100644 DuckDuckGo/SettingsDebugView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0b6853aaec..82e50bde58 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -780,6 +780,7 @@ D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */; }; D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C632B238432006C8AFB /* SettingsAboutView.swift */; }; + D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2415,6 +2416,7 @@ D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsMoreView.swift; sourceTree = ""; }; D6E83C632B238432006C8AFB /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; + D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDebugView.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4499,6 +4501,7 @@ D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */, D6E83C632B238432006C8AFB /* SettingsAboutView.swift */, + D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */, ); name = Sections; sourceTree = ""; @@ -6510,6 +6513,7 @@ 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, + D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, diff --git a/DuckDuckGo/Base.lproj/Settings.storyboard b/DuckDuckGo/Base.lproj/Settings.storyboard index 52538150b8..6feba9b4ca 100644 --- a/DuckDuckGo/Base.lproj/Settings.storyboard +++ b/DuckDuckGo/Base.lproj/Settings.storyboard @@ -744,11 +744,11 @@ - + - + @@ -1259,32 +174,14 @@ - - - - - - - - - - - - - - - - - - - + - + @@ -1515,7 +412,7 @@ - + @@ -1580,7 +477,7 @@ - + @@ -1719,7 +616,7 @@ - + @@ -1871,14 +768,14 @@ - + - + @@ -1971,14 +868,14 @@ - + - + @@ -2064,14 +961,14 @@ - + - + @@ -2168,7 +1065,6 @@ - diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 7a96bc3662..7788619ee9 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1453,6 +1453,9 @@ https://duckduckgo.com/mac"; /* Second subtitle for Network Protection join waitlist screen */ "network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + /* Title for Network Protection joined waitlist screen */ "network-protection.waitlist.joined.title" = "You’re on the list!"; @@ -1723,7 +1726,7 @@ https://duckduckgo.com/mac"; /* Settings cell for About DDG */ "settings.about.ddg" = "About DuckDuckGo"; -/* Settings section title for About */ +/* Settings section title for About DuckDuckGo */ "settings.about.section" = "About"; /* about page */ @@ -1735,10 +1738,10 @@ Our privacy protections work without having to know anything about the technical But if you *do* want a peek under the hood, you can find more information about how DuckDuckGo privacy protections work on our [help pages](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; -/* Settings screen cell text for add to dock */ +/* Settings screen cell text for adding the app to the dock */ "settings.add.to.dock" = "Add App to Your Dock"; -/* Settings screen cell text for add widget */ +/* Settings screen cell text for add widget to the home screen */ "settings.add.widget" = "Add Widget to Home Screen"; /* Settings screen cell text for addess bar position */ @@ -1771,7 +1774,7 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings title for the customize section */ "settings.customize" = "Customize"; -/* Settings screen cell text for default browser */ +/* Settings screen cell text for setting the app as default browser */ "settings.default.browser" = "Set as Default Browser"; /* Settings cell for Email Protection */ @@ -1792,7 +1795,7 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings screen cell text for GPC */ "settings.gpc" = "Global Privacy Control"; -/* Settings screen cell text for app icon */ +/* Settings screen cell text for app icon selection */ "settings.icon" = "App Icon"; /* Settings screen cell for Keyboard */ @@ -1810,13 +1813,13 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings title for the privacy section */ "settings.privacy" = "Privacy"; -/* Settings screen cell text for sync */ +/* Settings screen cell text for sync and backup */ "settings.sync" = "Sync & Backup"; /* Settings screen cell text for text size */ "settings.text.size" = "Text Size"; -/* Settings screen cell text for them */ +/* Settings screen cell text for theme */ "settings.theme" = "Theme"; /* Title for the Settings View */ From eab5c1d2cac065945694b033505a463375fedd88 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 14:10:50 +0100 Subject: [PATCH 25/99] Fixed Remaining Strings --- DuckDuckGo/Theme.swift | 22 ++++++++++++++++++---- DuckDuckGo/UserText.swift | 4 ++-- DuckDuckGo/en.lproj/Localizable.strings | 4 ++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/Theme.swift b/DuckDuckGo/Theme.swift index 70da3e70b9..6051e50a35 100644 --- a/DuckDuckGo/Theme.swift +++ b/DuckDuckGo/Theme.swift @@ -20,12 +20,26 @@ import UIKit enum ThemeName: String, CaseIterable, Identifiable, CustomStringConvertible { - case systemDefault = "System Default" - case light = "Light" - case dark = "Dark" + case systemDefault + case light + case dark var id: String { self.rawValue } - var description: String { self.rawValue } + + var description: String { + return descriptionText + } + + var descriptionText: String { + switch self { + case .systemDefault: + return UserText.themeNameDefault + case .light: + return UserText.themeNameLight + case .dark: + return UserText.themeNameDark + } + } } protocol Theme { diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 3c655da37b..990dc359fa 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -936,8 +936,8 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsAddressBar = NSLocalizedString("settings.address.bar", value: "Address Bar Position", comment: "Settings screen cell text for addess bar position") public static let settingsPrivacySection = NSLocalizedString("settings.privacy", value: "Privacy", comment: "Settings title for the privacy section") - public static let settingsGPC = NSLocalizedString("settings.gpc", value: "Global Privacy Control", comment: "Settings screen cell text for GPC") - public static let settingsCookiePopups = NSLocalizedString("settings.cookie.popups", value: "Manage Cookie Popups", comment: "Settings screen cell text for Cookie popups") + public static let settingsGPC = NSLocalizedString("settings.gpc", value: "Global Privacy Control (GPC)", comment: "Settings screen cell text for GPC") + public static let settingsCookiePopups = NSLocalizedString("settings.cookie.popups", value: "Manage Cookie Pop-ups", comment: "Settings screen cell text for Cookie popups") public static let settingsUnprotectedSites = NSLocalizedString("settings.unprotected.sites", value: "Unprotected Sites", comment: "Settings screen cell text for Unprotected Sites") public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 7788619ee9..1bd0fb0559 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1769,7 +1769,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.clear.data" = "Automatically Clear Data"; /* Settings screen cell text for Cookie popups */ -"settings.cookie.popups" = "Manage Cookie Popups"; +"settings.cookie.popups" = "Manage Cookie Pop-ups"; /* Settings title for the customize section */ "settings.customize" = "Customize"; @@ -1793,7 +1793,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.fireproof.sites" = "Fireproof Sites"; /* Settings screen cell text for GPC */ -"settings.gpc" = "Global Privacy Control"; +"settings.gpc" = "Global Privacy Control (GPC)"; /* Settings screen cell text for app icon selection */ "settings.icon" = "App Icon"; From a6cc6df6843d13c080fe728a645181891cfc5813 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 14:46:59 +0100 Subject: [PATCH 26/99] Localization Files --- DuckDuckGo/bg.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/cs.lproj/Localizable.strings | 134 ++++++++++- DuckDuckGo/da.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/de.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/el.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/es.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/et.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/fi.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/fr.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/hr.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/hu.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/it.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/lt.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/lv.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/nb.lproj/Localizable.strings | 232 ++++++++++++++++++-- DuckDuckGo/nb.lproj/Localizable.stringsdict | 118 ++++++---- DuckDuckGo/nl.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/pl.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/pt.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/ro.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/ru.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/sk.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/sl.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/sv.lproj/Localizable.strings | 108 +++++++++ DuckDuckGo/tr.lproj/Localizable.strings | 108 +++++++++ 25 files changed, 2797 insertions(+), 63 deletions(-) diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index 4619b25016..cda2a56879 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Добре дошли в\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo за Mac притежава необходимата скорост, както и очакваните от Вас функции за сърфиране, и предлага най-добрите в този клас елементи за защита."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Любими"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Относно DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "За нас"; + /* about page */ "settings.about.text" = "DuckDuckGo е независима компания, основана през 2008 г., за защита на личните данни в интернет за всеки, на когото му е омръзнало да бъде проследяван онлайн и иска лесно решение на проблема. Ние сме доказателство, че можете да получите реална и безкомпромисна защита на личните данни онлайн.\n\nБраузърът DuckDuckGo притежава функциите, които очаквате от един основен браузър, като отметки, раздели, пароли и много други, както и [редица мощни защити на поверителността](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), които не се предлагат в повечето популярни браузъри по подразбиране. Този уникален и комплексен набор от защити на поверителността предлага сигурност при онлайн дейностите – търсене, сърфиране, изпращане на имейли и др.\n\nЗа да работят, нашите защити на поверителността не изискват да имате технически познания или да се извършвате сложни настройки. Необходимо е да използвате браузъра DuckDuckGo на всички Ваши устройства и ще получите поверителност по подразбиране.\n\nНо ако *наистина* искате да надникнете зад кулисите, и да разберете как работят защитите на поверителността на DuckDuckGo, можете да намерите повече информация на нашите [страници за помощ](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Добавяне на приложението към панела"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Добавяне на приспособлението към началния екран"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Позиция на адресната лента"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Външен Вид"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Отваряне на връзките в свързаните приложения"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Деактивирайте, за да предотвратите автоматично отваряне на връзки в други инсталирани приложения."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Показване на предложенията за автоматично довършване"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Заключване на приложение"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Автоматично изчистване на данните"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Автоматично изчистване на данните"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Управление на прозорци за бисквитки"; + +/* Settings title for the customize section */ +"settings.customize" = "Персонализиране"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Задаване като браузър по подразбиране"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Защита на имейл"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Блокирайте имейл тракерите и скрийте своя адрес"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Споделяне на отзив"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Анимация на огнения бутон"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Огнеустойчиви сайтове"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Глобален контрол на поверителността (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Икона на приложението"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Клавиатура"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Данни за вход"; + +/* Settings title for the 'More' section */ +"settings.more" = "Още от DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Прегледи с продължително натискане"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Поверителност"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Синхронизиране и архивиране"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Размер на текста"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Тема"; + +/* Title for the Settings View */ +"settings.title" = "Настройки"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Незащитени сайтове"; + +/* Settings cell for Version */ +"settings.version" = "Версия"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Поверително гласово търсене"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Изпращане на доклад"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Разрешете достъп до микрофона в настройките на системата iOS, за да може DuckDuckGo да използва гласовите функции."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "ОК"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Изисква се достъп до микрофона"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 1542c6559b..93a782a622 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Vítejte na\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo pro Mac má rychlost, kterou potřebuješ, funkce prohlížeče, které očekáváš, a obsahuje naše nejlepší nástroje na ochranu soukromí."; @@ -1453,7 +1456,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1489,24 +1492,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Upozornění VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Po přerušení automaticky obnovit připojení VPN."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vždycky zapnuté"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Oznámení sítě VPN"; @@ -1517,10 +1529,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ochrana sítě zabraňuje únikům DNS k poskytovateli internetových služeb tím, že směruje dotazy DNS přes tunel VPN na náš vlastní resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Zabezpečený systém DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Nastavení sítě VPN"; @@ -1648,9 +1657,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Oblíbené"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "O DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "O společnosti"; + /* about page */ "settings.about.text" = "DuckDuckGo je nezávislá společnost založená v roce 2008, která se zabývá ochranou soukromí pro všechny, kdo už mají dost sledování na internetu a chtějí snadné řešení. Jsme důkazem, že skutečná ochrana soukromí na internetu nemusí mít kompromisy.\n\nProhlížeč DuckDuckGo má všechno, co od prohlížeče očekáváš, jako jsou záložky, karty, hesla a další funkce. A k tomu má víc než [desítku účinných funkcí na ochranu soukromí](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), které většina oblíbených prohlížečů ve výchozím nastavení nenabízí. Tahle jedinečná komplexní sada na ochranu soukromí pomáhá chránit tvoje aktivity na internetu, ať už zrovna něco vyhledáváš, jen tak surfuješ, nebo posíláš e-maily.\n\nAby naše ochrana soukromí fungovala, nemusíš znát technické detaily ani řešit žádná složitá nastavení. Stačí přejít na prohlížeč DuckDuckGo ve všech zařízeních a dostaneš soukromí pro všechny své aktivity.\n\nJestli chceš nahlédnout pod pokličku, víc informací o tom, jak ochrana soukromí DuckDuckGo funguje, najdeš v naší [nápovědě](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Přidat aplikaci do docku"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Přidat widget na domovskou obrazovku"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Pozice adresního řádku"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Vzhled"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Otevřít odkazy v přidružených aplikacích"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Zakažte, chcete-li zabránit automatickému otevírání odkazů v jiných nainstalovaných aplikacích."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Zobrazit návrhy automatického doplňování"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Zámek aplikace"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Automaticky vymazat data"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Automaticky vymazat data"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Správa vyskakovacích oken ohledně cookies"; + +/* Settings title for the customize section */ +"settings.customize" = "Přizpůsobit"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Nastavit jako výchozí prohlížeč"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Ochrana e-mailu"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokování trackerů v e-mailu a skrytí adresy"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Podělte se o zpětnou vazbu"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animace tlačítka pro mazání"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Stránky s ochranou"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Globální kontrola ochrany osobních údajů (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Ikona aplikace"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Klávesnice"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Přihlášení"; + +/* Settings title for the 'More' section */ +"settings.more" = "Další od DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Náhledy dlouhého stisknutí"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Soukromí"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synchronizace a zálohování"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Velikost textu"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Téma"; + +/* Title for the Settings View */ +"settings.title" = "Nastavení"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Nechráněné stránky"; + +/* Settings cell for Version */ +"settings.version" = "Verze"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Soukromé hlasové vyhledávání"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Odešlete zprávu"; @@ -1735,6 +1846,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Pro používání hlasových funkcí DuckDuckGo je potřeba v Nastaveních iOS povolit přístup k mikrofonu."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "DOBŘE"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Je nutný přístup k mikrofonu"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 3b9fcbc22c..fa5de6c141 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Velkommen til\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo til Mac har den hastighed, du har brug for, de browserfunktioner, du forventer, og leveres spækket med vores bedste privatlivsartikler i klassen."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoritter"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Om DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Omkring"; + /* about page */ "settings.about.text" = "DuckDuckGo er en uafhængig virksomhed, grundlagt i 2008, der beskytter privatlivet på internettet for alle, der er trætte af at blive sporet online og ønsker en nem løsning. Vi er bevis på, at du kan få ægte beskyttelse af privatlivet online uden at gå på kompromis.\n\nDuckDuckGo-browseren kommer med de funktioner, du forventer af en standardbrowser, som bogmærker, faner, adgangskoder og meget mere, plus over [et dusin kraftfulde beskyttelser af privatlivet](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), som ikke tilbydes i de fleste populære browsere som standard. Dette unikke og omfattende sæt af beskyttelsesfunktioner hjælper med at beskytte dine onlineaktiviteter, fra søgning til browsing, e-mailing og meget mere.\n\nVores beskyttelse af privatlivet fungerer, uden at du behøver at vide noget om de tekniske detaljer eller håndtere komplicerede indstillinger. Det eneste, du skal gøre, er at skifte din browser til DuckDuckGo på alle dine enheder, så får du privatliv som standard.\n\nMen hvis du gerne vil have et kig under motorhjelmen, kan du finde flere oplysninger om, hvordan DuckDuckGos beskyttelse af privatlivet fungerer, på vores [hjælpesider](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Føj appen til din dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Tilføj widget til startskærmen"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Placering af adresselinje"; + +/* Settings screen appearance section title */ +"settings.appearance" = "udseende"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Åbn links i tilknyttede apps"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Deaktiver for at forhindre, at links automatisk åbnes i andre installerede apps."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Vis forslag til Autoudfyld"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Applikationslås"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Ryd data automatisk"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Ryd data automatisk"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Administrer cookie pop op-vinduer"; + +/* Settings title for the customize section */ +"settings.customize" = "Tilpas"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Angiv som standardbrowser"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-mailbeskyttelse"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Bloker e-mailtrackere, og skjul din adresse"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Del feedback"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Ildknap-animation"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Brandsikre websteder"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "App-ikon"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tastatur"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Logins"; + +/* Settings title for the 'More' section */ +"settings.more" = "Mere fra DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Eksempler med lang tryk"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privatliv"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synkronisering og sikkerhedskopiering"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Tekststørrelse"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Indstillinger"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Ubeskyttede websteder"; + +/* Settings cell for Version */ +"settings.version" = "Version"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privat stemmesøgning"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Indsend rapport"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Tillad mikrofonadgang i iOS-systemindstillinger for DuckDuckGo for at bruge stemmefunktioner."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "Okay"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofonadgang påkrævet"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 3a0b2d35d1..64b930603b 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Willkommen bei\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo für Mac hat die Geschwindigkeit, die du brauchst, die Browserfunktionen, die du erwartest, und ist vollgepackt mit unseren grundlegenden Funktionen für den Datenschutz."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoriten"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Über DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Über"; + /* about page */ "settings.about.text" = "DuckDuckGo ist die unabhängige Firma für Internet-Datenschutz, die 2008 für alle gegründet wurde, die es leid sind, online getrackt zu werden, und eine einfache Lösung suchen. Wir sind der Beweis, dass du echten Online-Datenschutz ohne Kompromisse erhalten kannst.\n\nDer DuckDuckGo-Browser bietet die Funktionen, die von einem alltagstauglichen Browser erwartet werden, wie Lesezeichen, Tabs, Passwörter und mehr, sowie über [ein Dutzend leistungsstarke Datenschutzmaßnahmen](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), die in den meisten gängigen Browsern nicht standardmäßig angeboten werden. Dieser einzigartige, umfassende Datenschutz hilft dir, deine Online-Aktivitäten zu schützen – von der Suche über das Browsen bis hin zu E-Mails und vielem mehr.\n\nUnser Datenschutz funktioniert, ohne dass du etwas über die technischen Details wissen oder dich mit komplizierten Einstellungen auseinandersetzen musst. Du musst lediglich den Browser auf allen deinen Geräten auf DuckDuckGo umstellen und schon erhältst du echte Privatsphäre als Standard.\n\nWenn du jedoch einen Blick unter die Haube werfen möchtest, findest du auf unseren [Hilfeseiten](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/) weitere Informationen darüber, wie der Datenschutz bei DuckDuckGo funktioniert."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "App zum Dock hinzufügen"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Widget zum Startbildschirm hinzufügen"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Position der Adressleiste"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Aussehen"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Links in den dazugehörigen Apps öffnen"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Aktivieren, damit sich Links nicht automatisch in anderen installierten Apps öffnen."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Vorschläge für die Autovervollständigung"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Anwendungssperre"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Daten automatisch löschen"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Daten automatisch löschen"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Cookie-Pop-ups verwalten"; + +/* Settings title for the customize section */ +"settings.customize" = "Anpassen"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Als Standard-Browser festlegen"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-Mail-Schutz"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "E-Mail-Tracker blockieren und deine Adresse verbergen"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Feedback teilen"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animation der Schaltfläche „Feuer“"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Feuerfeste Seiten"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "App-Symbol"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tastatur"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Anmeldedaten"; + +/* Settings title for the 'More' section */ +"settings.more" = "Mehr von DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Vorschau durch langes Tippen"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privatsphäre"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synchronisieren und sichern"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Textgröße"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Design"; + +/* Title for the Settings View */ +"settings.title" = "Einstellungen"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Ungeschützte Websites"; + +/* Settings cell for Version */ +"settings.version" = "Version"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Private Sprachsuche"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Bericht senden"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Bitte erlaube den Mikrofonzugriff in den iOS-Systemeinstellungen, damit DuckDuckGo Sprachfunktionen nutzen kann."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofonzugriff erforderlich"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index b416648110..27a65200ca 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Καλώς ορίσατε στο\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "Το DuckDuckGo για Mac έχει την ταχύτητα που χρειάζεστε, τις λειτουργίες περιήγησης που αναμένετε και περιλαμβάνει τις καλύτερες δυνατότητες απορρήτου στην κατηγορία μας."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Αγαπημένα"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Σχετικά με το DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Σχετικά"; + /* about page */ "settings.about.text" = "Το DuckDuckGo αποτελεί την ανεξάρτητη εταιρεία προστασίας προσωπικών δεδομένων στο διαδίκτυο που ιδρύθηκε το 2008 για όσους έχουν βαρεθεί να παρακολουθούνται στο διαδίκτυο και επιθυμούν μια εύκολη λύση. Αποτελούμε τρανή απόδειξη ότι μπορείτε να αποκτήσετε πραγματική προστασία του απορρήτου σας στο διαδίκτυο χωρίς συμβιβασμούς.\n\nο πρόγραμμα περιήγησης DuckDuckGo διαθέτει τις λειτουργίες που περιμένετε από ένα πρόγραμμα περιήγησης, όπως σελιδοδείκτες, καρτέλες, κωδικούς πρόσβασης και πολλά άλλα, καθώς και πάνω από [δώδεκα ισχυρές λειτουργίες προστασίες απορρήτου](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) που δεν προσφέρονται από τα περισσότερα δημοφιλή προγράμματα περιήγησης βάσει προεπιλογής. Αυτό το μοναδικά ολοκληρωμένο σύνολο προστασίας του απορρήτου συμβάλλει στην προστασία των διαδικτυακών δραστηριοτήτων σας, από αναζήτηση έως περιήγηση, αποστολή email και πολλά άλλα.\n\nΗ προστασία απορρήτου μας λειτουργεί χωρίς να χρειάζεται να γνωρίζετε τις τεχνικές λεπτομέρειες ή να ασχολείστε με περίπλοκες ρυθμίσεις. Το μόνο που έχετε να κάνετε είναι να αλλάξετε το πρόγραμμα περιήγησής σας σε DuckDuckGo σε όλες τις συσκευές σας και θα έχετε ιδιωτικότητα βάσει προεπιλογής.\n\nΑλλά αν *θέλετε* να ρίξετε μια αναλυτική ματιά, μπορείτε να βρείτε περισσότερες πληροφορίες σχετικά με το πώς λειτουργούν οι προστασίες απορρήτου του DuckDuckGo στις [σελίδες της ενότητας Βοήθεια](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Προσθήκη εφαρμογής στο Dock σας"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Προσθήκη μικροεφαρμογής στην Αρχική οθόνη"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Θέση γραμμής διευθύνσεων"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Εμφάνιση"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Ανοίξτε τους συνδέσμους στις σχετικές εφαρμογές"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Απενεργοποιήστε για να αποτρέψετε το αυτόματο άνοιγμα συνδέσμων σε άλλες εγκατεστημένες εφαρμογές."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Εμφάνιση προτάσεων αυτόματης συμπλήρωσης"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Κλείδωμα εφαρμογής"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Αυτόματη απαλοιφή δεδομένων"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Αυτόματη απαλοιφή δεδομένων"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Διαχείριση αναδυόμενων παραθύρων cookies"; + +/* Settings title for the customize section */ +"settings.customize" = "Προσαρμογή"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Ορισμός ως προεπιλεγμένο πρόγραμμα περιήγησης"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Προστασία email"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Αποκλείστε εφαρμογές παρακολούθησης email και αποκρύψτε τη διεύθυνσή σας"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Κοινοποίηση σχολίου"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Κινούμενο κουμπί φωτιάς"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Ασφαλείς ιστότοποι"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Παγκόσμιος έλεγχος απορρήτου (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Εικονίδιο Εφαρμογής"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Πληκτρολόγιο"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Συνδέσεις"; + +/* Settings title for the 'More' section */ +"settings.more" = "Περισσότερα από το DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Προεπισκοπήσεις με παρατεταμένο πάτημα"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Ιδιωτικότητα"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Συγχρονισμός και δημιουργία αντιγράφων ασφαλείας"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Μέγεθος κειμένου"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Θέμα"; + +/* Title for the Settings View */ +"settings.title" = "Ρυθμίσεις"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Μη προστατευόμενοι ιστότοποι"; + +/* Settings cell for Version */ +"settings.version" = "Έκδοση"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Ιδιωτική φωνητική αναζήτηση"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Υποβολή αναφοράς"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Επιτρέψτε πρόσβαση στο Μικρόφωνο από τις Ρυθμίσεις συστήματος iOS για να κάνει το DuckDuckGo χρήση των φωνητικών λειτουργιών."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "Εντάξει"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Απαιτείται πρόσβαση στο μικρόφωνο"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index ed3a0d69b7..c974029da3 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "¡Bienvenido a\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo para Mac tiene la velocidad que necesitas, las funciones de navegación que esperas y viene repleta de nuestros mejores Privacy Essentials."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoritos"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Acerca de DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Acerca de"; + /* about page */ "settings.about.text" = "DuckDuckGo es la empresa de privacidad en internet independiente fundada en 2008 para quienes estén cansados de ser rastreados en línea y quieran una solución sencilla. Somos la prueba de que es posible obtener protección de privacidad real en línea sin concesiones.\n\nEl navegador DuckDuckGo incluye las funciones que esperas de un navegador de referencia, como marcadores, pestañas, contraseñas y más, así como más de [una docena de potentes funciones de protección de privacidad](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) no se ofrece en la mayoría de los navegadores populares de forma predeterminada. Este conjunto completo de funciones de protección de privacidad ayuda a proteger tu actividad en línea, desde la búsqueda hasta la navegación, el correo electrónico y mucho más.\n\nNuestra protección de privacidad funciona sin necesidad de conocimientos técnicos y sin tener que lidiar con configuraciones complicadas. Lo único que tienes que hacer es cambiar el navegador a DuckDuckGo en todos tus dispositivos y obtener privacidad de forma predeterminada.\n\nSin embargo, si quieres conocer los entresijos, puedes encontrar más información sobre cómo funcionan las protecciones de privacidad de DuckDuckGo en nuestras [páginas de ayuda](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Añadir una aplicación a tu Dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Añadir widget a la pantalla de inicio"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Posición de la barra de direcciones"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Apariencia"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Abrir enlaces en aplicaciones asociadas"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Desactivar para evitar que los enlaces se abran automáticamente en otras aplicaciones instaladas."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Sugerencias de autocompletado"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Bloqueo de aplicación"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Borrar datos automáticamente"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Borrar datos automáticamente"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Administrar ventanas emergentes de cookies"; + +/* Settings title for the customize section */ +"settings.customize" = "Personalizar"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Definir como navegador predeterminado"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Protección del correo electrónico"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Bloquea los rastreadores de correo electrónico y oculta tu dirección"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Compartir opiniones"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animación del botón Fuego"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Sitios web a prueba de fuego"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Control Global de Privacidad (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Icono de la aplicación"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Teclado"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Inicios de sesión"; + +/* Settings title for the 'More' section */ +"settings.more" = "Más sobre DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Vistas previas con pulsación larga"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privacidad"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sincronización y copia de seguridad"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Tamaño del texto"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Ajustes"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Sitios no protegidos"; + +/* Settings cell for Version */ +"settings.version" = "Versión"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Búsqueda privada por voz"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Enviar informe"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Permite el acceso al micrófono en la configuración del sistema de iOS para que DuckDuckGo use las funciones de voz."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "De acuerdo"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Se requiere acceso al micrófono"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index f777ed9d3c..d5feec758b 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Tere tulemast\nDuckDuckGo kasutajaks!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo for Mac on just nii kiire, kui vajad, oodatud sirvimisfunktsioonidega ja tulvil meie oma klassi parimaid privaatsuselemente."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Lemmikud"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "DuckDuckGo'st"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Teave"; + /* about page */ "settings.about.text" = "DuckDuckGo on 2008. aastal asutatud sõltumatu internetiprivaatsuse ettevõte igaühele, kellel on kõrini varjatud jälgimisest internetis ja kes soovib lihtsat lahendust selle vältimiseks. Me oleme tõestuseks, et internetis on võimalik saada tõelist privaatsuskaitset ilma kompromisse tegemata.\n\nDuckDuckGo brauseril on kõik funktsioonid, mida sa brauserilt ootad, nagu järjehoidjad, vahekaardid, paroolid ja palju muud, lisaks sellele aga ka üle [tosina võimsa privaatsuskaitse funktsiooni](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), mida enamik populaarsemaid brausereid vaikimisi ei paku. See ainulaadselt terviklik privaatsuskaitse aitab kaitsta sinu tegevust internetis alates otsingutest kuni sirvimiseni, meilide saatmiseni ja palju muuni.\n\nMeie privaatsuskaitse toimib ilma, et peaksid teadma midagi tehnilistest nüanssidest või tegelema keeruliste seadistustega. Ainus, mis sa pead tegema, on vahetada oma brauser kõigis seadmetes DuckDuckGo vastu – ja vaikeprivaatsus ongi tagatud.\n\nKui aga *soovid* piiluda kapoti alla, leiate lisateavet DuckDuckGo privaatsuskaitse tööpõhimõtete kohta meie [abilehtedelt](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Lisa rakendus oma dokki"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Lisa vidin avakuvale"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Aadressiriba asukoht"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Välimus"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Ava lingid seotud rakendustes"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Keela, et takistada linkide automaatset avanemist teistes installitud rakendustes."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Automaatselt täidetavad ettepanekud"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Rakenduse lukk"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Kustuta andmed automaatselt"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Kustuta andmed automaatselt"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Halda küpsiste hüpikaknaid"; + +/* Settings title for the customize section */ +"settings.customize" = "Kohanda"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Määrake vaikebrauseriks"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-posti kaitse"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokeeri meilijälgurid ja peida oma aadress"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Jaga tagasisidet"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Tulenupu animatsioon"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Tulekindlad veebisaidid"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Üleilmne privaatsuskontroll (Global Privacy Control (GPC))"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Rakenduse ikoon"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Klaviatuur"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Sisselogimisandmed"; + +/* Settings title for the 'More' section */ +"settings.more" = "Veel DuckDuckGo'lt"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Pika vajutusega eelvaated"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privaatsus"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sünkroonimine ja varundamine"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Teksti suurus"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Teema"; + +/* Title for the Settings View */ +"settings.title" = "Seaded"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Kaitseta saidid"; + +/* Settings cell for Version */ +"settings.version" = "Versioon"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privaatne häälotsing"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Saada aruanne"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Häälfunktsioonide kasutamiseks lubage DuckDuckGo jaoks juurdepääs mikrofonile iOS-i süsteemiseadetes."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Vajalik juurdepääs mikrofonile"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index 626d914125..a20894d098 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Tervetuloa\nDuckDuckGo-sovellukseen!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "Macin DuckDuckGo tarjoaa kaipaamasi nopeuden, odottamasi selaintoiminnot ja luokkansa parhaat tietosuojaominaisuudet."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Suosikit"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Tietoa DuckDuckGo:sta"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Tietoja"; + /* about page */ "settings.about.text" = "DuckDuckGo on vuonna 2008 perustettu riippumaton internetissä tietosuojaa tarjoava yritys niille, jotka ovat kyllästyneet siihen, että heitä seurataan netissä, ja haluavat yksinkertaisen ratkaisun. Olemme todiste siitä, että voit saada todellista tietosuojaa verkossa ilman kompromisseja.\n\nDuckDuckGo-selaimessa on kaikki parhaiden selainten ominaisuudet, kuten kirjanmerkit, välilehdet, salasanat ja paljon muuta, sekä yli [kymmenkunta tehokasta tietosuojausta](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) ei tarjolla oletuksena suosituimmissa selaimissa. Tämä ainutlaatuisen kattava tietosuoja auttaa turvaamaan haut, selaamisen, sähköpostin lähettämiseen ja muun toimintasi verkossa.\n\nTietosuojamme toimivat, vaikka et tietäisi mitään teknisistä yksityiskohdista tai osaisi käsitellä monimutkaisia asetuksia. Sinun tarvitsee vain vaihtaa selaimesi DuckDuckGohon kaikilla laitteillasi, ja saat tietosuojan oletuksena.\n\nJos kuitenkin haluat kurkistaa konepellin alle, lisätietoja DuckDuckGon tietosuojan toiminnasta on [ohjesivuillamme](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Lisää sovellus telakkaasi"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Lisää widget aloitusnäyttöön"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Osoitepalkin sijainti"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Ulkoasu"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Avaa linkit liittyvissä sovelluksissa"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Ota pois käytöstä estääksesi linkkejä avautumasta automaattisesti muissa asennetuissa sovelluksissa."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Näytä automaattisen täydennyksen ehdotukset"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Sovelluksen lukitus"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Tyhjennä tiedot automaattisesti"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Tyhjennä tiedot automaattisesti"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Evästeiden hallinnan ponnahdusikkunat"; + +/* Settings title for the customize section */ +"settings.customize" = "Mukauta"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Aseta oletusselaimeksi"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Sähköpostisuojaus"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Estä sähköpostiseuraajat ja piilota osoitteesi"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Jaa palaute"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Liekki-painikkeen animaatio"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Tee sivustoista palonkestäviä"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Sovelluskuvake"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Näppäimistö"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Kirjautumistiedot"; + +/* Settings title for the 'More' section */ +"settings.more" = "Lisää DuckDuckGolta"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Esikatselu pitkällä painalluksella"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Tietosuoja"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synkronoi ja varmuuskopioi"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Tekstin koko"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Teema"; + +/* Title for the Settings View */ +"settings.title" = "Asetukset"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Suojaamattomat sivustot"; + +/* Settings cell for Version */ +"settings.version" = "Versio"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Yksityinen äänihaku"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Lähetä raportti"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Salli mikrofonin käyttö DuckDuckGolle iOS-järjestelmäasetuksissa, jotta voit käyttää ääniominaisuuksia."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofonin käyttöoikeus vaaditaan"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 2b4df59dc5..7ce5b93429 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Bienvenue sur\nDuckDuckGo !"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo pour Mac répond à vos besoins de rapidité et de fonctionnalités de navigation, et dispose des meilleurs outils de leur catégorie pour protéger votre vie privée."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoris"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "À propos de DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "À propos"; + /* about page */ "settings.about.text" = "DuckDuckGo est une société indépendante de confidentialité sur internet fondée en 2008, destinée à tous ceux qui en ont assez d'être suivis en ligne et qui veulent une solution simple. Nous sommes la preuve que vous pouvez bénéficier d'une véritable protection de la confidentialité en ligne, sans avoir à faire de compromis.\n\nLe navigateur DuckDuckGo est doté des fonctionnalités que vous attendez d'un navigateur de référence, comme les signets, les onglets et les mots de passe, entre autres, auxquels s'ajoutent plus d'[une douzaine de protections puissantes de la confidentialité](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) que la plupart des navigateurs populaires ne proposent pas par défaut. Cet ensemble unique et complet de protections de la confidentialité permet de préserver vos activités en ligne, de la recherche à la navigation, en passant par l'envoi d'e-mails, etc.\n\nInutile de s'y connaître en détails techniques ou de gérer des paramètres complexes pour faire fonctionner nos protections de la confidentialité. Il vous suffit d'opter pour le navigateur DuckDuckGo sur tous vos appareils afin de pouvoir bénéficier de la confidentialité par défaut.\n\nMais si vous voulez vous faire une idée, vous trouverez plus d'informations sur le fonctionnement des protections de la confidentialité DuckDuckGo sur nos [pages d'aide](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Ajouter l'application à votre Dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Ajouter le widget à l'écran d'accueil"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Position de la barre d'adresse"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Apparence"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Ouvrir les liens dans les applications associées"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Désactiver pour empêcher l'ouverture automatique des liens dans d'autres applications installées."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Afficher les suggestions de saisie semi-automatique"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Verrouillage de l'application"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Effacer automatiquement les données"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Effacer automatiquement les données"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Gérer les fenêtres contextuelles (cookies)"; + +/* Settings title for the customize section */ +"settings.customize" = "Personnaliser"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Définir comme navigateur par défaut"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Protection des e-mails"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Bloquez les traqueurs d'e-mails et masquez votre adresse"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Partagez vos commentaires"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animation du bouton en forme de flamme"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Sites coupe-feu"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Icône de l'appli"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Clavier"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Identifiants"; + +/* Settings title for the 'More' section */ +"settings.more" = "Plus de la part de DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Aperçus obtenus en appuyant longuement"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Confidentialité"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synchronisation et sauvegarde"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Taille du texte"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Thème"; + +/* Title for the Settings View */ +"settings.title" = "Paramètres"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Sites non protégés"; + +/* Settings cell for Version */ +"settings.version" = "Version"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Recherche vocale privée"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Envoyer le rapport"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Veuillez autoriser l'accès au microphone dans les réglages système iOS pour que DuckDuckGo puisse utiliser les fonctionnalités vocales."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Accès au microphone requis"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 442bb1fb10..fd0584e941 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Dobro došao/la u\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "Aplikacija DuckDuckGo za Mac pruža ti potrebnu brzinu i značajke pretraživanja kakve očekuješ, a isporučuje se s našim najboljim alatima za privatnost u svojoj klasi - privacy essentials."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Omiljeno"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "O DuckDuckGou"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "O nama"; + /* about page */ "settings.about.text" = "DuckDuckGo neovisna je tvrtka za privatnost na internetu osnovana 2008. godine. Namijenjena je svakome tko je umoran od toga da ga prate na internetu i želi jednostavno rješenje za to. Mi smo dokaz da je prava zaštita privatnosti na internetu moguća bez kompromisa.\n\nPreglednik DuckDuckGo dolazi sa značajkama koje očekujete od preglednika, kao što su oznake, kartice, lozinke i još mnogo toga, plus više od [desetak moćnih zaštita privatnosti](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) nije ponuđeno u većini popularnih preglednika prema zadanim postavkama. Ovaj jedinstveno sveobuhvatan skup zaštita privatnosti pomaže u zaštiti tvojih mrežnih aktivnosti, od pretraživanja do pregledavanja, slanja e-pošte i još mnogo toga.\n\nNaša zaštita privatnosti funkcionira bez potrebe da znamo bilo što o tehničkim detaljima ili da se bavimo kompliciranim postavkama. Sve što trebaš učiniti jest prebaciti svoj preglednik na DuckDuckGo na svim svojim uređajima i prema zadanim postavkama dobivaš našu zaštitu privatnosti.\n\nAli ako *želite* zaviriti \"ispod haube\", više o tome kako DuckDuckGo zaštita privatnosti funkcionira možeš pronaći na našim [stranicama pomoći](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Dodajte aplikaciju na Dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Dodajte widget na početni zaslon"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Položaj adresne trake"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Izgled"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Otvori veze u povezanim aplikacijama"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Onemogući da spriječiš automatsko otvaranje poveznica u drugim instaliranim aplikacijama."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Prikaži prijedloge za automatsko ispunjavanje"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Zaključavanje aplikacije"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Automatsko brisanje podataka"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Automatsko brisanje podataka"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Upravljanje skočnim prozorima kolačića"; + +/* Settings title for the customize section */ +"settings.customize" = "Prilagodba"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Postavi kao zadani preglednik"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Zaštita e-pošte"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokiraj programe za praćenje e-pošte i sakrij svoju adresu"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Podijeli povratne informacije"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animacija gumba Vatre"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Zaštićena web-mjesta"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Globalna kontrola privatnosti (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Ikona aplikacije"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tipkovnica"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Prijave"; + +/* Settings title for the 'More' section */ +"settings.more" = "Više od DuckDuckGoa"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Pretpregledi na dugi pritisak"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Zaštita privatnosti"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sinkronizacija i sigurnosno kopiranje"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Veličina teksta"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Postavke"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Nezaštićena web-mjesta"; + +/* Settings cell for Version */ +"settings.version" = "Verzija"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privatno glasovno pretraživanje"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Pošalji izvješće"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Dopustite pristup mikrofonu u postavkama sustava iOS kako bi DuckDuckGo mogao koristiti glasovne funkcije."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "U redu"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Potreban je pristup mikrofonu"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 7fb08d0a2e..289a0808d9 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Üdvözlünk a\nDuckDuckGo-ban!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "A DuckDuckGo Mac verziója gyors, böngészési funkciókban gazdag, és kategóriája legjobb adatvédelmi megoldásait kínálja."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Kedvencek"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "A DuckDuckGóról"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Rólunk"; + /* about page */ "settings.about.text" = "A 2008-ban alapított független internetes adatvédelmi cég, a DuckDuckGo egyszerű megoldást kínál azoknak, akiknek elegük van abból, hogy állandóan nyomon követik őket az interneten. Mi vagyunk annak a bizonyítéka, hogy kompromisszumok nélkül is lehet valódi online adatvédelmet biztosítani.\n\nA DuckDuckGo böngésző rendelkezik a böngészőktől elvárt funkciókkal, mint például könyvjelzők, lapok, jelszavak és egyéb funkciók, valamint több mint [egy tucat olyan hatékony adatvédelmi megoldással](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), amelyet a legtöbb népszerű böngésző nem kínál alapértelmezés szerint. Ezek az egyedülállóan átfogó adatvédelmi megoldások segítenek megvédeni online tevékenységeid, legyen szó keresésről, böngészésről, e-mailezésről vagy sok minden másról.\n\nAdatvédelmi megoldásaink anélkül működnek, hogy bármit is tudnod kellene a technikai részletekről vagy bonyolult beállításokkal kellene foglalkoznod. Mindössze annyit kell tenned, hogy minden eszközödön a DuckDuckGo böngészőre váltasz, és máris igénybe veheted az alapértelmezés szerinti adatvédelmet.\n\nHa azonban *igazán* szeretnél bepillantani a motorháztető alá is, a [súgóoldalainkon](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/) további információkat találhatsz a DuckDuckGo adatvédelmi megoldásainak működéséről."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Alkalmazás hozzáadása a dokkodhoz"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Minialkalmazás hozzáadása a kezdőképernyőhöz"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Címsor elhelyezkedése"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Megjelenés"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Hivatkozások megnyitása társított alkalmazásokban"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Letiltásával megakadályozhatod, hogy a hivatkozások automatikusan megnyíljanak más telepített alkalmazásokban."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Automatikus kiegészítési javaslatok megjelenítése"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Alkalmazás zárolás"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Adatok automatikus törlése"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Adatok automatikus törlése"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Felugró sütiablakok kezelése"; + +/* Settings title for the customize section */ +"settings.customize" = "Testreszabás"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Beállítás alapértelmezett böngészőként"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-mail védelem"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "E-mail nyomkövetők letiltása, és a cím elrejtése"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Visszajelzés megosztása"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Tűz gomb animáció"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Tűzálló weboldalak"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Nemzetközi adatvédelmi szabályozás (Global Privacy Control, GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Alkalmazásikon"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Billentyűzet"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Bejelentkezések"; + +/* Settings title for the 'More' section */ +"settings.more" = "Továbbiak a DuckDuckGo-tól"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Hosszú lenyomásos előnézetek"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Adatvédelem"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Szinkronizálás és biztonsági mentés"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Szövegméret"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Kinézet"; + +/* Title for the Settings View */ +"settings.title" = "Beállítások"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Védelem nélküli weboldalak"; + +/* Settings cell for Version */ +"settings.version" = "Változat"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privát beszédhangalapú keresés"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Jelentés beküldése"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "A beszédhangfunkciók használatához az iOS rendszerbeállításaiban engedélyezd a DuckDuckGo számára a Mikrofon-hozzáférést."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofon-hozzáférés szükséges"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index 327788f150..81eb7f6111 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "DuckDuckGo\nti dà il benvenuto!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo per Mac ti offre la velocità di cui hai bisogno, le funzioni di navigazione che desideri e i migliori strumenti per la privacy."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Preferiti"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Info su DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Informazioni"; + /* about page */ "settings.about.text" = "Fondata nel 2008, DuckDuckGo è un'azienda indipendente che tutela la privacy online per tutti coloro che sono stanchi di essere tracciati online e vogliono una soluzione semplice. Siamo la dimostrazione che si può garantire una vera protezione della privacy online senza compromessi.\n\nIl browser DuckDuckGo è ricco di funzioni essenziali come i segnalibri, le schede, le password e molto altro ancora, [oltre a numerose e potenti protezioni della privacy](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) che normalmente non vengono offerte dai browser più comuni. Stiamo parlando di uno straordinario set completo per la protezione della privacy, che aiuta a proteggere le attività online, dalla ricerca alla navigazione, alle e-mail e molto altro ancora.\n\nCon le nostre protezioni della privacy non è necessario conoscere particolari tecnici o dover affrontare configurazioni complesse. È sufficiente scegliere il browser DuckDuckGo su tutti i dispositivi personali per avere automaticamente garantita la privacy.\n\nPer saperne di più su come funzionano le protezioni della privacy di DuckDuckGo, è possibile consultare le nostre [pagine di aiuto](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Aggiungi app al tuo dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Aggiungi widget alla schermata iniziale"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Posizione Barra degli indirizzi"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Aspetto"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Apri i collegamenti nelle loro app associate"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Disattiva per impedire che i collegamenti si aprano automaticamente in altre app installate."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Mostra suggerimenti di completamento automatico"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Blocco applicazione"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Cancellazione automatica dei dati"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Cancellazione automatica dei dati"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Gestione popup dei cookie"; + +/* Settings title for the customize section */ +"settings.customize" = "Personalizza"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Imposta come browser predefinito"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Protezione email"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blocca i sistemi di tracciamento delle email e nascondi il tuo indirizzo"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Condividi feedback"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animazione pulsante fuoco"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Siti protetti"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Icona dell'app"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tastiera"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Dati di accesso"; + +/* Settings title for the 'More' section */ +"settings.more" = "Ulteriori informazioni su DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Anteprime con pressione prolungata"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privacy"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sincronizzazione e backup"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Dimensione testo"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Impostazioni"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Siti non protetti"; + +/* Settings cell for Version */ +"settings.version" = "Versione"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Ricerca vocale privata"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Invia segnalazione"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Consenti l'accesso al microfono nelle impostazioni di sistema di iOS, in modo che DuckDuckGo possa utilizzare le funzioni vocali."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Accesso al microfono obbligatorio"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 716ca0ad61..87425c6d70 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Sveiki atvykę į\n„DuckDuckGo“!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "„Mac“ skirta „DuckDuckGo“ pasižymi jums reikiama sparta, naršymo funkcijomis ir aukščiausios klasės privatumo įrankiais."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Mėgstami"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Apie DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Apie"; + /* about page */ "settings.about.text" = "„DuckDuckGo“ yra nepriklausoma interneto privatumo bendrovė, įkurta 2008 metais ir skirta visiems, kurie pavargo nuo sekimo internete ir nori lengvo sprendimo. Esame įrodymas, kad galite gauti tikrą privatumo apsaugą internete be kompromisų.\n\n„DuckDuckGo“ naršyklėje yra funkcijos, kurių tikitės iš pagrindinės naršyklės, pvz., žymės, skirtukai, slaptažodžiai ir dar daugiau, taip pat [keliolika galingų privatumo apsaugos priemonių](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) pagal numatytuosius nustatymus nėra siūlomas daugelyje populiarių naršyklių. Šis unikalus išsamus privatumo apsaugos rinkinys padeda apsaugoti jūsų veiklą internete – nuo paieškos iki naršymo, el. pašto ir kt.\n\nMūsų privatumo apsaugos priemonės veikia nereikalaudamos nieko žinoti apie technines detales ar naudotis sudėtingais nustatymais. Tereikia visuose įrenginiuose perjungti naršyklę į „DuckDuckGo“ ir privatumas bus užtikrintas pagal numatytuosius nustatymus.\n\nTačiau jei norite žvilgtelėti po gaubtu, daugiau informacijos apie tai, kaip veikia „DuckDuckGo“ privatumo apsauga, galite rasti mūsų [pagalbos puslapiuose] (ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Įtraukti programėlę į stotelę"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Įtraukti valdiklį į pagrindinį ekraną"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Adreso juostos padėtis"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Išvaizda"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Atidaryti nuorodas susijusiose programose"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Išjungti, kad nuorodos automatiškai neatsidarytų kitose įdiegtose programose."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Rodyti automatinio užbaigimo pasiūlymus"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Programos užraktas"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Automatiškai valyti duomenis"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Automatiškai valyti duomenis"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Valdyti slapukų iššokančiuosius langus"; + +/* Settings title for the customize section */ +"settings.customize" = "Tinkinti"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Nustatyti kaip numatytąją naršyklę"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "El. pašto apsauga"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokuokite el. laiškų sekimo priemones ir paslėpkite savo adresą"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Bendrinti atsiliepimą"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Mygtuko „Fire“ animacija"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Apsaugoti svetaines"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Visuotinė privatumo kontrolė (VPK)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Programėlės piktograma"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Klaviatūra"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Prisijungimai"; + +/* Settings title for the 'More' section */ +"settings.more" = "Daugiau iš „DuckDuckGo“"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Peržiūros ilgai spaudžiant"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privatumas"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sinchronizuoti ir kurti atsarginę kopiją"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Teksto dydis"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Nustatymai"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Neapsaugotos svetainės"; + +/* Settings cell for Version */ +"settings.version" = "Versija"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privati paieška balsu"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Pateikti ataskaitą"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Patvirtinkite prieigą prie mikrofono „iOS“ sistemos nustatymuose, kad galėtumėte naudotis „DuckDuckGo“ balsu valdomomis funkcijomis."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "GERAI"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Būtina prieiga prie mikrofono"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 09a7d5fa0a..3a1c3c0137 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Laipni lūdzam\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo Mac datoram sniedz nepieciešamo ātrumu, ierastās pārlūkošanas funkcijas un savā klasē labākās privātuma pamatfunkcijas."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Izlase"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Par DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Par"; + /* about page */ "settings.about.text" = "DuckDuckGo ir neatkarīgs 2008. gadā dibināts interneta privātuma uzņēmums ikvienam, kam ir apnikusi izsekošana tiešsaistē un kas vēlas vienkāršu risinājumu. Mēs esam pierādījums tam, ka tiešsaistē var iegūt reālu privātuma aizsardzību bez kompromisiem.\n\nDuckDuckGo pārlūks sniedz visas iespējas, ko gaidi no pārlūkprogrammas, piemēram, grāmatzīmes, cilnes, paroles un citas, kā arī vairāk nekā [duci spēcīgu privātuma aizsardzības funkciju](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) pēc noklusējuma netiek piedāvāts lielākajā daļā populārāko pārlūkprogrammu. Šis unikāli visaptverošais privātuma aizsardzības komplekts palīdz aizsargāt tavas darbības tiešsaistē, sākot no meklēšanas līdz pārlūkošanai, e-pasta sūtīšanai u.c.\n\nMūsu privātuma aizsardzība darbojas vienmēr – tev nav jāpārzina sarežģīti tehniskie aspekti vai iestatījumi. Tev vienkārši jālieto DuckDuckGo pārlūks visās savās ierīcēs, un privātums būs tavs standarta risinājums.\n\nBet, ja vēlies paskatīties, kas lācītim vēderā, vairāk informācijas par to, kā darbojas DuckDuckGo privātuma aizsardzība, vari atrast mūsu [palīdzības lapās](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Pievienot lietotni dokam"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Pievienot logrīku sākuma ekrānam"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Adreses joslas pozīcija"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Parādas"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Atvērt saites saistītajās lietotnēs"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Atspējo, lai novērstu saišu automātisku atvēršanu citās instalētajās lietotnēs."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Automātiski pabeigt ieteikumus"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Lietojumprogrammas bloķēšana"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Automātiski notīrīt datus"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Automātiski notīrīt datus"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Pārvaldīt sīkfailu uznirstošos logus"; + +/* Settings title for the customize section */ +"settings.customize" = "Pielāgošana"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Iestatīt kā noklusējuma pārlūku"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-pasta aizsardzība"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Bloķē e-pasta izsekotājus un paslēp savu adresi"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Kopīgot atsauksmi"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Uguns pogas animācija"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Ugunsdrošas vietnes"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Globālā privātuma kontrole (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Lietotnes ikona"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tastatūra"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Pieteikšanās dati"; + +/* Settings title for the 'More' section */ +"settings.more" = "Vairāk no DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Ilgas nospiešanas rezultātu priekšskatījumi"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privātums"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sinhronizācija un dublēšana"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Burtu izmērs"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Motīvs"; + +/* Title for the Settings View */ +"settings.title" = "Iestatījumi"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Neaizsargātas vietnes"; + +/* Settings cell for Version */ +"settings.version" = "Versija"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privātā balss meklēšana"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Iesniegt ziņojumu"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Lūdzu, atļauj piekļuvi mikrofonam iOS sistēmas iestatījumos, lai DuckDuckGo varētu izmantot balss funkcijas."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "Labi"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Nepieciešama piekļuve mikrofonam"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 29ea848984..407e416b85 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -874,6 +874,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internettet kan være ganske skummelt.\n\nMen ikke vær redd! Det er enklere enn du tror å søke og surfe privat."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Deaktivert"; @@ -1348,6 +1351,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Velkommen til\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo for Mac har hastigheten du trenger, nettleserfunksjonene du forventer, og er stappfull av våre førsteklasses personvernfunksjoner."; @@ -1411,18 +1417,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Personvernerklæring"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Jeg har en invitasjonskode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Kom i gang"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du står på listen!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Åpne invitasjonen din"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du står på listen!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Sett deg på den private ventelisten"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1453,7 +1546,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1489,24 +1582,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-varsler"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Gjenopprett automatisk VPN-tilkobling etter avbrudd."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Alltid på"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-varsler"; @@ -1517,10 +1619,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Nettverksbeskyttelse forhindrer DNS-lekkasjer til internettleverandøren din ved å rute DNS-forespørsler gjennom VPN-tunnelen til vår egen resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Sikker DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-innstillinger"; @@ -1648,9 +1747,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoritter"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Om DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Om"; + /* about page */ "settings.about.text" = "DuckDuckGo er et uavhengig personvernselskap som ble grunnlagt i 2008, for alle som er lei av å bli sporet på nettet og vil ha en enkel løsning. Vi er beviset på at ekte personvern er mulig på nettet uten å fire på kravene.\n\nDuckDuckGo-nettleseren har funksjoner du kan forvente i en vanlig nettleser, som bokmerker, faner, passord med mer, pluss over [en rekke kraftige personvernfunksjoner](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) ikke tilbys i de fleste populære nettlesere som standard. Disse usedvanlig omfattende personvernfunksjonene bidrar til å beskytte nettaktivitetene dine, fra søk til surfing, e-poster med mer.\n\nPersonvernfunksjonene fungerer uten at du behøver å kunne noe om de tekniske detaljene eller forholde deg til kompliserte innstillinger. Det eneste du behøver å gjøre, er å bytte nettleser til DuckDuckGo på alle enhetene dine, så får du personvern som standard.\n\nMen hvis du *vil* ta en titt under panseret, kan du finne mer informasjon om hvordan DuckDuckGos personvern fungerer på våre [hjelpesider](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Legg til appen i docken"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Legg til widgeten på startskjermen"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Plassering av adressefelt"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Utseende"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Åpne lenker i tilknyttede apper"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Deaktiver for å forhindre at lenker automatisk åpnes i andre installerte apper."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Vis forslag fra autofullføring"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Applås"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Slett data automatisk"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Slett data automatisk"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Administrer vinduer om informasjonskapsler"; + +/* Settings title for the customize section */ +"settings.customize" = "Tilpass"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Gjør til standardnettleser"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-postbeskyttelse"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokker e-postsporere og skjul adressen din"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Del tilbakemelding"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animasjon for brannknappen"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Brannsikre nettsteder"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Appikon"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tastatur"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Pålogginger"; + +/* Settings title for the 'More' section */ +"settings.more" = "Mer fra DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Forhåndsvisning ved å trykke og holde"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Personvern"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synkronisering og sikkerhetskopiering"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Tekststørrelse"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Utseende"; + +/* Title for the Settings View */ +"settings.title" = "Innstillinger"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Ubeskyttede nettsteder"; + +/* Settings cell for Version */ +"settings.version" = "Versjon"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privat talesøk"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Send inn rapport"; @@ -1735,6 +1936,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "For å bruke talefunksjoner må du gi DuckDuckGo mikrofontilgang i systeminnstillingene i iOS."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofontilgang kreves"; diff --git a/DuckDuckGo/nb.lproj/Localizable.stringsdict b/DuckDuckGo/nb.lproj/Localizable.stringsdict index 74bdeef709..6a12a317f3 100644 --- a/DuckDuckGo/nb.lproj/Localizable.stringsdict +++ b/DuckDuckGo/nb.lproj/Localizable.stringsdict @@ -2,6 +2,38 @@ + appTP.home.blockedCount + + NSStringLocalizedFormatKey + %#@forsøk@ + attempts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d sporingsforsøk + other + %d sporingsforsøk + + + appTP.trackingattempts + + NSStringLocalizedFormatKey + %#@forsøk@ + attempts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d sporingsforsøk + other + %d sporingsforsøk + + bookmark.deleteFolderAlert.message NSStringLocalizedFormatKey @@ -13,9 +45,9 @@ NSStringFormatValueTypeKey d one - Er du sikker på at du vil slette denne mappen og %1$d element? + Er du sikker på at du vil slette denne mappen og %d element? other - Er du sikker på at du vil slette denne mappen og %1$d elementer? + Er du sikker på at du vil slette denne mappen og %d elementer? dax.onboarding.browsing.multiple.trackers @@ -29,36 +61,44 @@ NSStringFormatValueTypeKey d one - *%2$@, %3$@* og *1 annen* prøvde å spore deg her. + *%2$@, %3$@* og *1 annen* prøvde å spore deg her. -Jeg blokkerte dem! +Jeg har blokkert dem! ☝️ Du kan sjekke adresselinjen for å se hvem som prøver å spore deg når du besøker et nytt nettsted.️ other - *%2$@, %3$@* og *%1$d andre* prøvde å spore deg her. + *%2$@, %3$@* og *%d andre* prøvde å spore deg her. + +Jeg har blokkert dem! + +☝️ Du kan sjekke adresselinjen for å se hvem som prøver å spore deg når du besøker et nytt nettsted.️ + zero + *%2$@ og %3$@* prøvde å spore deg her. -Jeg blokkerte dem! +Jeg har blokkert dem! ☝️ Du kan sjekke adresselinjen for å se hvem som prøver å spore deg når du besøker et nytt nettsted.️ - privacy.protection.major.trackers.found + number.of.tabs NSStringLocalizedFormatKey - %#@networks@ - networks + %#@tabs@ + tabs NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey d one - %1$d større sporer funnet + %d privat fane other - %1$d større sporere funnet + %d private faner + zero + Ingen private faner - privacy.protection.major.trackers.blocked + privacy.protection.first.party.trackers.loaded NSStringLocalizedFormatKey %#@networks@ @@ -69,12 +109,12 @@ Jeg blokkerte dem! NSStringFormatValueTypeKey d one - %1$d større sporer er blokkert + %d Tracker Owned by This Site (Loaded) other - %1$d større sporere er blokkert + %d Trackers Owned by This Site (Loaded) - privacy.protection.trackers.found + privacy.protection.major.trackers.blocked NSStringLocalizedFormatKey %#@networks@ @@ -85,12 +125,12 @@ Jeg blokkerte dem! NSStringFormatValueTypeKey d one - %1$d sporer funnet + %d større sporer blokkert other - %1$d sporere funnet + %d større sporere blokkert - privacy.protection.trackers.blocked + privacy.protection.major.trackers.found NSStringLocalizedFormatKey %#@networks@ @@ -101,57 +141,57 @@ Jeg blokkerte dem! NSStringFormatValueTypeKey d one - %1$d sporing blokkert + %d større sporer funnet other - %1$d sporinger blokkert + %d større sporere funnet - number.of.tabs + privacy.protection.other.domains.loaded NSStringLocalizedFormatKey - %#@tabs@ - tabs + %#@networks@ + networks NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey d one - %1$d privat fane + %d Other Domain Loaded other - %1$d private faner + %d Other Domains Loaded - appTP.home.blockedCount + privacy.protection.trackers.blocked NSStringLocalizedFormatKey - %#@attempts@ - attempts + %#@networks@ + networks NSStringFormatSpecTypeKey NSStringPluralRuleType - other - %d tracking attempts - one - %d tracking attempt NSStringFormatValueTypeKey d + one + %d Tracker Blocked from Loading + other + %d Trackers Blocked from Loading - appTP.trackingattempts + privacy.protection.trackers.found NSStringLocalizedFormatKey - %#@attempts@ - attempts + %#@networks@ + networks NSStringFormatSpecTypeKey NSStringPluralRuleType - other - %d tracking attempts - one - %d tracking attempt NSStringFormatValueTypeKey d + one + %d sporer funnet + other + %d sporere funnet diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 0d8da8c2e0..8bdb8046e0 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Welkom bij\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo voor Mac heeft de snelheid die je nodig hebt en de browsefuncties die je verwacht, en zit boordevol met onze allerbeste privacy essentials."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favorieten"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Over DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Over"; + /* about page */ "settings.about.text" = "DuckDuckGo is het onafhankelijke internetprivacybedrijf, opgericht in 2008, voor iedereen die het beu is om online gevolgd te worden en daar een eenvoudige oplossing voor wil. Wij zijn het bewijs dat je privacy op internet écht beschermd kan worden zonder compromissen.\n\nDe DuckDuckGo-browser wordt geleverd met de functies die je van een webbrowser verwacht, zoals bladwijzers, tabbladen, wachtwoorden en meer, plus meer dan [een dozijn krachtige tools om je privacy te beschermen](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) die niet standaard worden aangeboden in de meeste populaire browsers. Deze unieke, uitgebreide privacybescherming helpt je online activiteiten te beschermen – of je nu zoekt, surft of mailt.\n\nJe hoeft niets te weten over de technische details of ingewikkelde instellingen om te profiteren van onze privacybescherming. Je hoeft alleen maar DuckDuckGo op al je apparaten te installeren en geniet dan standaard van privacy.\n\nMaar als je *een kijkje onder de motorkap* wilt, vind je meer informatie over de privacybescherming van DuckDuckGo op onze [hulppagina's](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "App toevoegen aan je dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Widget toevoegen aan startscherm"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Positie van adresbalk"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Uiterlijk"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Links openen in bijbehorende apps"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Uitschakelen om te voorkomen dat links automatisch geopend worden in andere geïnstalleerde apps."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Suggesties voor automatisch aanvullen weergeven"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "App-vergrendeling"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Gegevens automatisch wissen"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Gegevens automatisch wissen"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Cookiepop-ups beheren"; + +/* Settings title for the customize section */ +"settings.customize" = "Aanpassen"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Instellen als standaardbrowser"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-mailbescherming"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokkeer e-mailtrackers en verberg je adres"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Feedback delen"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animatie vuurknop"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Brandveilige websites"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "App-pictogram"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Toetsenbord"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Aanmeldgegevens"; + +/* Settings title for the 'More' section */ +"settings.more" = "Meer over DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Voorbeeldweergave bij lang indrukken"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privacy"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synchronisatie en back-up"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Lettergrootte"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Thema"; + +/* Title for the Settings View */ +"settings.title" = "Instellingen"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Onbeschermde sites"; + +/* Settings cell for Version */ +"settings.version" = "Versie"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Private spraakzoekopdracht"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Rapport versturen"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Sta microfoontoegang toe in de iOS-systeeminstellingen zodat DuckDuckGo spraakfuncties kan gebruiken."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Toegang tot microfoon vereist"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index f283b6e277..4d771ab66d 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Witamy\nw DuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "Aplikacja DuckDuckGo dla komputerów Mac zapewnia szybkość, której potrzebujesz, i niezbędne funkcje przeglądania. Ponadto jest wyposażona w najlepsze w swojej klasie narzędzia do ochrony prywatności."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Ulubione"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "O DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Informacje"; + /* about page */ "settings.about.text" = "DuckDuckGo to założona w 2008 roku niezależna firma zajmująca się prywatnością w Internecie dla osób, które mają dosyć śledzenia online i poszukują łatwego w obsłudze rozwiązania. Jesteśmy dowodem na to, że możesz uzyskać prawdziwą ochronę prywatności online bez kompromisów.\n\nPrzeglądarka DuckDuckGo jest wyposażona w funkcje, których potrzebujesz, takie jak zakładki, karty, hasła i inne, a także [kilkanaście zaawansowanych mechanizmów ochrony prywatności](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), które nie są domyślnie oferowane w większości popularnych przeglądarek. Ten wyjątkowo zaawansowany zestaw mechanizmów ochrony prywatności pomaga chronić działania w Internecie, od wyszukiwania po przeglądanie, obsługę wiadomości e-mail i nie tylko.\n\nDo korzystania z mechanizmów ochrony prywatności nie jest wymagana znajomość szczegółów technicznych ani skomplikowane ustawienia. Aby uzyskać ochronę prywatności, wystarczy ustawić na wszystkich urządzeniach domyślną przeglądarkę DuckDuckGo.\n\nJeśli interesują Cię szczegóły techniczne, więcej informacji o działaniu mechanizmów ochrony prywatności DuckDuckGo można znaleźć na [stronach pomocy](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Dodaj aplikację do Docka"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Dodaj widżet do ekranu głównego"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Pozycja paska adresu"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Wygląd"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Otwieraj łącza w powiązanych aplikacjach"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Wyłącz, aby uniemożliwić automatyczne otwieranie łączy w innych zainstalowanych aplikacjach."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Pokaż sugestie autouzupełniania"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Blokada aplikacji"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Automatyczne czyszczenie danych"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Automatyczne czyszczenie danych"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Zarządzaj okienkami z prośbą o zgodę"; + +/* Settings title for the customize section */ +"settings.customize" = "Spersonalizuj"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Ustaw jako domyślną przeglądarkę"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Ochrona poczty e-mail"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Zablokuj skrypty śledzące pocztę e-mail i ukryj swój adres"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Podziel się opinią"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animacja przycisku ognia"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Bezpieczne witryny internetowe"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Globalna Kontrola Prywatności (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Ikona aplikacji"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Klawiatura"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Dane logowania"; + +/* Settings title for the 'More' section */ +"settings.more" = "Więcej możliwości DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Podglądy po dłuższym przyciśnięciu"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Prywatność"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synchronizacja i kopia zapasowa"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Rozmiar tekstu"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Motyw"; + +/* Title for the Settings View */ +"settings.title" = "Ustawienia"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Niezabezpieczone witryny"; + +/* Settings cell for Version */ +"settings.version" = "Wersja"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Prywatne wyszukiwanie głosowe"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Prześlij raport"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Aby korzystać z funkcji głosowych, zezwól na dostęp DuckDuckGo do mikrofonu w ustawieniach systemu iOS."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Wymagany dostęp do mikrofonu"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 8aa6300773..e25e0f3044 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Damos-lhe as boas-vindas ao\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "O DuckDuckGo para Mac tem a velocidade de que necessita, as funcionalidades de navegação que espera e os nossos Privacy Essentials líderes no setor."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoritos"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Sobre o DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Acerca de"; + /* about page */ "settings.about.text" = "A DuckDuckGo é a empresa independente de privacidade na Internet fundada em 2008 para quem está cansado de ser rastreado online e quer uma solução simples. Somos a prova de que podes proteger a tua privacidade online sem compromissos.\n\nO navegador DuckDuckGo vem com as funcionalidades que esperas num navegador de referência, como marcadores, separadores, palavras-passe, entre outras, além de mais de [uma dúzia de proteções de privacidade poderosas](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) que os navegadores mais populares não oferecem por predefinição. Este conjunto especialmente abrangente de proteções de privacidade ajuda a proteger as tuas atividades online, desde a pesquisa à navegação, e-mails, entre outras.\n\nPara utilizares as nossas proteções de privacidade, não tens de saber nada sobre os detalhes técnicos nem lidar com definições complicadas. Tudo o que tens de fazer é mudar o teu navegador para o DuckDuckGo em todos os teus dispositivos e desfrutar de privacidade como predefinição.\n\nMas *se* quiseres espreitar os bastidores, podes consultar mais informações sobre como funcionam as proteções de privacidade do DuckDuckGo nas nossas [páginas de ajuda](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Adicionar aplicação à sua dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Adicionar widget ao ecrã inicial"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Posição da barra de endereços"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Aparência"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Abrir links nas aplicações associadas"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Desative para prevenir que ligações sejam abertas automaticamente noutras aplicações instaladas."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Mostrar sugestões de preenchimento automático"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Bloqueio de aplicações"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Limpar os dados automaticamente"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Limpar os dados automaticamente"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Gerir pop-ups de cookies"; + +/* Settings title for the customize section */ +"settings.customize" = "Personalizar"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Definir como navegador padrão"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Proteção de e-mail"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Bloqueie rastreadores de e-mail e oculte o seu endereço."; + +/* Settings cell for Feedback */ +"settings.feedback" = "Partilhar comentários"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animação do Botão de Fogo"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Sites com barreira de segurança"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Controlo Global de Privacidade (CGP)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Ícone da aplicação"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Teclado"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Inícios de sessão"; + +/* Settings title for the 'More' section */ +"settings.more" = "Mais de DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Pré-visualizações ao premir prolongadamente"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Privacidade"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sincronização e cópia de segurança"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Tamanho do texto"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Definições"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Sites desprotegidos"; + +/* Settings cell for Version */ +"settings.version" = "Versão"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Pesquisa por voz privada"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Submeter relatório"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Permita o acesso ao microfone nas definições do sistema iOS para que o DuckDuckGo use as funcionalidades de voz."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "É necessário o acesso ao microfone"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 2dcbdc6a07..bfb0bbf1df 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Bine ai venit la\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo pentru Mac are viteza de care ai nevoie, funcționalitățile de răsfoire pe care ți le dorești și este prevăzută cu cele mai bune caracteristici de bază în privința confidențialității din domeniu."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Preferințe"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Despre DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Despre"; + /* about page */ "settings.about.text" = "DuckDuckGo este o companie independentă înființată în 2008, care oferă confidențialitate pe internet tuturor celor care s-au săturat să fie urmăriți online și își doresc o soluție simplă. Suntem dovada că protecția confidențialității online fără compromisuri este reală.\n\nBrowserul DuckDuckGo oferă funcțiile pe care le aștepți de la browserul predilect, cum ar fi marcajele, filele, parolele și multe altele, plus peste [o duzină de instrumente puternice de protecție a confidențialității](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) care nu sunt oferite implicit de majoritatea browserelor populare. Acest set unic și cuprinzător de măsuri de protecție a confidențialității te ajută să îți protejezi activitățile online, de la căutări la navigare, e-mailuri și multe altele.\n\nMăsurile noastre robuste de protecție a confidențialității funcționează fără a necesita cunoștințe tehnice din partea ta și fără a fi nevoie să faci setări complicate. Tot ce trebuie să faci este să folosești browserul DuckDuckGo pe toate dispozitivele și vei beneficia implicit de confidențialitate.\n\nDar dacă *chiar dorești* să consulți detaliile mai tehnice, poți găsi mai multe informații despre modul în care funcționează instrumentele DuckDuckGo de protecție a confidențialității în [paginile noastre de asistență](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Adaugă aplicația la secțiunea ta fixă"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Adaugă un widget la ecranul de întâmpinare"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Poziția barei de adrese"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Aspect"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Deschide linkuri în aplicațiile asociate"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Dezactivează pentru a preveni deschiderea automată a linkurilor în alte aplicații instalate."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Afișează sugestiile de completare automată"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Blocarea aplicației"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Șterge automat datele"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Șterge automat datele"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Gestionare pop-up-uri pentru module cookie"; + +/* Settings title for the customize section */ +"settings.customize" = "Personalizare"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Setare ca browser implicit"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Protecția comunicațiilor prin e-mail"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blochează tehnologiile de urmărire prin e-mail și ascunde-ți adresa"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Partajează feedback"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animație buton Foc"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Site-uri cu ștergerea activității și istoricului din browser la ieșire"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Pictograma aplicației"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tastatură"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Conectări"; + +/* Settings title for the 'More' section */ +"settings.more" = "Mai multe de la DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Previzualizări prin apăsare lungă"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Confidențialitate"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sincronizare și copiere de rezervă"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Dimensiune text"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Temă"; + +/* Title for the Settings View */ +"settings.title" = "Setări"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Site-uri neprotejate"; + +/* Settings cell for Version */ +"settings.version" = "Versiune"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Căutare vocală privată"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Trimite raportul"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Permite accesul la microfon din setările sistemului de operare iOS, pentru ca DuckDuckGo să folosească funcționalitățile vocale."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Este necesar accesul la microfon"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index c36a7b98e5..43b718f1c2 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Добро пожаловать в\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "Приложение DuckDuckGo для Mac — это высокая скорость, функциональность и беспрецедентная конфиденциальность."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Избранное"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Несколько слов о DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "О нас"; + /* about page */ "settings.about.text" = "DuckDuckGo — независимая компания, которая c 2008 года предоставляет услуги защиты личных данных в интернете. Мы предлагаем простое решение для тех, кому надоело отслеживание онлайн. Своим примером DuckDuckGo показывает, что защита конфиденциальности в Сети не требует компромиссов.\n\nВ универсальном браузере DuckDuckGo вы найдете все те же привычные функции — вкладки, закладки, пароли, — а также более [десятка мощных средств защиты личной информации](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/), которых нет в стандартных пакетах других популярных веб-обозревателей. Наш всесторонний набор инструментов скроет от посторонних глаз всю вашу онлайн-активность, включая поиск, просмотр сайтов и чтение почты.\n\nСервисы DuckDuckGo не требуют особых технических познаний или работы со сложными настройками. Достаточно сменить свой браузер на DuckDuckGo на всех устройствах, и конфиденциальность онлайн станет вашим спутником по умолчанию.\n\nЕсли же вас интересует наша «кухня», вы можете почитать о том, как устроены инструменты защиты DuckDuckGo, на [страницах нашего справочного центра](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Добавьте приложение на док-панель"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Добавьте виджет на домашний экран"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Положение адресной строки"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Внешний вид"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Открывать ссылки в связанных приложениях"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Отключение этой функции не позволит ссылкам автоматически открываться в других приложениях."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Показать предложения автозаполнения"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Блокировка"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Автоудаление данных"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Автоудаление данных"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Управление окнами выбора куки-файлов"; + +/* Settings title for the customize section */ +"settings.customize" = "Собственная настройка"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Сделать браузером по умолчанию"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Защита электронной почты"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Блокировка почтовых трекеров и скрытие адреса"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Оставьте нам отзыв"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Aнимация кнопки «Огонь»"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Огнеупорные сайты"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Глобальный контроль конфиденциальности (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Значок приложения"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Клавиатура"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Логины"; + +/* Settings title for the 'More' section */ +"settings.more" = "DuckDuckGo также предлагает..."; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Предпросмотр долгим нажатием"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Настройки конфиденциальности"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Синхронизация и резервное копирование"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Размер текста"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Тема"; + +/* Title for the Settings View */ +"settings.title" = "Настройки"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Незащищенные сайты"; + +/* Settings cell for Version */ +"settings.version" = "Версия"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Конфиденциальный голосовой поиск"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Отправить жалобу"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Чтобы пользоваться голосовыми функциями, откройте DuckDuckGo доступ к микрофону в системных настройках iOS."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "Хорошо"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Требуется доступ к микрофону"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index bfa9a50608..25e901ed93 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Vitajte v\nprehliadači DuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo pre Mac má rýchlosť, ktorú potrebujete, funkcie prehliadania, ktoré očakávate, a obsahuje naše prvotriedne privacy essentials."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Obľúbené položky"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "O službe DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "O nás"; + /* about page */ "settings.about.text" = "DuckDuckGo je nezávislá spoločnosť na ochranu súkromia na internete, ktorá bola založená v roku 2008 pre všetkých, ktorých už nebaví sledovanie na internete a chcú jednoduché riešenie. Sme dôkazom, že skutočnú online ochranu súkromia môžete získať bez kompromisov.\n\nPrehliadač DuckDuckGo je vybavený funkciami, ktoré od prehliadača očakávate, ako sú napríklad záložky, karty, heslá a ďalšie funkcie. Navyše obsahuje viac ako [tucet účinných ochranných opatrení na ochranu súkromia](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) sa vo väčšine populárnych prehliadačov štandardne neponúka. Tento jedinečne komplexný súbor ochrany súkromia pomáha chrániť vaše online aktivity od vyhľadávania po prehliadanie, posielanie e-mailov a ďalšie.\n\nNaša ochrana súkromia funguje bez toho, aby ste museli poznať technické detaily alebo riešiť zložité nastavenia. Stačí prepnúť prehliadač na DuckDuckGo vo všetkých zariadeniach a získate predvolené súkromie.\n\nAk však chcete nahliadnuť pod pokrievku, viac informácií o tom, ako funguje ochrana súkromia DuckDuckGo, nájdete na našich [stránkach pomoci](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Pridať aplikáciu do Docku"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Pridať miniaplikáciu na domovskú obrazovku"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Poloha riadku s adresou"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Vzhľad"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Otvárať odkazy v pridružených aplikáciách"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Zakážte a zabráňte tak automatickému otváraniu odkazov v iných nainštalovaných aplikáciách."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Zobraziť návrhy automatického dopĺňania"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Zámok aplikácie"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Automaticky vymazať údaje"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Automaticky vymazať údaje"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Správa kontextových okien súborov cookie"; + +/* Settings title for the customize section */ +"settings.customize" = "Prispôsobiť"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Nastaviť ako predvolený prehliadač"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Ochrana e-mailu"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Zablokujte nástroje na sledovanie e‑mailov a skryte vašu adresu"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Zdieľať spätnú väzbu"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animácia tlačidla ohňa"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Zabezpečené stránky"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Globálna kontrola súkromia (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Ikona aplikácie"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Klávesnica"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Prihlasovacie údaje"; + +/* Settings title for the 'More' section */ +"settings.more" = "Viac od DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Náhľady po dlhodobom stlačení"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Súkromie"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synchronizácia a zálohovanie"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Veľkosť textu"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Motív"; + +/* Title for the Settings View */ +"settings.title" = "Nastavenia"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Nezabezpečené webové stránky"; + +/* Settings cell for Version */ +"settings.version" = "Verzia"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Súkromné hlasové vyhľadávanie"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Odoslať správu"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Ak chcete používať hlasové funkcie, povoľte pre DuckDuckGo prístup k mikrofónu v nastaveniach systému iOS."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Vyžaduje sa prístup k mikrofónu"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index ccf5941d44..8f3e9e7d01 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Dobrodošli v aplikaciji\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo za računalnike Mac zagotavlja hitrost, ki jo potrebujete, in funkcije brskanja, ki jih pričakujete, ter je opremljen z našimi ključnimi funkcijami zasebnosti, ki so najboljše v tem razredu."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Priljubljeni"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "O DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Več o"; + /* about page */ "settings.about.text" = "DuckDuckGo je neodvisno podjetje za zasebnost v internetu, ustanovljeno leta 2008, in je namenjeno vsem, ki so naveličani sledenja v spletu in si želijo preproste rešitve. Dokazujemo, da je v spletu mogoče zagotoviti pravo zaščito zasebnosti brez sklepanja kompromisov.\n\nBrskalnik DuckDuckGo ima funkcije, ki jih pričakujete od priljubljenega brskalnika, kot so zaznamki, zavihki, gesla in drugo, ter več kot [ducat učinkovitih zaščit zasebnosti](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) ki v večini priljubljenih brskalnikov niso na voljo privzeto. Ta edinstveni celovit sklop zaščite zasebnosti pomaga zaščititi vaše spletne dejavnosti, od iskanja do brskanja, pošiljanja e-pošte in še več.\n\nNaša zaščita zasebnosti deluje, ne da bi se vam bilo treba spoznati na tehnične podrobnosti ali se ukvarjati z zapletenimi nastavitvami. Vse, kar morate storiti, je, da brskalnik preklopite na DuckDuckGo v vseh napravah in zasebnost bo privzeta.\n\nČe pa želite pogledati v mehanizem delovanja, lahko več informacij o delovanju zaščite zasebnosti DuckDuckGo najdete na naših [straneh s pomočjo](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Dodajte aplikacijo na svoj domači zaslon"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Dodajte pripomoček na domači zaslon"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Položaj naslovne vrstice"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Izgled"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Odpri povezave v povezanih aplikacijah"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Onemogoči, da preprečiš samodejno odpiranje povezav v drugih nameščenih aplikacijah."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Pokaži predloge za samodokončanje"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Zaklepanje aplikacije"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Samodejno počisti podatke"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Samodejno počisti podatke"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Upravljanje pojavnih oken za piškotke"; + +/* Settings title for the customize section */ +"settings.customize" = "Prilagodi"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Nastavite za privzeti brskalnik"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "Zaščita e-pošte"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blokirajte sledilnike e-pošte in skrijte svoj naslov"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Deli povratne informacije"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animacija za gumb ogenj"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Spletne strani s požarno zaščito"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Globalni nadzor zasebnosti (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Ikona aplikacije"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tipkovnica"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Prijave"; + +/* Settings title for the 'More' section */ +"settings.more" = "Več od iskalnika DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Pregledi z dolgim pritiskom"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Zasebnost"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Sinhronizacija in varnostno kopiranje"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Velikost besedila"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Nastavitve"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Nezaščitene strani"; + +/* Settings cell for Version */ +"settings.version" = "Različica"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Zasebno glasovno iskanje"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Pošlji poročilo"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Dovolite dostop do mikrofona v nastavitvah sistema iOS, da boste lahko v brskalniku DuckDuckGo uporabljali glasovne funkcije."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "V REDU"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Potreben je dostop do mikrofona"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 5e05b4d917..603e62d7d0 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "Välkommen till\nDuckDuckGo!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "DuckDuckGo för Mac har hastigheten du behöver, webbläsarfunktionerna du förväntar dig och våra många förstklassiga integritetsfunktioner."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoriter"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "Om DuckDuckGo"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Om"; + /* about page */ "settings.about.text" = "DuckDuckGo är det oberoende integritetsskyddsföretaget som grundades 2008 för alla som är trötta på att bli spårade online och vill ha en enkel lösning. Vi är beviset på att man kan få riktigt integritetsskydd på nätet utan att kompromissa.\n\nDuckDuckGo-webbläsaren har de funktioner du förväntar dig av en vanlig webbläsare, som bokmärken, flikar, lösenord och mer, plus över [ett dussin kraftfulla integritetsskydd](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) som inte ingår som standard i de flesta populära webbläsare. Den här omfattande uppsättningen av integritetsskydd hjälper dig att skydda dina onlineaktiviteter, från sökning och surfning till e-post och mycket mer.\n\nVårt integritetsskydd fungerar utan att du behöver känna till de tekniska detaljerna eller ta itu med komplicerade inställningar. Det enda du behöver göra är att byta webbläsare till DuckDuckGo på alla dina enheter för att få integritet som standard.\n\nMen om du * verkligen* vill ta en titt under huven hittar du mer information om hur DuckDuckGos integritetsskydd fungerar på våra [hjälpsidor](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/)."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Lägg till app i din Dock"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Lägg till widget på startsidan"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Adressfältsläge"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Utseende"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Öppna länkar i associerade appar"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Inaktivera för att förhindra att länkar automatiskt öppnas i andra installerade appar."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Visa Autoslutför förslag"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "App-lås"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Rensa data automatiskt"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Rensa data automatiskt"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Hantera popup-fönster för cookies"; + +/* Settings title for the customize section */ +"settings.customize" = "Anpassa"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Ställ in som standardwebbläsare"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-postskydd"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "Blockera e-postspårare och dölj din adress"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Berätta vad du tycker"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Animation för brännarknapp"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Brandsäkra webbplatser"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Global Privacy Control (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "App-ikon"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Tangentbord"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Inloggningar"; + +/* Settings title for the 'More' section */ +"settings.more" = "Mer från DuckDuckGo"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Förhandsvisning vid nedhållning"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Sekretess"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Synkronisering och säkerhetskopiering"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Textstorlek"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Inställningar"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Oskyddade webbplatser"; + +/* Settings cell for Version */ +"settings.version" = "Version"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Privat röstsökning"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Skicka in rapport"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "För att DuckDuckGo ska kunna använda röstfunktionerna måste du tillåta åtkomst till mikrofonen i systeminställningarna för iOS."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "OK"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofonåtkomst krävs"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 4aed95b6cc..8507885afb 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -1348,6 +1348,9 @@ /* Please preserve newline character */ "launchscreenWelcomeMessage" = "DuckDuckGo'ya\nHoş Geldiniz!"; +/* No comment provided by engineer. */ +"LOREM IPSUM" = "LOREM IPSUM"; + /* Summary text for the macOS browser waitlist */ "mac-browser.waitlist.summary" = "Mac için DuckDuckGo, ihtiyacınız olan hız ve beklediğiniz tarama özelliklerinin yanı sıra sınıfındaki en iyi gizlilik özelliklerini (Privacy Essentials) sunuyor."; @@ -1648,9 +1651,111 @@ /* No comment provided by engineer. */ "section.title.favorites" = "Favoriler"; +/* Settings cell for About DDG */ +"settings.about.ddg" = "DuckDuckGo Hakkında"; + +/* Settings section title for About DuckDuckGo */ +"settings.about.section" = "Hakkında"; + /* about page */ "settings.about.text" = "DuckDuckGo, çevrim içi takip edilmeyi hiç istemeyen ve pratik bir çözüm arayan herkese hizmet veren, 2008 yılında kurulmuş bağımsız bir İnternet gizlilik şirketidir. Biz, çevrim içi ortamda ödün vermeden gerçek gizlilik koruması elde edebileceğinizin kanıtıyız.\n\nDuckDuckGo tarayıcı; yer imleri, sekmeler, parolalar ve daha fazlası gibi en çok kullandığınız tarayıcıdan beklediğiniz ama çoğu popüler tarayıcıda varsayılan olarak bulunmayan özelliklerin yanı sıra [sayısı ondan fazla güçlü gizlilik koruması](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/) ile birlikte gelir. Son derece kapsamlı olan bu gizlilik koruma seti; aramadan gezinmeye, e-posta göndermeye ve daha fazlasına kadar çevrim içi etkinliklerinizde koruma sağlamaya yardımcı olur.\n\nGizlilik korumalarımız, teknik ayrıntılar hakkında hiçbir şey bilmenize veya karmaşık ayarlarla uğraşmanıza gerek kalmadan çalışır. Tüm cihazlarınızda tarayıcı olarak DuckDuckGo'yu kullanarak aradığınız gizliliği sağlayabilirsiniz.\n\nAncak ayrıntılara bir göz atmak *istiyorsanız*, DuckDuckGo gizlilik korumalarının nasıl çalıştığı hakkında daha fazla bilgiyi [yardım sayfalarımızda](ddgQuickLink://duckduckgo.com/duckduckgo-help-pages/) bulabilirsiniz."; +/* Settings screen cell text for adding the app to the dock */ +"settings.add.to.dock" = "Uygulamayı Dock'a Ekle"; + +/* Settings screen cell text for add widget to the home screen */ +"settings.add.widget" = "Widget'ı Ana Ekrana Ekle"; + +/* Settings screen cell text for addess bar position */ +"settings.address.bar" = "Adres Çubuğu Konumu"; + +/* Settings screen appearance section title */ +"settings.appearance" = "Görünüm"; + +/* Settings screen cell for opening links in associated apps */ +"settings.associated.apps" = "Bağlantıları İlişkili Uygulamalarda Aç"; + +/* Description for associated apps description */ +"settings.associated.apps.description" = "Bağlantıların yüklenmiş olan diğer uygulamalarda otomatik olarak açılmasını önlemek için devre dışı bırakın."; + +/* Settings screen cell for autocomplete */ +"settings.autocomplete" = "Otomatik Tamamlama Önerileri"; + +/* Settings screen cell text for Application Lock */ +"settings.autolock" = "Uygulama Kilidi"; + +/* Section footer Autolock description */ +"settings.autolock.description" = "Verileri Otomatik Olarak Temizle"; + +/* Settings screen cell text for Automatically Clearing Data */ +"settings.clear.data" = "Verileri Otomatik Olarak Temizle"; + +/* Settings screen cell text for Cookie popups */ +"settings.cookie.popups" = "Çerez Açılır Pencerelerini Yönetin"; + +/* Settings title for the customize section */ +"settings.customize" = "Özelleştir"; + +/* Settings screen cell text for setting the app as default browser */ +"settings.default.browser" = "Varsayılan Tarayıcı olarak ayarla"; + +/* Settings cell for Email Protection */ +"settings.emailProtection" = "E-posta Koruması"; + +/* Settings cell for Email Protection */ +"settings.emailProtection.description" = "E-posta izleyicileri engelleyin ve adresinizi gizleyin"; + +/* Settings cell for Feedback */ +"settings.feedback" = "Geri Bildirim Paylaş"; + +/* Settings screen cell text for fire button animation */ +"settings.firebutton" = "Yangın Düğmesi Animasyonu"; + +/* Settings screen cell text for Fireproof Sites */ +"settings.fireproof.sites" = "Korumalı Siteler"; + +/* Settings screen cell text for GPC */ +"settings.gpc" = "Küresel Gizlilik Kontrolü (GPC)"; + +/* Settings screen cell text for app icon selection */ +"settings.icon" = "Uygulama Simgesi"; + +/* Settings screen cell for Keyboard */ +"settings.keyboard" = "Klavye"; + +/* Settings screen cell text for logins */ +"settings.logins" = "Giriş bilgileri"; + +/* Settings title for the 'More' section */ +"settings.more" = "DuckDuckGo'dan daha fazlası"; + +/* Settings screen cell for long press previews */ +"settings.previews" = "Uzun Basma Önizlemeleri"; + +/* Settings title for the privacy section */ +"settings.privacy" = "Gizlilik"; + +/* Settings screen cell text for sync and backup */ +"settings.sync" = "Senkronizasyon ve Yedekleme"; + +/* Settings screen cell text for text size */ +"settings.text.size" = "Metin Boyutu"; + +/* Settings screen cell text for theme */ +"settings.theme" = "Tema"; + +/* Title for the Settings View */ +"settings.title" = "Ayarlar"; + +/* Settings screen cell text for Unprotected Sites */ +"settings.unprotected.sites" = "Korumasız Siteler"; + +/* Settings cell for Version */ +"settings.version" = "Versiyon"; + +/* Settings screen cell for voice search */ +"settings.voice.search" = "Özel Sesli Arama"; + /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Rapor Gönder"; @@ -1735,6 +1840,9 @@ /* Message for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.message" = "Ses özelliklerini kullanmak istiyorsanız iOS Sistem Ayarları'ndan DuckDuckGo için Mikrofon erişimine izin verin."; +/* OK button alert warning the user about missing microphone permission */ +"voiceSearch.alert.no-permission.ok" = "Tamam"; + /* Title for alert warning the user about missing microphone permission */ "voiceSearch.alert.no-permission.title" = "Mikrofon Erişimi Gerekli"; From 7f43367eca0d9c122bf7802d524d97899940cfb2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 14:47:55 +0100 Subject: [PATCH 27/99] Remove SettingsViewController --- DuckDuckGo.xcodeproj/project.pbxproj | 4 - DuckDuckGo/SettingsViewController.swift | 710 ------------------------ 2 files changed, 714 deletions(-) delete mode 100644 DuckDuckGo/SettingsViewController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bab9e3c213..97099dc4d7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -876,7 +876,6 @@ F198D7981E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; }; F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A568391E70F98E0081082E /* AutocompleteRequest.swift */; }; F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A886771F29394E0096251E /* WebCacheManager.swift */; }; - F1AB2B421E3F7D5C00868554 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */; }; F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */; }; F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4A70D1E57725800A6CA1B /* OmniBar.swift */; }; @@ -2518,7 +2517,6 @@ F1A568391E70F98E0081082E /* AutocompleteRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteRequest.swift; sourceTree = ""; }; F1A886771F29394E0096251E /* WebCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCacheManager.swift; sourceTree = ""; }; F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; F1B745211E549D550072547E /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIColorExtension.swift; path = ../Core/UIColorExtension.swift; sourceTree = ""; }; F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialSettings.swift; sourceTree = ""; }; @@ -3815,7 +3813,6 @@ 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */, 85449EF423FDA02800512AAF /* KeyboardSettingsViewController.swift */, 8540BD5523D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift */, - F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */, 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */, 8531A08D1F9950E6000484F0 /* UnprotectedSitesViewController.swift */, D6E83C672B23B6A3006C8AFB /* FontSettings.swift */, @@ -6650,7 +6647,6 @@ AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, - F1AB2B421E3F7D5C00868554 /* SettingsViewController.swift in Sources */, 8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */, B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */, 0290471E29E708750008FE3C /* AppTPManageTrackersView.swift in Sources */, diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift deleted file mode 100644 index 876ee7e6a2..0000000000 --- a/DuckDuckGo/SettingsViewController.swift +++ /dev/null @@ -1,710 +0,0 @@ -// -// SettingsViewController.swift -// DuckDuckGo -// -// Copyright © 2017 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit -import Core -import BrowserServicesKit -import Persistence -import SwiftUI -import Common -import DDGSync -import Combine - -#if APP_TRACKING_PROTECTION -import NetworkExtension -#endif - -#if NETWORK_PROTECTION -import NetworkProtection -#endif - -// swiftlint:disable file_length type_body_length -class SettingsViewController: UITableViewController { - - @IBOutlet weak var defaultBrowserCell: UITableViewCell! - @IBOutlet weak var themeAccessoryText: UILabel! - @IBOutlet weak var fireButtonAnimationAccessoryText: UILabel! - @IBOutlet weak var addressBarPositionCell: UITableViewCell! - @IBOutlet weak var addressBarPositionAccessoryText: UILabel! - @IBOutlet weak var appIconCell: UITableViewCell! - @IBOutlet weak var appIconImageView: UIImageView! - @IBOutlet weak var autocompleteToggle: UISwitch! - @IBOutlet weak var authenticationToggle: UISwitch! - @IBOutlet weak var autoClearAccessoryText: UILabel! - @IBOutlet weak var versionText: UILabel! - @IBOutlet weak var openUniversalLinksToggle: UISwitch! - @IBOutlet weak var longPressPreviewsToggle: UISwitch! - @IBOutlet weak var rememberLoginsCell: UITableViewCell! - @IBOutlet weak var rememberLoginsAccessoryText: UILabel! - @IBOutlet weak var doNotSellCell: UITableViewCell! - @IBOutlet weak var doNotSellAccessoryText: UILabel! - @IBOutlet weak var autoconsentCell: UITableViewCell! - @IBOutlet weak var autoconsentAccessoryText: UILabel! - @IBOutlet weak var emailProtectionCell: UITableViewCell! - @IBOutlet weak var emailProtectionAccessoryText: UILabel! - @IBOutlet weak var macBrowserWaitlistCell: UITableViewCell! - @IBOutlet weak var macBrowserWaitlistAccessoryText: UILabel! - @IBOutlet weak var windowsBrowserWaitlistCell: UITableViewCell! - @IBOutlet weak var windowsBrowserWaitlistAccessoryText: UILabel! - @IBOutlet weak var netPCell: UITableViewCell! - @IBOutlet weak var longPressCell: UITableViewCell! - @IBOutlet weak var versionCell: UITableViewCell! - @IBOutlet weak var textSizeCell: UITableViewCell! - @IBOutlet weak var textSizeAccessoryText: UILabel! - @IBOutlet weak var widgetEducationCell: UITableViewCell! - @IBOutlet weak var syncCell: UITableViewCell! - @IBOutlet weak var autofillCell: UITableViewCell! - @IBOutlet weak var debugCell: UITableViewCell! - @IBOutlet weak var voiceSearchCell: UITableViewCell! - @IBOutlet weak var voiceSearchToggle: UISwitch! - - @IBOutlet var labels: [UILabel]! - @IBOutlet var accessoryLabels: [UILabel]! - - private let syncSectionIndex = 1 - private let autofillSectionIndex = 2 - private let appearanceSectionIndex = 3 - private let moreFromDDGSectionIndex = 6 - private let debugSectionIndex = 8 - - private let bookmarksDatabase: CoreDataDatabase - - private lazy var emailManager = EmailManager() - - private lazy var versionProvider: AppVersion = AppVersion.shared - fileprivate lazy var privacyStore = PrivacyUserDefaults() - fileprivate lazy var appSettings = AppDependencyProvider.shared.appSettings - fileprivate lazy var variantManager = AppDependencyProvider.shared.variantManager - fileprivate lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger - fileprivate let syncService: DDGSyncing - fileprivate let syncDataProviders: SyncDataProviders - fileprivate let internalUserDecider: InternalUserDecider -#if NETWORK_PROTECTION - private let connectionObserver = ConnectionStatusObserverThroughSession() -#endif - private var cancellables: Set = [] - - private var shouldShowDebugCell: Bool { - return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild - } - - private var shouldShowVoiceSearchCell: Bool { - AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable - } - - private var shouldShowAutofillCell: Bool { - return featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) - } - - private var shouldShowSyncCell: Bool { - return featureFlagger.isFeatureOn(.sync) - } - - private var shouldShowTextSizeCell: Bool { - return UIDevice.current.userInterfaceIdiom != .pad - } - - private var shouldShowAddressBarPositionCell: Bool { - return UIDevice.current.userInterfaceIdiom != .pad - } - - private lazy var shouldShowNetPCell: Bool = { -#if NETWORK_PROTECTION - if #available(iOS 15, *) { - return featureFlagger.isFeatureOn(.networkProtection) - } else { - return false - } -#else - return false -#endif - }() - - override func viewDidLoad() { - super.viewDidLoad() - - configureAutofillCell() - configureSyncCell() - configureThemeCellAccessory() - configureFireButtonAnimationCellAccessory() - configureAddressBarPositionCell() - configureTextSizeCell() - configureDisableAutocompleteToggle() - configureSecurityToggles() - configureVersionText() - configureUniversalLinksToggle() - configureLinkPreviewsToggle() - configureRememberLogins() - configureDebugCell() - configureVoiceSearchCell() - configureNetPCell() - (ThemeManager.shared.currentTheme) - - internalUserDecider.isInternalUserPublisher.dropFirst().sink(receiveValue: { [weak self] _ in - self?.configureAutofillCell() - self?.configureSyncCell() - self?.configureDebugCell() - self?.tableView.reloadData() - - // Scroll to force-redraw section headers and footers - self?.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) - }) - .store(in: &cancellables) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - configureFireButtonAnimationCellAccessory() - configureAddressBarPositionCell() - configureTextSizeCell() - configureAutoClearCellAccessory() - configureRememberLogins() - configureDoNotSell() - configureAutoconsent() - configureIconViews() - configureEmailProtectionAccessoryText() - configureMacBrowserWaitlistCell() - configureWindowsBrowserWaitlistCell() - configureSyncCell() - -#if NETWORK_PROTECTION - updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) -#endif - - // Make sure multiline labels are correctly presented - tableView.setNeedsLayout() - tableView.layoutIfNeeded() - } - - init?(coder: NSCoder, - bookmarksDatabase: CoreDataDatabase, - syncService: DDGSyncing, - syncDataProviders: SyncDataProviders, - internalUserDecider: InternalUserDecider) { - - self.bookmarksDatabase = bookmarksDatabase - self.syncService = syncService - self.syncDataProviders = syncDataProviders - self.internalUserDecider = internalUserDecider - super.init(coder: coder) - } - - required init?(coder: NSCoder) { - fatalError("Not implemented") - } - - func openLogins() { - showAutofill() - } - - func openLogins(accountDetails: SecureVaultModels.WebsiteAccount) { - showAutofillAccountDetails(accountDetails) - } - - func openCookiePopupManagement() { - showCookiePopupManagement(animated: true) - } - - @IBSegueAction func onCreateRootDebugScreen(_ coder: NSCoder, sender: Any?, segueIdentifier: String?) -> RootDebugViewController { - guard let controller = RootDebugViewController(coder: coder, - sync: syncService, - bookmarksDatabase: bookmarksDatabase, - internalUserDecider: AppDependencyProvider.shared.internalUserDecider) else { - fatalError("Failed to create controller") - } - - return controller - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.destination is DoNotSellSettingsViewController { - Pixel.fire(pixel: .settingsDoNotSellShown) - return - } else if segue.destination is AutoconsentSettingsViewController { - Pixel.fire(pixel: .settingsAutoconsentShown) - return - } else if let textSizeSettings = segue.destination as? TextSizeSettingsViewController { - Pixel.fire(pixel: .textSizeSettingsShown) - presentationController?.delegate = textSizeSettings - return - } - - if let navController = segue.destination as? UINavigationController, navController.topViewController is FeedbackViewController { - if UIDevice.current.userInterfaceIdiom == .pad { - segue.destination.modalPresentationStyle = .formSheet - } - } - } - - private func configureAutofillCell() { - autofillCell.isHidden = !shouldShowAutofillCell - } - - private func configureSyncCell() { - syncCell.textLabel?.text = "Sync & Backup" - if SyncBookmarksAdapter.isSyncBookmarksPaused || SyncCredentialsAdapter.isSyncCredentialsPaused { - syncCell.textLabel?.text = "⚠️ " + "Sync & Backup" - } - syncCell.isHidden = !shouldShowSyncCell - } - - private func configureVoiceSearchCell() { - voiceSearchCell.isHidden = !shouldShowVoiceSearchCell - voiceSearchToggle.isOn = appSettings.voiceSearchEnabled - } - - private func configureThemeCellAccessory() { - switch appSettings.currentThemeName { - case .systemDefault: - themeAccessoryText.text = UserText.themeAccessoryDefault - case .light: - themeAccessoryText.text = UserText.themeAccessoryLight - case .dark: - themeAccessoryText.text = UserText.themeAccessoryDark - } - } - - private func configureFireButtonAnimationCellAccessory() { - fireButtonAnimationAccessoryText.text = appSettings.currentFireButtonAnimation.descriptionText - } - - private func configureAddressBarPositionCell() { - addressBarPositionCell.isHidden = !shouldShowAddressBarPositionCell - addressBarPositionAccessoryText.text = appSettings.currentAddressBarPosition.descriptionText - } - - private func configureTextSizeCell() { - textSizeCell.isHidden = !shouldShowTextSizeCell - textSizeAccessoryText.text = "\(appSettings.textSize)s" - } - - private func configureIconViews() { - if AppIconManager.shared.isAppIconChangeSupported { - appIconImageView.image = AppIconManager.shared.appIcon.smallImage - } else { - appIconCell.isHidden = true - } - } - - private func configureDisableAutocompleteToggle() { - autocompleteToggle.isOn = appSettings.autocomplete - } - - private func configureSecurityToggles() { - authenticationToggle.isOn = privacyStore.authenticationEnabled - } - - private func configureAutoClearCellAccessory() { - if AutoClearSettingsModel(settings: appSettings) != nil { - autoClearAccessoryText.text = UserText.autoClearAccessoryOn - } else { - autoClearAccessoryText.text = UserText.autoClearAccessoryOff - } - } - - private func configureDoNotSell() { - doNotSellAccessoryText.text = appSettings.sendDoNotSell ? UserText.doNotSellEnabled : UserText.doNotSellDisabled - } - - private func configureAutoconsent() { - autoconsentAccessoryText.text = appSettings.autoconsentEnabled ? UserText.autoconsentEnabled : UserText.autoconsentDisabled - } - - private func configureRememberLogins() { - rememberLoginsAccessoryText.text = PreserveLogins.shared.allowedDomains.isEmpty ? "" : "\(PreserveLogins.shared.allowedDomains.count)" - } - - private func configureVersionText() { - versionText.text = versionProvider.versionAndBuildNumber - } - - private func configureUniversalLinksToggle() { - openUniversalLinksToggle.isOn = appSettings.allowUniversalLinks - } - - private func configureLinkPreviewsToggle() { - longPressCell.isHidden = false - longPressPreviewsToggle.isOn = appSettings.longPressPreviews - } - - private func configureMacBrowserWaitlistCell() { - macBrowserWaitlistCell.detailTextLabel?.text = MacBrowserWaitlist.shared.settingsSubtitle - macBrowserWaitlistCell.detailTextLabel?.text = MacBrowserWaitlist.shared.settingsSubtitle - } - - private func configureWindowsBrowserWaitlistCell() { - windowsBrowserWaitlistCell.isHidden = !WindowsBrowserWaitlist.shared.isAvailable - - if WindowsBrowserWaitlist.shared.isAvailable { - windowsBrowserWaitlistCell.detailTextLabel?.text = WindowsBrowserWaitlist.shared.settingsSubtitle - } - } - - private func configureNetPCell() { - netPCell.isHidden = !shouldShowNetPCell -#if NETWORK_PROTECTION - updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) - connectionObserver.publisher - .receive(on: DispatchQueue.main) - .sink { [weak self] status in - self?.updateNetPCellSubtitle(connectionStatus: status) - } - .store(in: &cancellables) -#endif - } - -#if NETWORK_PROTECTION - private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { - switch NetworkProtectionAccessController().networkProtectionAccessType() { - case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: - netPCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle - case .waitlistInvited, .inviteCodeInvited: - switch connectionStatus { - case .connected: netPCell.detailTextLabel?.text = UserText.netPCellConnected - default: netPCell.detailTextLabel?.text = UserText.netPCellDisconnected - } - } - } -#endif - - private func configureDebugCell() { - debugCell.isHidden = !shouldShowDebugCell - } - - func showSync(animated: Bool = true) { - let controller = SyncSettingsViewController(syncService: syncService, syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) - navigationController?.pushViewController(controller, animated: animated) - } - - private func showAutofill(animated: Bool = true) { - let autofillController = AutofillLoginSettingsListViewController( - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, - selectedAccount: nil - ) - autofillController.delegate = self - Pixel.fire(pixel: .autofillSettingsOpened) - navigationController?.pushViewController(autofillController, animated: animated) - } - - func showAutofillAccountDetails(_ account: SecureVaultModels.WebsiteAccount) { - let autofillController = AutofillLoginSettingsListViewController( - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, - selectedAccount: account - ) - autofillController.delegate = self - let detailsController = autofillController.makeAccountDetailsScreen(account) - - var controllers = navigationController?.viewControllers ?? [] - controllers.append(autofillController) - controllers.append(detailsController) - navigationController?.viewControllers = controllers - } - - private func configureEmailProtectionAccessoryText() { - if let userEmail = emailManager.userEmail { - emailProtectionAccessoryText.text = userEmail - } else { - emailProtectionAccessoryText.text = UserText.emailSettingsSubtitle - } - } - - private func showEmailWebDashboard() { - UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) - } - - private func showMacBrowserWaitlistViewController() { - navigationController?.pushViewController(MacWaitlistViewController(nibName: nil, bundle: nil), animated: true) - } - -#if NETWORK_PROTECTION - @available(iOS 15, *) - private func showNetP() { - switch NetworkProtectionAccessController().networkProtectionAccessType() { - case .inviteCodeInvited, .waitlistInvited: - // This will be tidied up as part of https://app.asana.com/0/0/1205084446087078/f - let rootViewController = NetworkProtectionRootViewController { [weak self] in - self?.navigationController?.popViewController(animated: true) - let newRootViewController = NetworkProtectionRootViewController() - self?.pushNetP(newRootViewController) - } - - pushNetP(rootViewController) - default: - navigationController?.pushViewController(VPNWaitlistViewController(nibName: nil, bundle: nil), animated: true) - } - } - - @available(iOS 15, *) - private func pushNetP(_ rootViewController: NetworkProtectionRootViewController) { - navigationController?.pushViewController( - rootViewController, - animated: true - ) - } -#endif - - private func showWindowsBrowserWaitlistViewController() { - navigationController?.pushViewController(WindowsWaitlistViewController(nibName: nil, bundle: nil), animated: true) - } - - func showCookiePopupManagement(animated: Bool = true) { - navigationController?.pushViewController(AutoconsentSettingsViewController.loadFromStoryboard(), animated: animated) - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let cell = tableView.cellForRow(at: indexPath) - - switch cell { - - case defaultBrowserCell: - Pixel.fire(pixel: .defaultBrowserButtonPressedSettings) - guard let url = URL(string: UIApplication.openSettingsURLString) else { return } - UIApplication.shared.open(url) - - case emailProtectionCell: - showEmailWebDashboard() - - case macBrowserWaitlistCell: - showMacBrowserWaitlistViewController() - - case windowsBrowserWaitlistCell: - showWindowsBrowserWaitlistViewController() - - case autofillCell: - showAutofill() - - case syncCell: - showSync() - - case netPCell: - if #available(iOS 15, *) { -#if NETWORK_PROTECTION - showNetP() -#else - break -#endif - } - default: break - } - - } - - override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - let theme = ThemeManager.shared.currentTheme - cell.backgroundColor = theme.tableCellBackgroundColor - - if cell == netPCell { - DailyPixel.fire(pixel: .networkProtectionSettingsRowDisplayed) - } - } - - override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection: Int) { - if let view = view as? UITableViewHeaderFooterView { - let theme = ThemeManager.shared.currentTheme - view.textLabel?.textColor = theme.tableHeaderTextColor - } - } - - override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection: Int) { - if let view = view as? UITableViewHeaderFooterView { - let theme = ThemeManager.shared.currentTheme - view.textLabel?.textColor = theme.tableHeaderTextColor - } - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let cell = super.tableView(tableView, cellForRowAt: indexPath) - return cell.isHidden ? 0 : UITableView.automaticDimension - } - - override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } - - /// Only use this to hide the header if the entire section can be conditionally hidden. - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - if syncSectionIndex == section && !shouldShowSyncCell { - return CGFloat.leastNonzeroMagnitude - } else if autofillSectionIndex == section && !shouldShowAutofillCell { - return CGFloat.leastNonzeroMagnitude - } else if debugSectionIndex == section && !shouldShowDebugCell { - return CGFloat.leastNonzeroMagnitude - } else { - return super.tableView(tableView, heightForHeaderInSection: section) - } - } - - /// Only use this to hide the footer if the entire section can be conditionally hidden. - override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - if syncSectionIndex == section && !shouldShowSyncCell { - return CGFloat.leastNonzeroMagnitude - } else if autofillSectionIndex == section && !shouldShowAutofillCell { - return CGFloat.leastNonzeroMagnitude - } else if debugSectionIndex == section && !shouldShowDebugCell { - return CGFloat.leastNonzeroMagnitude - } else { - return super.tableView(tableView, heightForFooterInSection: section) - } - } - - /// Only use this if the *last cell* in the section is to be conditionally hidden in order to retain the section rounding. - /// If your cell is not the last you don't need to modify the number of rows. - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let rows = super.tableView(tableView, numberOfRowsInSection: section) - if section == moreFromDDGSectionIndex && !shouldShowNetPCell { - return rows - 1 - } else if section == appearanceSectionIndex && UIDevice.current.userInterfaceIdiom == .pad { - // Both the text size and bottom bar settings are at the end of the section so need to reduce the section size appropriately - return rows - 2 - } else { - return rows - } - } - - @IBAction func onVoiceSearchToggled(_ sender: UISwitch) { - var enableVoiceSearch = sender.isOn - let isFirstTimeAskingForPermission = SpeechRecognizer.recordPermission == .undetermined - - SpeechRecognizer.requestMicAccess { permission in - if !permission { - enableVoiceSearch = false - sender.setOn(false, animated: true) - if !isFirstTimeAskingForPermission { - self.showNoMicrophonePermissionAlert() - } - } - - AppDependencyProvider.shared.voiceSearchHelper.enableVoiceSearch(enableVoiceSearch) - } - } - - @IBAction func onAboutTapped() { - navigationController?.pushViewController(AboutViewController(), animated: true) - } - - private func showNoMicrophonePermissionAlert() { - let alertController = NoMicPermissionAlert.buildAlert() - present(alertController, animated: true, completion: nil) - } - - @IBAction func onAuthenticationToggled(_ sender: UISwitch) { - privacyStore.authenticationEnabled = sender.isOn - } - - @IBAction func onDonePressed(_ sender: Any) { - dismiss(animated: true, completion: nil) - } - - @IBAction func onAutocompleteToggled(_ sender: UISwitch) { - appSettings.autocomplete = sender.isOn - } - - @IBAction func onAllowUniversalLinksToggled(_ sender: UISwitch) { - appSettings.allowUniversalLinks = sender.isOn - } - - @IBAction func onLinkPreviewsToggle(_ sender: UISwitch) { - appSettings.longPressPreviews = sender.isOn - } -} - -extension SettingsViewController: Themable { - - func decorate(with theme: Theme) { - view.backgroundColor = theme.backgroundColor - - decorateNavigationBar(with: theme) - configureThemeCellAccessory() - - for label in labels { - label.textColor = theme.tableCellTextColor - } - - for label in accessoryLabels { - label.textColor = theme.tableCellAccessoryTextColor - } - - versionText.textColor = theme.tableCellTextColor - - autocompleteToggle.onTintColor = theme.buttonTintColor - authenticationToggle.onTintColor = theme.buttonTintColor - openUniversalLinksToggle.onTintColor = theme.buttonTintColor - longPressPreviewsToggle.onTintColor = theme.buttonTintColor - voiceSearchToggle.onTintColor = theme.buttonTintColor - - tableView.backgroundColor = theme.backgroundColor - tableView.separatorColor = theme.tableCellSeparatorColor - - UIView.transition(with: view, - duration: 0.2, - options: .transitionCrossDissolve, animations: { - self.tableView.reloadData() - }, completion: nil) - } -} - -extension SettingsViewController { - static var fontSizeForHeaderView: CGFloat { - let contentSize = UIApplication.shared.preferredContentSizeCategory - switch contentSize { - case .extraSmall: - return 12 - case .small: - return 12 - case .medium: - return 12 - case .large: - return 13 - case .extraLarge: - return 15 - case .extraExtraLarge: - return 17 - case .extraExtraExtraLarge: - return 19 - case .accessibilityMedium: - return 23 - case .accessibilityLarge: - return 27 - case .accessibilityExtraLarge: - return 33 - case .accessibilityExtraExtraLarge: - return 38 - case .accessibilityExtraExtraExtraLarge: - return 44 - default: - return 13 - } - } -} - -// MARK: - AutofillLoginSettingsListViewControllerDelegate - -extension SettingsViewController: AutofillLoginSettingsListViewControllerDelegate { - func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { - navigationController?.popViewController(animated: true) - } -} -// swiftlint:enable file_length type_body_length From 92e0afddd03e4ffae341a4869fcf0a516fbe4c35 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 14:51:31 +0100 Subject: [PATCH 28/99] Update layout priority for Text accesory --- DuckDuckGo/SettingsCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 13233c8802..b07e2c9ad6 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -145,6 +145,7 @@ struct SettingsCellView: View, Identifiable { Text(value) .daxSubheadRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) + .layoutPriority(1) case .toggle(let isOn): Toggle("", isOn: isOn) .toggleStyle(SwitchToggleStyle(tint: Color(designSystemColor: .accent))) From 26078892fa1fbc3bb78d233430e4710d65a746ea Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 15:06:17 +0100 Subject: [PATCH 29/99] Cleanup --- DuckDuckGo/MainViewController+Segues.swift | 54 ---------------------- DuckDuckGo/OptionalBinding.swift | 32 ------------- DuckDuckGo/View+onChageSkippingFirst.swift | 44 ------------------ DuckDuckGo/WidgetEducationView.swift | 2 +- 4 files changed, 1 insertion(+), 131 deletions(-) delete mode 100644 DuckDuckGo/OptionalBinding.swift delete mode 100644 DuckDuckGo/View+onChageSkippingFirst.swift diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 2c2448ccc4..a6e773d0e8 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -252,28 +252,6 @@ extension MainViewController { completion?(settingsViewModel) } } - - /* - private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { - os_log(#function, log: .generalLog, type: .debug) - let settingsModel = SettingsViewModel(bookmarksDatabase: self.bookmarksDatabase, - syncService: self.syncService, - syncDataProviders: self.syncDataProviders, - internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - - let settingsController = SettingsHostingController(viewModel: settingsModel, rootView: AnyView(EmptyView())) - settingsController.viewModel = settingsModel - - let settingsView = SettingsView(viewModel: settingsModel) { [weak settingsController] legacyVC in - settingsController?.pushLegacyViewController(legacyVC) - } - settingsController.rootView = AnyView(settingsView) - settingsController.modalPresentationStyle = .automatic - present(settingsController, animated: true) { - completion?(settingsModel) - } - } - */ private func hideAllHighlightsIfNeeded() { os_log(#function, log: .generalLog, type: .debug) @@ -282,36 +260,4 @@ extension MainViewController { } } - - func presentTextSizeSettings() { - if let presentingVC = self.presentedViewController { - presentingVC.dismiss(animated: true) { [weak self] in - self?.presentTextSizeSettingsViewController() - } - } else { - presentTextSizeSettingsViewController() - } - } - - private func presentTextSizeSettingsViewController() { - let storyboard = UIStoryboard(name: "Settings", bundle: nil) - let viewController = storyboard.instantiateViewController(identifier: "TextSize") as! TextSizeSettingsViewController - Pixel.fire(pixel: .textSizeSettingsShown) - presentationController?.delegate = viewController - - if #available(iOS 15.0, *) { - // Configure settingsVC as a sheet - viewController.modalPresentationStyle = .pageSheet - viewController.modalTransitionStyle = .coverVertical - - let presentationController = viewController.presentationController as? UISheetPresentationController - presentationController?.detents = [.medium(), .large()] - presentationController?.preferredCornerRadius = 16 - presentationController?.largestUndimmedDetentIdentifier = .medium - presentationController?.prefersScrollingExpandsWhenScrolledToEdge = false - - } - - self.present(viewController, animated: true, completion: nil) - } } diff --git a/DuckDuckGo/OptionalBinding.swift b/DuckDuckGo/OptionalBinding.swift deleted file mode 100644 index 962a0924e4..0000000000 --- a/DuckDuckGo/OptionalBinding.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// OptionalBinding.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 - -extension Binding { - - /// Creates a binding by projecting the value of an optional to a non-optional, replacing `nil` values with a default. - /// As onChange props are optional (nil by default) This prevents onChange events from triggering when props are initialized - init(_ source: Binding, replacingNilWith defaultValue: Value) { - self.init( - get: { source.wrappedValue ?? defaultValue }, - set: { source.wrappedValue = $0 } - ) - } -} diff --git a/DuckDuckGo/View+onChageSkippingFirst.swift b/DuckDuckGo/View+onChageSkippingFirst.swift deleted file mode 100644 index a726115b59..0000000000 --- a/DuckDuckGo/View+onChageSkippingFirst.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// View+onChageSkippingFirst.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 - -/// Implements a custom modifier to implement .onChange events that skip the first update - -@propertyWrapper -struct SkipFirstUpdate where Value: Equatable { - private var value: Value? - private var isInitialUpdate = true - - var wrappedValue: Value? { - didSet { - guard !isInitialUpdate, oldValue != wrappedValue else { - isInitialUpdate = false - return - } - projectedValue?(wrappedValue) - } - } - - var projectedValue: ((Value?) -> Void)? - - init(wrappedValue: Value?) { - self.value = wrappedValue - } -} diff --git a/DuckDuckGo/WidgetEducationView.swift b/DuckDuckGo/WidgetEducationView.swift index 6dce4e57d6..3a3b3bcd71 100644 --- a/DuckDuckGo/WidgetEducationView.swift +++ b/DuckDuckGo/WidgetEducationView.swift @@ -46,7 +46,7 @@ struct WidgetEducationView: View { .padding(.horizontal) .padding(.top, Const.Padding.top) } - }.navigationBarTitle("Add Widget to Home Screen", displayMode: .inline) + }.navigationBarTitle(UserText.settingsAddWidget, displayMode: .inline) } private var secondParagraphText: Text { From 3355e9269ed2838822b77c05fe32562b6505ac7f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 15:09:05 +0100 Subject: [PATCH 30/99] Revert Appicon changes --- DuckDuckGo/AppIconManager.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/AppIconManager.swift b/DuckDuckGo/AppIconManager.swift index d47c446eb9..9a8450a9e1 100644 --- a/DuckDuckGo/AppIconManager.swift +++ b/DuckDuckGo/AppIconManager.swift @@ -21,18 +21,18 @@ import Common import UIKit import Core -class AppIconManager: ObservableObject { +class AppIconManager { static var shared = AppIconManager() - @Published var appIcon: AppIcon = { + var appIcon: AppIcon { guard let appIconName = UIApplication.shared.alternateIconName, let appIcon = AppIcon(rawValue: appIconName) else { return AppIcon.defaultAppIcon } return appIcon - }() + } var isAppIconChangeSupported: Bool { UIApplication.shared.supportsAlternateIcons @@ -45,17 +45,13 @@ class AppIconManager: ObservableObject { } let alternateIconName = appIcon != AppIcon.defaultAppIcon ? appIcon.rawValue : nil - UIApplication.shared.setAlternateIconName(alternateIconName) { error in - DispatchQueue.main.async { + UIApplication.shared.setAlternateIconName(alternateIconName) { error in if let error = error { os_log("Error while changing app icon: %s", log: .generalLog, type: .debug, error.localizedDescription) completionHandler?(error) } else { - self.appIcon = appIcon completionHandler?(nil) } } - } } - } From f2bcb58edd6d46494fd488ada67d76f4e308ecd0 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 15:11:01 +0100 Subject: [PATCH 31/99] Revert AppIcon Changes --- DuckDuckGo/AppIconManager.swift | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo/AppIconManager.swift b/DuckDuckGo/AppIconManager.swift index 9a8450a9e1..dbee15c1c3 100644 --- a/DuckDuckGo/AppIconManager.swift +++ b/DuckDuckGo/AppIconManager.swift @@ -24,15 +24,6 @@ import Core class AppIconManager { static var shared = AppIconManager() - - var appIcon: AppIcon { - guard let appIconName = UIApplication.shared.alternateIconName, - let appIcon = AppIcon(rawValue: appIconName) else { - return AppIcon.defaultAppIcon - } - - return appIcon - } var isAppIconChangeSupported: Bool { UIApplication.shared.supportsAlternateIcons @@ -45,13 +36,23 @@ class AppIconManager { } let alternateIconName = appIcon != AppIcon.defaultAppIcon ? appIcon.rawValue : nil - UIApplication.shared.setAlternateIconName(alternateIconName) { error in - if let error = error { - os_log("Error while changing app icon: %s", log: .generalLog, type: .debug, error.localizedDescription) - completionHandler?(error) - } else { - completionHandler?(nil) - } + UIApplication.shared.setAlternateIconName(alternateIconName) { error in + if let error = error { + os_log("Error while changing app icon: %s", log: .generalLog, type: .debug, error.localizedDescription) + completionHandler?(error) + } else { + completionHandler?(nil) } + } } + + var appIcon: AppIcon { + guard let appIconName = UIApplication.shared.alternateIconName, + let appIcon = AppIcon(rawValue: appIconName) else { + return AppIcon.defaultAppIcon + } + + return appIcon + } + } From 1e7d33af27bc425ad4d8724162aed0c68826a54c Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 16:48:01 +0100 Subject: [PATCH 32/99] Fix linter issues (pass 1) --- DuckDuckGo/SettingsAppeareanceView.swift | 1 + DuckDuckGo/SettingsCell.swift | 1 - DuckDuckGo/SettingsCustomizeView.swift | 1 + DuckDuckGo/SettingsLegacyViewProvider.swift | 17 +++++++++++++- DuckDuckGo/SettingsMoreView.swift | 1 + DuckDuckGo/SettingsPrivacyView.swift | 1 + DuckDuckGo/SettingsUserText.swift | 26 --------------------- 7 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 DuckDuckGo/SettingsUserText.swift diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index 268baa0a80..351bf21f11 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -1,3 +1,4 @@ +// // SettingsAppeareanceView.swift // DuckDuckGo // diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index b07e2c9ad6..1c07d7e4c8 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -210,7 +210,6 @@ struct SettingsPickerCellView: View { var content: Content diff --git a/DuckDuckGo/SettingsCustomizeView.swift b/DuckDuckGo/SettingsCustomizeView.swift index 85cee7fb94..f226897473 100644 --- a/DuckDuckGo/SettingsCustomizeView.swift +++ b/DuckDuckGo/SettingsCustomizeView.swift @@ -1,3 +1,4 @@ +// // SettingsCustomizeView.swift // DuckDuckGo // diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 5da4d072cc..8e4e0532ce 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -44,7 +44,22 @@ class SettingsLegacyViewProvider: ObservableObject { } enum LegacyView { - case addToDock, sync, logins, textSize, appIcon, gpc, autoconsent, unprotectedSites, fireproofSites, autoclearData, keyboard, macApp, windowsApp, netP, about, feedback, debug + case addToDock, + sync, + logins, + textSize, + appIcon, + gpc, + autoconsent, + unprotectedSites, + fireproofSites, + autoclearData, + keyboard, + macApp, + windowsApp, + netP, + about, + feedback, debug } private func instantiate(_ identifier: String, fromStoryboard name: String) -> UIViewController { diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index 478408229e..bbf45fab58 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -1,3 +1,4 @@ +// // SettingsMoreView.swift // DuckDuckGo // diff --git a/DuckDuckGo/SettingsPrivacyView.swift b/DuckDuckGo/SettingsPrivacyView.swift index cc6d5450af..df869e9bd8 100644 --- a/DuckDuckGo/SettingsPrivacyView.swift +++ b/DuckDuckGo/SettingsPrivacyView.swift @@ -1,3 +1,4 @@ +// // SettingsPrivacyView.swift // DuckDuckGo // diff --git a/DuckDuckGo/SettingsUserText.swift b/DuckDuckGo/SettingsUserText.swift deleted file mode 100644 index 186afee8ee..0000000000 --- a/DuckDuckGo/SettingsUserText.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// SettingsUserText.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 Core - -public struct SettingsUserText { - - - - From f88de4d15c605f0981435e6af104bb2a8d791fcf Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 17:08:41 +0100 Subject: [PATCH 33/99] Remove cyclomatic warning --- DuckDuckGo/SettingsCell.swift | 17 ++++++++++------- DuckDuckGo/SettingsLegacyViewProvider.swift | 12 ++++++++---- DuckDuckGo/SettingsLoginsView.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 6 +++--- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 1c07d7e4c8..f1931451a5 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -188,13 +188,8 @@ struct SettingsPickerCellView Void, option: String) -> some View { + return Button(action: action) { + Text(option) + .daxBodyRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + } + } } /// A simple settings cell that can act as a link and include a disclosure indicator diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 8e4e0532ce..91cc4f888b 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -1,5 +1,5 @@ // -// LegacyViewProvider.swift +// SettingsLegacyViewProvider.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. @@ -105,9 +105,13 @@ class SettingsLegacyViewProvider: ObservableObject { var debug: UIViewController { let storyboard = UIStoryboard(name: "Debug", bundle: nil) - let viewController = storyboard.instantiateViewController(withIdentifier: "DebugMenu") as! RootDebugViewController - viewController.configure(sync: syncService, bookmarksDatabase: bookmarksDatabase, internalUserDecider: AppDependencyProvider.shared.internalUserDecider) - return viewController + if let viewController = storyboard.instantiateViewController(withIdentifier: "DebugMenu") as? RootDebugViewController { + viewController.configure(sync: syncService, + bookmarksDatabase: bookmarksDatabase, + internalUserDecider: AppDependencyProvider.shared.internalUserDecider) + return viewController + } + return UIViewController() } } diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index d874beb8d6..e194f90770 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -1,3 +1,4 @@ +// // SettingsLoginsView.swift // DuckDuckGo // diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 3fb26886d0..be6a7e2344 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -317,9 +317,9 @@ extension SettingsViewModel { // for all existing subviews, default to UIKit based presentation until we // can review and migrate extension SettingsViewModel { - - @MainActor - func presentLegacyView(_ view: SettingsLegacyViewProvider.LegacyView) { + + // swiftlint:disable:next cyclomatic_complexity + @MainActor func presentLegacyView(_ view: SettingsLegacyViewProvider.LegacyView) { switch view { From 394e3f22955b56ebd125929539101c39890f95da Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 19:30:25 +0100 Subject: [PATCH 34/99] Added debug to previews --- DuckDuckGo/SettingsCell.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index f1931451a5..73ae165de5 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -243,7 +243,7 @@ struct SettingsCustomCell: View { } } - +#if DEBUG struct SettingsCellView_Previews: PreviewProvider { enum SampleOption: String, CaseIterable, Hashable, CustomStringConvertible { case optionOne = "Lorem" @@ -350,3 +350,4 @@ struct SettingsCellView_Previews: PreviewProvider { } } } +#endif From d98770953b7435cbb690bd41e86dff85c67b4e1b Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 19:34:55 +0100 Subject: [PATCH 35/99] Add all params to preview --- DuckDuckGo/SettingsCell.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 73ae165de5..6fe404bcf3 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -342,7 +342,9 @@ struct SettingsCellView_Previews: PreviewProvider { .foregroundColor(.orange) .imageScale(.large) } - }, disclosureIndicator: true) + }, action: {}, + asLink: false, + disclosureIndicator: true) .previewLayout(.sizeThatFits) From 4cc3c7a67996cf7f729a60ce4299addc34b1a75d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 19:39:11 +0100 Subject: [PATCH 36/99] Comment out preview --- DuckDuckGo/SettingsCell.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 6fe404bcf3..ffd62b897c 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -319,7 +319,7 @@ struct SettingsCellView_Previews: PreviewProvider { disclosureIndicator: false) .previewLayout(.sizeThatFits) - + /* SettingsCustomCell(content: { HStack(spacing: 15) { Image(systemName: "hand.wave") @@ -346,6 +346,7 @@ struct SettingsCellView_Previews: PreviewProvider { asLink: false, disclosureIndicator: true) .previewLayout(.sizeThatFits) + */ } From f484f895349899daa281f98ffe22fe992ed0c322 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 19:48:30 +0100 Subject: [PATCH 37/99] Passes content from a variable --- DuckDuckGo/SettingsCell.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index ffd62b897c..40ede5625b 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -319,8 +319,7 @@ struct SettingsCellView_Previews: PreviewProvider { disclosureIndicator: false) .previewLayout(.sizeThatFits) - /* - SettingsCustomCell(content: { + let cellContent: () -> some View = { HStack(spacing: 15) { Image(systemName: "hand.wave") .foregroundColor(.orange) @@ -342,11 +341,12 @@ struct SettingsCellView_Previews: PreviewProvider { .foregroundColor(.orange) .imageScale(.large) } - }, action: {}, - asLink: false, - disclosureIndicator: true) - .previewLayout(.sizeThatFits) - */ + } + SettingsCustomCell(content: cellContent, + action: {}, + asLink: false, + disclosureIndicator: true) + .previewLayout(.sizeThatFits) } From 5521fd0dd819e1710e761ad8f6c52ef7285ae640 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 19:58:11 +0100 Subject: [PATCH 38/99] Make disclosure indicator conditional --- DuckDuckGo/SettingsCell.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 40ede5625b..2123139188 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -237,7 +237,9 @@ struct SettingsCustomCell: View { HStack { content Spacer() - SettingsCellComponents.chevron + if disclosureIndicator { + SettingsCellComponents.chevron + } } .onTapGesture(perform: action) } @@ -342,10 +344,7 @@ struct SettingsCellView_Previews: PreviewProvider { .imageScale(.large) } } - SettingsCustomCell(content: cellContent, - action: {}, - asLink: false, - disclosureIndicator: true) + SettingsCustomCell(content: cellContent) .previewLayout(.sizeThatFits) From ad637f40edadcc19c60d2c28157605380c26eef9 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Dec 2023 20:05:08 +0100 Subject: [PATCH 39/99] Comment out cell --- DuckDuckGo/SettingsCell.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index 2123139188..b8c46a1bdb 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -344,8 +344,10 @@ struct SettingsCellView_Previews: PreviewProvider { .imageScale(.large) } } - SettingsCustomCell(content: cellContent) - .previewLayout(.sizeThatFits) + // For some unknown reason, this breaks on CI, but works normally + // Perhaps an XCODE version issue? + // SettingsCustomCell(content: cellContent) + // .previewLayout(.sizeThatFits) } From 970c2831a251fd2d231967dc98e26a2ef7c333e6 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Mon, 11 Dec 2023 05:52:51 -0800 Subject: [PATCH 40/99] Add additional debug options to the VPN debug menu (#2251) --- DuckDuckGo/Debug.storyboard | 15 +- ...NetworkProtectionDebugViewController.swift | 304 +++++++++++++++++- 2 files changed, 307 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 9b09d31187..8e770932b8 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -662,12 +662,21 @@ - + + + + @@ -797,13 +806,13 @@ - + - + diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index ffa9d42df0..eb6e272c9f 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -17,6 +17,8 @@ // limitations under the License. // +// swiftlint:disable file_length + import UIKit #if !NETWORK_PROTECTION @@ -27,26 +29,34 @@ final class NetworkProtectionDebugViewController: UITableViewController { #else +import Common +import Network +import NetworkExtension import NetworkProtection +// swiftlint:disable:next type_body_length final class NetworkProtectionDebugViewController: UITableViewController { private let titles = [ Sections.keychain: "Keychain", Sections.debugFeature: "Debug Features", Sections.simulateFailure: "Simulate Failure", Sections.registrationKey: "Registration Key", - Sections.notifications: "Notifications" + Sections.notifications: "Notifications", + Sections.networkPath: "Network Path", + Sections.connectionTest: "Connection Test", + Sections.vpnConfiguration: "VPN Configuration" ] enum Sections: Int, CaseIterable { - case keychain case debugFeature case simulateFailure case registrationKey case notifications - + case connectionTest + case networkPath + case vpnConfiguration } enum KeychainRows: Int, CaseIterable { @@ -60,7 +70,6 @@ final class NetworkProtectionDebugViewController: UITableViewController { } enum SimulateFailureRows: Int, CaseIterable { - case tunnelFailure case controllerFailure case crashFatalError @@ -69,19 +78,48 @@ final class NetworkProtectionDebugViewController: UITableViewController { } enum RegistrationKeyRows: Int, CaseIterable { - case expireNow - } enum NotificationsRows: Int, CaseIterable { - case triggerTestNotification + } + + enum NetworkPathRows: Int, CaseIterable { + case networkPath + } + + enum ConnectionTestRows: Int, CaseIterable { + case runConnectionTest + } + enum ConfigurationRows: Int, CaseIterable { + case baseConfigurationData + case fullProtocolConfigurationData } + // MARK: Properties + private let debugFeatures: NetworkProtectionDebugFeatures private let tokenStore: NetworkProtectionTokenStore + private let pathMonitor = NWPathMonitor() + + private var currentNetworkPath: String? + private var baseConfigurationData: String? + private var fullProtocolConfigurationData: String? + + private struct ConnectionTestResult { + let interface: String + let version: String + let success: Bool + let errorDescription: String? + } + + private var connectionTestResults: [ConnectionTestResult] = [] + private var connectionTestResultError: String? + private let connectionTestQueue = DispatchQueue(label: "com.duckduckgo.ios.vpnDebugConnectionTestQueue") + + // MARK: Lifecycle init?(coder: NSCoder, tokenStore: NetworkProtectionTokenStore, @@ -97,6 +135,14 @@ final class NetworkProtectionDebugViewController: UITableViewController { self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore()) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + loadConfigurationData() + startPathMonitor() + } + + // MARK: Table View + override func numberOfSections(in tableView: UITableView) -> Int { return Sections.allCases.count } @@ -106,10 +152,13 @@ final class NetworkProtectionDebugViewController: UITableViewController { return titles[section] } + // swiftlint:disable:next cyclomatic_complexity override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + cell.textLabel?.font = .daxBodyRegular() cell.detailTextLabel?.text = nil + cell.accessoryType = .none switch Sections(rawValue: indexPath.section) { @@ -133,6 +182,15 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .notifications: configure(cell, forNotificationRow: indexPath.row) + case .networkPath: + configure(cell, forNetworkPathRow: indexPath.row) + + case .connectionTest: + configure(cell, forConnectionTestRow: indexPath.row) + + case .vpnConfiguration: + configure(cell, forConfigurationRow: indexPath.row) + case.none: break } @@ -147,11 +205,15 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .simulateFailure: return SimulateFailureRows.allCases.count case .registrationKey: return RegistrationKeyRows.allCases.count case .notifications: return NotificationsRows.allCases.count + case .networkPath: return NetworkPathRows.allCases.count + case .connectionTest: return ConnectionTestRows.allCases.count + connectionTestResults.count + case .vpnConfiguration: return ConfigurationRows.allCases.count case .none: return 0 } } + // swiftlint:disable:next cyclomatic_complexity override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch Sections(rawValue: indexPath.section) { case .keychain: @@ -164,9 +226,19 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .simulateFailure: didSelectSimulateFailure(at: indexPath) case .registrationKey: - didSelectRegistationKeyAction(at: indexPath) + didSelectRegistrationKeyAction(at: indexPath) case .notifications: didSelectTestNotificationAction(at: indexPath) + case .networkPath: + break + case .connectionTest: + if indexPath.row == connectionTestResults.count { + Task { + await runConnectionTest() + } + } + case .vpnConfiguration: + break case .none: break } @@ -254,7 +326,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { } } - private func didSelectRegistationKeyAction(at indexPath: IndexPath) { + private func didSelectRegistrationKeyAction(at indexPath: IndexPath) { switch RegistrationKeyRows(rawValue: indexPath.row) { case .expireNow: Task { @@ -287,6 +359,184 @@ final class NetworkProtectionDebugViewController: UITableViewController { } } + // MARK: Network Path + + private func configure(_ cell: UITableViewCell, forNetworkPathRow row: Int) { + cell.textLabel?.font = .monospacedSystemFont(ofSize: 13.0, weight: .regular) + cell.textLabel?.text = currentNetworkPath ?? "Loading path..." + } + + private func startPathMonitor() { + pathMonitor.pathUpdateHandler = { [weak self] path in + var pathDescription: String = """ + Status: \(path.status) + Interfaces: \(path.availableInterfaces) + Gateways: \(path.gateways) + Is Expensive: \(path.isExpensive) + Is Constrained: \(path.isConstrained) + Supports DNS: \(path.supportsDNS) + Supports IPv4: \(path.supportsIPv4) + Supports IPv6: \(path.supportsIPv6) + """ + + if #available(iOS 14.2, *), path.status == .unsatisfied { + pathDescription.append("\nUnsatisfied Reason: \(path.unsatisfiedReason)") + } + + self?.currentNetworkPath = pathDescription + self?.tableView.reloadData() + } + + pathMonitor.start(queue: .main) + } + + // MARK: Connection Test + + private func configure(_ cell: UITableViewCell, forConnectionTestRow row: Int) { + if row == connectionTestResults.count { + cell.textLabel?.text = "Run Connection Test" + } else { + let result = self.connectionTestResults[row] + + if result.success { + cell.textLabel?.text = "✅ \(result.interface) IP\(result.version)" + } else { + cell.textLabel?.text = "❌ \(result.interface) IP\(result.version), error: \(result.errorDescription ?? "None")" + } + } + } + + @MainActor + private func runConnectionTest() async { + let interfaces = pathMonitor.currentPath.availableInterfaces + + guard !interfaces.isEmpty else { + self.connectionTestResultError = "No interfaces available" + return + } + + var results = [ConnectionTestResult]() + + for interface in interfaces { + let ipv4Result = await testConnection(interface: interface, version: .v4) + let ipv6Result = await testConnection(interface: interface, version: .v6) + + results.append(ipv4Result) + results.append(ipv6Result) + } + + self.connectionTestResults = results + self.tableView.reloadData() + } + + private func testConnection(interface: NWInterface, version: NWProtocolIP.Options.Version) async -> ConnectionTestResult { + let interfaceString = interface.debugDescription + let versionString = String(describing: version) + + let endpoint = NWEndpoint.hostPort(host: .name("apple.com", nil), port: .https) + let parameters = NWParameters.tcp + parameters.requiredInterface = interface + + // swiftlint:disable force_cast + let ip = parameters.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options + ip.version = version + // swiftlint:enable force_cast + + let connection = NWConnection(to: endpoint, using: parameters) + let stateUpdateStream = connection.stateUpdateStream + connection.start(queue: self.connectionTestQueue) + + defer { + connection.cancel() + } + + do { + return try await withTimeout(.seconds(5)) { + for await state in stateUpdateStream { + if case .ready = state { + return ConnectionTestResult( + interface: connection.endpoint.interface?.debugDescription ?? interfaceString, + version: versionString, + success: true, + errorDescription: nil + ) + } + + if case .waiting(let error) = state { + return ConnectionTestResult( + interface: interface.debugDescription, + version: versionString, + success: false, + errorDescription: error.localizedDescription + ) + } + } + + let currentConnectionState = connection.state + return ConnectionTestResult( + interface: interfaceString, + version: versionString, + success: false, + errorDescription: String(describing: currentConnectionState) + ) + } + } catch { + return ConnectionTestResult(interface: interfaceString, version: versionString, success: false, errorDescription: "Timeout reached") + } + + } + + // MARK: Configuration + + private func configure(_ cell: UITableViewCell, forConfigurationRow row: Int) { + cell.textLabel?.font = .monospacedSystemFont(ofSize: 13.0, weight: .regular) + + switch ConfigurationRows(rawValue: row) { + case .baseConfigurationData: + cell.textLabel?.text = baseConfigurationData ?? "Loading base configuration..." + case .fullProtocolConfigurationData: + cell.textLabel?.text = fullProtocolConfigurationData ?? "Loading protocol configuration..." + case .none: + assertionFailure("Couldn't map configuration row") + } + + } + + private func loadConfigurationData() { + Task { @MainActor in + do { + let tunnels = try await NETunnelProviderManager.loadAllFromPreferences() + + guard let tunnel = tunnels.first else { + self.baseConfigurationData = "No configurations found" + self.fullProtocolConfigurationData = "" + return + } + + guard let protocolConfiguration = tunnel.protocolConfiguration else { + self.baseConfigurationData = "No protocol configuration found" + self.fullProtocolConfigurationData = "" + return + } + + let configurationDescriptionString = String(describing: tunnel) + .replacingOccurrences(of: " ", with: " ") + + let protocolConfigurationString = String(describing: protocolConfiguration) + .replacingOccurrences(of: " ", with: "") + .dropping(prefix: "\n") + + self.baseConfigurationData = "CONFIGURATION OVERVIEW:\n\n" + configurationDescriptionString + self.fullProtocolConfigurationData = "FULL PROTOCOL CONFIGURATION:\n\n" + protocolConfigurationString + } catch { + self.baseConfigurationData = "Failed to load configuration: \(error.localizedDescription)" + self.fullProtocolConfigurationData = "" + } + + self.tableView.reloadData() + } + } + // MARK: Selection Actions private func clearAuthToken() { @@ -294,4 +544,40 @@ final class NetworkProtectionDebugViewController: UITableViewController { } } + +extension NWConnection { + + var stateUpdateStream: AsyncStream { + let (stream, continuation) = AsyncStream.makeStream(of: State.self) + + class ConnectionLifeTimeTracker { + let continuation: AsyncStream.Continuation + init(continuation: AsyncStream.Continuation) { + self.continuation = continuation + } + deinit { + continuation.finish() + } + } + let connectionLifeTimeTracker = ConnectionLifeTimeTracker(continuation: continuation) + + self.stateUpdateHandler = { state in + withExtendedLifetime(connectionLifeTimeTracker) { + _=continuation.yield(state) + + switch state { + case .cancelled, .failed: + continuation.finish() + default: break + } + } + } + + return stream + } + +} + #endif + +// swiftlint:enable file_length From 9c0b336826c9e7bb2342df8020b0b4315f76a066 Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Mon, 11 Dec 2023 15:50:39 +0100 Subject: [PATCH 41/99] Quality metrics for Sync (#2254) Task/Issue URL: https://app.asana.com/0/0/1206127893364843/f Description: Implement relevant quality metrics for Sync. --- Core/PixelEvent.swift | 15 +++++++ Core/SyncBookmarksAdapter.swift | 7 +++- Core/SyncCredentialsAdapter.swift | 7 +++- Core/SyncDataProviders.swift | 19 +++++++-- Core/SyncMetricsEventsHandler.swift | 40 +++++++++++++++++++ Core/SyncSettingsAdapter.swift | 7 +++- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 8 ++++ .../FavoritesDisplayModeSyncHandler.swift | 2 +- ...cSettingsViewController+SyncDelegate.swift | 2 + DuckDuckGo/SyncSettingsViewController.swift | 2 + 12 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 Core/SyncMetricsEventsHandler.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 25d9f0421d..d35c102063 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -21,6 +21,7 @@ import Foundation import BrowserServicesKit import Bookmarks import Configuration +import DDGSync // swiftlint:disable file_length extension Pixel { @@ -504,6 +505,13 @@ extension Pixel { case bookmarksMigrationCouldNotRemoveOldStore case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders + case syncSignupDirect + case syncSignupConnect + case syncLogin + case syncDaily + case syncDuckAddressOverride + case syncSuccessRateDaily + case syncLocalTimestampResolutionTriggered(Feature) case syncFailedToMigrate case syncFailedToLoadAccount case syncFailedToSetupEngine @@ -1022,6 +1030,13 @@ extension Pixel.Event { case .bookmarksMigrationCouldNotRemoveOldStore: return "m_d_bookmarks_migration_could_not_remove_old_store" case .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders: return "m_d_bookmarks_migration_could_not_prepare_multiple_favorite_folders" + case .syncSignupDirect: return "m_sync_signup_direct" + case .syncSignupConnect: return "m_sync_signup_connect" + case .syncLogin: return "m_sync_login" + case .syncDaily: return "m_sync_daily" + case .syncDuckAddressOverride: return "m_sync_duck_address_override" + case .syncSuccessRateDaily: return "m_sync_success_rate_daily" + case .syncLocalTimestampResolutionTriggered(let feature): return "m_sync_\(feature.name)_local_timestamp_resolution_triggered" case .syncFailedToMigrate: return "m_d_sync_failed_to_migrate" case .syncFailedToLoadAccount: return "m_d_sync_failed_to_load_account" case .syncFailedToSetupEngine: return "m_d_sync_failed_to_setup_engine" diff --git a/Core/SyncBookmarksAdapter.swift b/Core/SyncBookmarksAdapter.swift index 6063cb766c..0d3a4acf2f 100644 --- a/Core/SyncBookmarksAdapter.swift +++ b/Core/SyncBookmarksAdapter.swift @@ -119,7 +119,11 @@ public final class SyncBookmarksAdapter { } } - public func setUpProviderIfNeeded(database: CoreDataDatabase, metadataStore: SyncMetadataStore) { + public func setUpProviderIfNeeded( + database: CoreDataDatabase, + metadataStore: SyncMetadataStore, + metricsEventsHandler: EventMapping? = nil + ) { guard provider == nil else { return } @@ -129,6 +133,7 @@ public final class SyncBookmarksAdapter { let provider = BookmarksProvider( database: database, metadataStore: metadataStore, + metricsEvents: metricsEventsHandler, syncDidUpdateData: { [weak self] in self?.syncDidCompleteSubject.send() Self.isSyncBookmarksPaused = false diff --git a/Core/SyncCredentialsAdapter.swift b/Core/SyncCredentialsAdapter.swift index d98f7ba068..7d2acb7849 100644 --- a/Core/SyncCredentialsAdapter.swift +++ b/Core/SyncCredentialsAdapter.swift @@ -63,7 +63,11 @@ public final class SyncCredentialsAdapter { } } - public func setUpProviderIfNeeded(secureVaultFactory: AutofillVaultFactory, metadataStore: SyncMetadataStore) { + public func setUpProviderIfNeeded( + secureVaultFactory: AutofillVaultFactory, + metadataStore: SyncMetadataStore, + metricsEventsHandler: EventMapping? = nil + ) { guard provider == nil else { return } @@ -73,6 +77,7 @@ public final class SyncCredentialsAdapter { secureVaultFactory: secureVaultFactory, secureVaultErrorReporter: secureVaultErrorReporter, metadataStore: metadataStore, + metricsEvents: metricsEventsHandler, syncDidUpdateData: { [weak self] in self?.syncDidCompleteSubject.send() Self.isSyncCredentialsPaused = false diff --git a/Core/SyncDataProviders.swift b/Core/SyncDataProviders.swift index a70b4acd4f..f655abc5ae 100644 --- a/Core/SyncDataProviders.swift +++ b/Core/SyncDataProviders.swift @@ -38,9 +38,21 @@ public class SyncDataProviders: DataProvidersSource { return [] } - bookmarksAdapter.setUpProviderIfNeeded(database: bookmarksDatabase, metadataStore: syncMetadata) - credentialsAdapter.setUpProviderIfNeeded(secureVaultFactory: secureVaultFactory, metadataStore: syncMetadata) - settingsAdapter.setUpProviderIfNeeded(metadataDatabase: syncMetadataDatabase, metadataStore: syncMetadata) + bookmarksAdapter.setUpProviderIfNeeded( + database: bookmarksDatabase, + metadataStore: syncMetadata, + metricsEventsHandler: metricsEventsHandler + ) + credentialsAdapter.setUpProviderIfNeeded( + secureVaultFactory: secureVaultFactory, + metadataStore: syncMetadata, + metricsEventsHandler: metricsEventsHandler + ) + settingsAdapter.setUpProviderIfNeeded( + metadataDatabase: syncMetadataDatabase, + metadataStore: syncMetadata, + metricsEventsHandler: metricsEventsHandler + ) let providers: [Any] = [ bookmarksAdapter.provider as Any, @@ -122,6 +134,7 @@ public class SyncDataProviders: DataProvidersSource { private var isDatabaseCleanersSetUp: Bool = false private var syncMetadata: SyncMetadataStore? private var syncAuthStateDidChangeCancellable: AnyCancellable? + private let metricsEventsHandler = SyncMetricsEventsHandler() private let syncMetadataDatabase: CoreDataDatabase = SyncMetadataDatabase.make() private let bookmarksDatabase: CoreDataDatabase diff --git a/Core/SyncMetricsEventsHandler.swift b/Core/SyncMetricsEventsHandler.swift new file mode 100644 index 0000000000..4b288c4478 --- /dev/null +++ b/Core/SyncMetricsEventsHandler.swift @@ -0,0 +1,40 @@ +// +// SyncMetricsEventsHandler.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 Common +import SyncDataProviders +import Foundation + +public class SyncMetricsEventsHandler: EventMapping { + + public init() { + super.init { event, _, _, _ in + switch event { + case .overrideEmailProtectionSettings: + Pixel.fire(pixel: .syncDuckAddressOverride) + case .localTimestampResolutionTriggered(let feature): + Pixel.fire(pixel: .syncLocalTimestampResolutionTriggered(feature)) + } + } + } + + override init(mapping: @escaping EventMapping.Mapping) { + fatalError("Use init()") + } +} diff --git a/Core/SyncSettingsAdapter.swift b/Core/SyncSettingsAdapter.swift index 9bfe132bb4..9b310a9291 100644 --- a/Core/SyncSettingsAdapter.swift +++ b/Core/SyncSettingsAdapter.swift @@ -38,7 +38,11 @@ public final class SyncSettingsAdapter { public func updateDatabaseCleanupSchedule(shouldEnable: Bool) { } - public func setUpProviderIfNeeded(metadataDatabase: CoreDataDatabase, metadataStore: SyncMetadataStore) { + public func setUpProviderIfNeeded( + metadataDatabase: CoreDataDatabase, + metadataStore: SyncMetadataStore, + metricsEventsHandler: EventMapping? = nil + ) { guard provider == nil else { return } @@ -50,6 +54,7 @@ public final class SyncSettingsAdapter { metadataDatabase: metadataDatabase, metadataStore: metadataStore, settingsHandlers: settingHandlers + [emailProtectionSyncHandler], + metricsEvents: metricsEventsHandler, syncDidUpdateData: { [weak self] in self?.syncDidCompleteSubject.send() } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 97099dc4d7..80fcdad943 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -246,6 +246,7 @@ 31DD208427395A5A008FB313 /* VoiceSearchHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DD208327395A5A008FB313 /* VoiceSearchHelper.swift */; }; 31E69A63280F4CB600478327 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = 31E69A62280F4CB600478327 /* DuckUI */; }; 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */; }; + 372A0FF02B2389590033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */; }; 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */; }; 373608922ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373608912ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift */; }; 373608932ABB432600629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373608912ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift */; }; @@ -1299,6 +1300,7 @@ 31CC224828369B38001654A4 /* AutofillLoginSettingsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSettingsListViewController.swift; sourceTree = ""; }; 31DD208327395A5A008FB313 /* VoiceSearchHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceSearchHelper.swift; sourceTree = ""; }; 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListItemViewModel.swift; sourceTree = ""; }; + 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncMetricsEventsHandler.swift; sourceTree = ""; }; 3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesDisplayModeStorage.swift; sourceTree = ""; }; 373608912ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoritesDisplayMode+UserDefaults.swift"; sourceTree = ""; }; 37445F962A155F7C0029F789 /* SyncDataProviders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncDataProviders.swift; sourceTree = ""; }; @@ -3392,6 +3394,7 @@ 37445F962A155F7C0029F789 /* SyncDataProviders.swift */, 37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */, 37CEFCAB2A673B90001EF741 /* CredentialsCleanupErrorHandling.swift */, + 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */, ); name = Sync; sourceTree = ""; @@ -6960,6 +6963,7 @@ 85D2187B24BF9F85004373D2 /* FaviconUserScript.swift in Sources */, 37FD780F2A29E28B00B36DB1 /* SyncErrorHandler.swift in Sources */, 85F21DC621145DD5002631A6 /* global.swift in Sources */, + 372A0FF02B2389590033BF7F /* SyncMetricsEventsHandler.swift in Sources */, F41C2DA326C1925700F9A760 /* BookmarksAndFolders.xcdatamodeld in Sources */, F4F6DFBA26EFF28A00ED7E12 /* BookmarkObjects.swift in Sources */, EE7A92872AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift in Sources */, @@ -9222,7 +9226,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 92.1.0; + version = 93.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 88f08bd738..1bc7f2baf8 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": "6d67e41feb5d7d22fec40fcede6b82eb88673900", - "version": "92.1.0" + "revision": "c85592d01647b222748bb751ff9cd980399d926b", + "version": "93.0.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 627e2b753b..a7b87702f1 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -80,6 +80,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private(set) var syncDataProviders: SyncDataProviders! private var syncDidFinishCancellable: AnyCancellable? private var syncStateCancellable: AnyCancellable? + private var isSyncInProgressCancellable: AnyCancellable? // MARK: lifecycle @@ -286,6 +287,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { syncService.initializeIfNeeded() self.syncService = syncService + isSyncInProgressCancellable = syncService.isSyncInProgressPublisher + .filter { $0 } + .prefix(1) + .sink { _ in + DailyPixel.fire(pixel: .syncDaily) + } + #if APP_TRACKING_PROTECTION let main = MainViewController(bookmarksDatabase: bookmarksDatabase, bookmarksDatabaseCleaner: syncDataProviders.bookmarksAdapter.databaseCleaner, diff --git a/DuckDuckGo/FavoritesDisplayModeSyncHandler.swift b/DuckDuckGo/FavoritesDisplayModeSyncHandler.swift index 3622af2270..cfe949f3b3 100644 --- a/DuckDuckGo/FavoritesDisplayModeSyncHandler.swift +++ b/DuckDuckGo/FavoritesDisplayModeSyncHandler.swift @@ -28,7 +28,7 @@ final class FavoritesDisplayModeSyncHandler: FavoritesDisplayModeSyncHandlerBase appSettings.favoritesDisplayMode.description } - override func setValue(_ value: String?) throws { + override func setValue(_ value: String?, shouldDetectOverride: Bool) throws { if let value, let displayMode = FavoritesDisplayMode(value) { appSettings.favoritesDisplayMode = displayMode NotificationCenter.default.post(name: AppUserDefaults.Notifications.favoritesDisplayModeChange, object: nil) diff --git a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift index a47505c79d..981cfe0a9e 100644 --- a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift +++ b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift @@ -17,6 +17,7 @@ // limitations under the License. // +import Core import UIKit import SwiftUI import SyncUI @@ -55,6 +56,7 @@ extension SyncSettingsViewController: SyncManagementViewModelDelegate { self.dismissPresentedViewController() self.showPreparingSync() try await syncService.createAccount(deviceName: deviceName, deviceType: deviceType) + Pixel.fire(pixel: .syncSignupDirect) self.rootView.model.syncEnabled(recoveryCode: recoveryCode) self.refreshDevices() navigationController?.topViewController?.dismiss(animated: true, completion: showRecoveryPDF) diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index f523ca0990..2fec903a79 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -231,6 +231,7 @@ extension SyncSettingsViewController: ScanOrPasteCodeViewModelDelegate { func loginAndShowDeviceConnected(recoveryKey: SyncCode.RecoveryKey) async throws { let registeredDevices = try await syncService.login(recoveryKey, deviceName: deviceName, deviceType: deviceType) mapDevices(registeredDevices) + Pixel.fire(pixel: .syncLogin) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.dismissVCAndShowRecoveryPDF() } @@ -269,6 +270,7 @@ extension SyncSettingsViewController: ScanOrPasteCodeViewModelDelegate { showPreparingSync() if syncService.account == nil { try await syncService.createAccount(deviceName: deviceName, deviceType: deviceType) + Pixel.fire(pixel: .syncSignupConnect) self.dismissVCAndShowRecoveryPDF() shouldShowSyncEnabled = false rootView.model.syncEnabled(recoveryCode: recoveryCode) From af736fce73cf1225643936efe5e786fd9c7cac8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Mon, 11 Dec 2023 17:47:59 +0100 Subject: [PATCH 42/99] Release 7.101.0 (#2257) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 309 +++++++----------- DuckDuckGo/Settings.bundle/Root.plist | 2 +- fastlane/metadata/default/release_notes.txt | 3 +- 5 files changed, 127 insertions(+), 193 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 3e6917b72b..3cfb3a57fe 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.99.0 +MARKETING_VERSION = 7.101.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 81a313c2df..b4d249dd65 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 = "\"7c6169d648700908e0dfc5904d640c37\"" - public static let embeddedDataSHA = "e3cd448b68a22fe6c9563deeb4c7d2a62ae947bc655ff33b3fc030bba766672c" + public static let embeddedDataETag = "\"9f95a5896c461768903058315bf6b931\"" + public static let embeddedDataSHA = "33bbcc8e715e93f6a6052b92d990860758774d7cf4828b6aedcc958b9f46f50a" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index 092015bfeb..a7e3c08b58 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1701253939396, + "version": 1702059545091, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -1061,7 +1061,7 @@ }, "customUserAgent": { "settings": { - "defaultPolicy": "ddgFixed", + "defaultPolicy": "closest", "ddgFixedSites": [], "ddgDefaultSites": [ { @@ -1070,131 +1070,7 @@ } ], "closestUserAgent": { - "versions": [ - "392", - "390", - "387", - "385", - "384", - "381", - "380", - "378", - "375", - "373", - "372", - "369", - "368", - "366", - "363", - "362", - "359", - "357", - "355", - "354", - "352", - "350", - "347", - "346", - "344", - "342", - "339", - "337", - "335", - "333", - "331", - "329", - "328", - "326", - "323", - "321", - "320", - "318", - "315", - "313", - "312", - "309", - "308", - "306", - "304", - "302", - "300", - "298", - "295", - "293", - "291", - "289", - "287", - "285", - "284", - "281", - "279", - "278", - "276", - "274", - "271", - "269", - "268", - "266", - "263", - "262", - "260", - "257", - "256", - "254", - "251", - "249", - "248", - "245", - "244", - "241", - "239", - "238", - "236", - "234", - "232", - "230", - "228", - "226", - "223", - "222", - "220", - "218", - "215", - "213", - "211", - "209", - "208", - "205", - "203", - "202", - "199", - "197", - "196", - "194", - "191", - "190", - "187", - "185", - "184", - "182", - "180", - "178", - "176", - "173", - "171", - "169", - "167", - "165", - "163", - "161", - "160", - "157", - "155", - "154", - "152", - "150", - "148" - ] + "versions": [] }, "omitApplicationSites": [ { @@ -1238,42 +1114,6 @@ }, { "domain": "sundancecatalog.com" - }, - { - "domain": "cvs.com" - }, - { - "domain": "facebook.com" - }, - { - "domain": "finewineandgoodspirits.com" - }, - { - "domain": "formula1.com" - }, - { - "domain": "gigisplayhouse.org" - }, - { - "domain": "hulu.com" - }, - { - "domain": "instagram.com" - }, - { - "domain": "republicservices.com" - }, - { - "domain": "xfinity.com" - }, - { - "domain": "homedepot.ca" - }, - { - "domain": "unclaimedmoneyinfo.com" - }, - { - "domain": "timesmachine.nytimes.com" } ], "omitVersionSites": [ @@ -1296,7 +1136,20 @@ }, "exceptions": [], "state": "enabled", - "hash": "4c62237a3eeaf9cfec814fc3fb7af282" + "hash": "161a405c1f003e7d2c0df2a14d873389" + }, + "dbp": { + "state": "disabled", + "features": { + "waitlist": { + "state": "disabled" + }, + "waitlistBetaActive": { + "state": "disabled" + } + }, + "exceptions": [], + "hash": "ba52a36920a4a76343fc3c44d98936f9" }, "duckPlayer": { "exceptions": [], @@ -2192,6 +2045,15 @@ } ] }, + { + "domain": "dpreview.com", + "rules": [ + { + "selector": ".ad-wrapper", + "type": "override" + } + ] + }, { "domain": "ebay.com", "rules": [ @@ -2540,6 +2402,23 @@ } ] }, + { + "domain": "healthline.com", + "rules": [ + { + "selector": "[data-testid='header-leaderboard']", + "type": "hide-empty" + }, + { + "selector": "[data-testid='sticky-inline-ad']", + "type": "closest-empty" + }, + { + "selector": "[data-ad='true']", + "type": "closest-empty" + } + ] + }, { "domain": "hindustantimes.com", "rules": [ @@ -3747,7 +3626,7 @@ ] }, "state": "enabled", - "hash": "f7c00905a329790def09ac2296a0e1da" + "hash": "5a0bfa6ba53c5d1f14ef839a136f2f59" }, "exceptionHandler": { "exceptions": [ @@ -4741,6 +4620,7 @@ { "rule": "analytics.analytics-egain.com/onetag/", "domains": [ + "landsend.com", "support.norton.com" ] } @@ -5240,6 +5120,7 @@ "ah.nl", "nytimes.com", "rocketnews24.com", + "uwbadgers.com", "wunderground.com", "youmath.it" ] @@ -5403,12 +5284,6 @@ }, "facebook.net": { "rules": [ - { - "rule": "connect.facebook.net/en_UK/sdk.js", - "domains": [ - "globalcyclingnetwork.com" - ] - }, { "rule": "connect.facebook.net/en_US/sdk.js", "domains": [ @@ -5679,6 +5554,7 @@ { "rule": "imasdk.googleapis.com/js/sdkloader/ima3.js", "domains": [ + "arkadium.com", "bloomberg.com", "gamak.tv", "games.washingtonpost.com", @@ -5706,6 +5582,7 @@ { "rule": "pagead2.googlesyndication.com/pagead/js/adsbygoogle.js", "domains": [ + "daotranslate.com", "drakescans.com", "duden.de", "magicgameworld.com", @@ -5745,7 +5622,8 @@ { "rule": "googletagmanager.com/gtag/js", "domains": [ - "abril.com.br" + "abril.com.br", + "cosmicbook.news" ] } ] @@ -6027,6 +5905,16 @@ } ] }, + "inmobi.com": { + "rules": [ + { + "rule": "cmp.inmobi.com", + "domains": [ + "express.co.uk" + ] + } + ] + }, "inq.com": { "rules": [ { @@ -6152,7 +6040,8 @@ "rule": "www.klaviyo.com/media/js/public/klaviyo_subscribe.js", "domains": [ "fearofgod.com", - "shopyalehome.com" + "shopyalehome.com", + "silhouetteu.com" ] }, { @@ -6282,6 +6171,16 @@ } ] }, + "mediavine.com": { + "rules": [ + { + "rule": "scripts.mediavine.com/tags/cosmic-book-news.js", + "domains": [ + "cosmicbook.news" + ] + } + ] + }, "medicare.gov": { "rules": [ { @@ -6424,6 +6323,16 @@ } ] }, + "onesignal.com": { + "rules": [ + { + "rule": "cdn.onesignal.com/sdks/OneSignalSDK.js", + "domains": [ + "cosmicbook.news" + ] + } + ] + }, "onlyfans.com": { "rules": [ { @@ -6679,17 +6588,6 @@ } ] }, - "protection-widget.route.com": { - "rules": [ - { - "rule": "protection-widget.route.com/protect.core.js", - "domains": [ - "littleunicorn.com", - "olfactif.com" - ] - } - ] - }, "pubmatic.com": { "rules": [ { @@ -6787,7 +6685,7 @@ { "rule": "protection-widget.route.com/protect.core.js", "domains": [ - "littleunicorn.com" + "" ] } ] @@ -6994,6 +6892,16 @@ } ] }, + "tfaforms.net": { + "rules": [ + { + "rule": "tfaforms.net", + "domains": [ + "" + ] + } + ] + }, "theplatform.com": { "rules": [ { @@ -7062,7 +6970,14 @@ "rule": "widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js", "domains": [ "azurestandard.com", - "domesticandgeneral.com" + "domesticandgeneral.com", + "www.hotpoint.co.uk" + ] + }, + { + "rule": "widget.trustpilot.com/bootstrap/v5/tp.widget.sync.bootstrap.min.js", + "domains": [ + "www.hotpoint.co.uk" ] } ] @@ -7171,6 +7086,16 @@ } ] }, + "visualwebsiteoptimizer.com": { + "rules": [ + { + "rule": "dev.visualwebsiteoptimizer.com/j.php", + "domains": [ + "searchhudforeclosures.com" + ] + } + ] + }, "voxmedia.com": { "rules": [ { @@ -7181,6 +7106,16 @@ } ] }, + "wovn.io": { + "rules": [ + { + "rule": "j.wovn.io/1", + "domains": [ + "tamashiiweb.com" + ] + } + ] + }, "wpadmngr.com": { "rules": [ { @@ -7379,7 +7314,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "1ce506662b48fccc1bf37a3133fd3f1e" + "hash": "d9e22bdb82a33762f16ce921ef991054" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 4af486a5a5..8180e96bfe 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.99.0 + 7.101.0 Key version Title diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index 318421afc6..6cd7a9e939 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,4 +1,3 @@ -We fixed a rare issue that caused text selection in the address bar to not work as expected. -Bug fixes and other improvements. +- Bug fixes and other improvements. Join our fully distributed team and help raise the standard of trust online! https://duckduckgo.com/hiring From 72fe6c6735524bdfe21eedb6bcf45df7df43f031 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Mon, 11 Dec 2023 13:00:22 -0800 Subject: [PATCH 43/99] Fix VPN IPv6 connectivity (#2258) Task/Issue URL: https://app.asana.com/0/0/1206136376883995/f Tech Design URL: CC: Description: This PR fixes IPv6 connectivity. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 80fcdad943..5dbab49ad4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9226,7 +9226,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 93.0.0; + version = 94.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1bc7f2baf8..1f616e1038 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": "c85592d01647b222748bb751ff9cd980399d926b", - "version": "93.0.0" + "revision": "e4f4ae624174c1398d345cfc387db38f8f69986d", + "version": "94.0.0" } }, { From b2b07cb94e59f0b8215106286712a30e74b9e47a Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Tue, 12 Dec 2023 14:01:47 +0100 Subject: [PATCH 44/99] Run sync e2e on multiple OS versions (#2256) --- .github/workflows/sync-end-to-end.yml | 28 ++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index 3dc49732fa..0125ab669a 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -40,7 +40,7 @@ jobs: run: | set -o pipefail && xcodebuild \ -scheme "DuckDuckGo" \ - -destination "platform=iOS Simulator,name=iPhone 14,OS=16.4" \ + -destination "platform=iOS Simulator,name=iPhone 14" \ -derivedDataPath "DerivedData" \ | tee xcodebuild.log @@ -50,8 +50,8 @@ jobs: with: debug: true - - name: Sync e2e tests - uses: mobile-dev-inc/action-maestro-cloud@v1.6.0 + - name: Sync e2e tests - iOS 15 + uses: mobile-dev-inc/action-maestro-cloud@v1.8.0 with: api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} app-file: DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app @@ -60,6 +60,28 @@ jobs: env: | CODE=${{ steps.sync-recovery-code.outputs.recovery-code }} + - name: Sync e2e tests - iOS 16 + uses: mobile-dev-inc/action-maestro-cloud@v1.8.0 + with: + api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} + app-file: DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app + ios-version: 16 + workspace: .maestro + include-tags: sync + env: | + CODE=${{ steps.sync-recovery-code.outputs.recovery-code }} + + - name: Sync e2e tests - iOS 17 + uses: mobile-dev-inc/action-maestro-cloud@v1.8.0 + with: + api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} + app-file: DerivedData/Build/Products/Debug-iphonesimulator/DuckDuckGo.app + ios-version: 17 + workspace: .maestro + include-tags: sync + env: | + CODE=${{ steps.sync-recovery-code.outputs.recovery-code }} + - name: Create Asana task when workflow failed if: ${{ failure() }} run: | From 8cbb93534fa817c0ae09e1e8484ea118ea5a7b2e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 14:10:09 +0100 Subject: [PATCH 45/99] Updated packages From c5a5f65630257fa7de177218292cbe7c4a3f7530 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 24 Nov 2023 15:55:58 +0100 Subject: [PATCH 46/99] Added base subscription Packages --- DuckDuckGo.xcodeproj/project.pbxproj | 132 +++------ .../xcshareddata/swiftpm/Package.resolved | 178 ----------- LocalPackages/Account/.gitignore | 9 + LocalPackages/Account/Package.swift | 27 ++ .../Sources/Account/AccountManager.swift | 219 ++++++++++++++ .../AccountKeychainStorage.swift | 195 ++++++++++++ .../AccountStorage/AccountStorage.swift | 31 ++ .../Account/Sources/Account/Logging.swift | 56 ++++ .../Sources/Account/Services/APIService.swift | 110 +++++++ .../Account/Services/AuthService.swift | 112 +++++++ .../Services/SubscriptionService.swift | 44 +++ .../Tests/AccountTests/AccountsTests.swift | 29 ++ LocalPackages/Purchase/.gitignore | 9 + LocalPackages/Purchase/Package.swift | 24 ++ .../Sources/Purchase/PurchaseManager.swift | 277 +++++++++++++++++ .../Tests/PurchaseTests/PurchaseTests.swift | 29 ++ LocalPackages/Subscription/.gitignore | 9 + LocalPackages/Subscription/Package.swift | 34 +++ .../DebugMenu/DebugPurchaseModel.swift | 55 ++++ .../DebugMenu/DebugPurchaseView.swift | 193 ++++++++++++ .../DebugPurchaseViewController.swift | 82 ++++++ .../PurchaseInProgressViewController.swift | 75 +++++ .../DebugMenu/SubscriptionDebugMenu.swift | 211 +++++++++++++ .../Extensions/RoundedBorder.swift | 30 ++ .../PreferencesSubscriptionModel.swift | 120 ++++++++ .../PreferencesSubscriptionView.swift | 278 ++++++++++++++++++ .../AppStoreAccountManagementFlow.swift | 57 ++++ .../PurchaseFlows/AppStorePurchaseFlow.swift | 102 +++++++ .../PurchaseFlows/AppStoreRestoreFlow.swift | 54 ++++ .../BadgeBackground.colorset/Contents.json | 38 +++ .../Colors/Contents.json | 6 + .../Colors/TextPrimary.colorset/Contents.json | 38 +++ .../TextSecondary.colorset/Contents.json | 38 +++ .../Subscription.xcassets/Contents.json | 6 + .../Subscription.xcassets/Icons/Contents.json | 6 + .../apple-id-icon.imageset/Contents.json | 15 + .../apple-id-icon.imageset/apple-id-icon.pdf | Bin 0 -> 3923 bytes .../Icons/email-icon.imageset/Contents.json | 15 + .../Icons/email-icon.imageset/email-icon.pdf | Bin 0 -> 1828 bytes .../itr-service-icon.imageset/Contents.json | 15 + .../itr-service-icon.pdf | Bin 0 -> 4560 bytes .../pir-service-icon.imageset/Contents.json | 15 + .../pir-service-icon.pdf | Bin 0 -> 4271 bytes .../Contents.json | 12 + .../subscription-active-icon.pdf | Bin 0 -> 3346 bytes .../Contents.json | 15 + .../subscription-inactive-icon.pdf | Bin 0 -> 2646 bytes .../Icons/sync-icon.imageset/Contents.json | 15 + .../Icons/sync-icon.imageset/sync-icon.pdf | Bin 0 -> 2952 bytes .../vpn-service-icon.imageset/Contents.json | 15 + .../vpn-service-icon.pdf | Bin 0 -> 3763 bytes .../Images/Contents.json | 6 + .../Placeholder-96x64.imageset/Contents.json | 12 + .../Placeholder-96x64.pdf | Bin 0 -> 6729 bytes .../Model/AccessChannel.swift | 47 +++ .../ActivateSubscriptionAccessModel.swift | 62 ++++ .../Model/ShareSubscriptionAccessModel.swift | 79 +++++ .../Model/SubscriptionAccessModel.swift | 49 +++ .../SubscriptionAccessRow.swift | 99 +++++++ .../SubscriptionAccessView.swift | 111 +++++++ .../SubscriptionAccessViewController.swift | 63 ++++ .../Subscription/URL+Subscription.swift | 44 +++ .../Sources/Subscription/UserText.swift | 90 ++++++ .../SubscriptionTests/SubscriptionTests.swift | 29 ++ LocalPackages/SubscriptionUI/.gitignore | 9 + LocalPackages/SubscriptionUI/Package.swift | 45 +++ LocalPackages/SubscriptionUI/README.md | 1 + .../Views/SubscriptionActionbar.swift | 20 ++ .../Views/SubscriptionContainerView.swift | 20 ++ .../Views/SubscriptionWebView.swift | 55 ++++ 70 files changed, 3599 insertions(+), 272 deletions(-) delete mode 100644 DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 LocalPackages/Account/.gitignore create mode 100644 LocalPackages/Account/Package.swift create mode 100644 LocalPackages/Account/Sources/Account/AccountManager.swift create mode 100644 LocalPackages/Account/Sources/Account/AccountStorage/AccountKeychainStorage.swift create mode 100644 LocalPackages/Account/Sources/Account/AccountStorage/AccountStorage.swift create mode 100644 LocalPackages/Account/Sources/Account/Logging.swift create mode 100644 LocalPackages/Account/Sources/Account/Services/APIService.swift create mode 100644 LocalPackages/Account/Sources/Account/Services/AuthService.swift create mode 100644 LocalPackages/Account/Sources/Account/Services/SubscriptionService.swift create mode 100644 LocalPackages/Account/Tests/AccountTests/AccountsTests.swift create mode 100644 LocalPackages/Purchase/.gitignore create mode 100644 LocalPackages/Purchase/Package.swift create mode 100644 LocalPackages/Purchase/Sources/Purchase/PurchaseManager.swift create mode 100644 LocalPackages/Purchase/Tests/PurchaseTests/PurchaseTests.swift create mode 100644 LocalPackages/Subscription/.gitignore create mode 100644 LocalPackages/Subscription/Package.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/apple-id-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/email-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/itr-service-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/pir-service-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/subscription-active-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-inactive-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-inactive-icon.imageset/subscription-inactive-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/sync-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/vpn-service-icon.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/vpn-service-icon.imageset/vpn-service-icon.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Images/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Images/Placeholder-96x64.imageset/Contents.json create mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Images/Placeholder-96x64.imageset/Placeholder-96x64.pdf create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/AccessChannel.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift create mode 100644 LocalPackages/Subscription/Sources/Subscription/UserText.swift create mode 100644 LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift create mode 100644 LocalPackages/SubscriptionUI/.gitignore create mode 100644 LocalPackages/SubscriptionUI/Package.swift create mode 100644 LocalPackages/SubscriptionUI/README.md create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5dbab49ad4..7f588d7f73 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -429,6 +429,7 @@ 85514FFD2372DA0100DBC528 /* ios13-home-row.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 85514FFC2372DA0000DBC528 /* ios13-home-row.mp4 */; }; 8551912724746EDC0010FDD0 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */; }; 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */; }; + 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */; }; 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */; }; 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */; }; 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */; }; @@ -579,6 +580,7 @@ 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9880722925FA497B0039EF4B /* MenuButton.swift */; }; 9880723725FA4E450039EF4B /* menu_dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 9880723525FA4E440039EF4B /* menu_dark.json */; }; 9880723825FA4E450039EF4B /* menu_light.json in Resources */ = {isa = PBXBuildFile; fileRef = 9880723625FA4E450039EF4B /* menu_light.json */; }; + 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */; }; 9887DC252354D2AA005C85F5 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9887DC242354D2AA005C85F5 /* Database.swift */; }; 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9888F77A2224980500C46159 /* FeedbackViewController.swift */; }; 988AC355257E47C100793C64 /* RequeryLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988AC354257E47C100793C64 /* RequeryLogic.swift */; }; @@ -764,22 +766,6 @@ CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; - D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; - D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; - D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; - D6E83C382B1F2236006C8AFB /* SettingsGeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */; }; - D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */; }; - D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */; }; - D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */; }; - D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */; }; - D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; - D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; - D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */; }; - D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; - D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */; }; - D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C632B238432006C8AFB /* SettingsAboutView.swift */; }; - D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */; }; - D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C672B23B6A3006C8AFB /* FontSettings.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -877,6 +863,7 @@ F198D7981E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; }; F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A568391E70F98E0081082E /* AutocompleteRequest.swift */; }; F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A886771F29394E0096251E /* WebCacheManager.swift */; }; + F1AB2B421E3F7D5C00868554 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */; }; F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */; }; F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4A70D1E57725800A6CA1B /* OmniBar.swift */; }; @@ -910,6 +897,7 @@ F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */; }; F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */; }; F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */; }; + F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */; }; F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */; }; F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47E53D8250A97330037C686 /* OnboardingDefaultBroswerViewController.swift */; }; F47E53DB250A9A1C0037C686 /* Onboarding.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */; }; @@ -1478,6 +1466,7 @@ 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerRadarIntegrationTests.swift; sourceTree = ""; }; 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+PDFRendering.swift"; sourceTree = ""; }; + 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarPositionSettingsViewController.swift; sourceTree = ""; }; 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherTransition.swift; sourceTree = ""; }; 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserChromeManager.swift; sourceTree = ""; }; 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotification.swift; sourceTree = ""; }; @@ -2127,6 +2116,7 @@ 9880722925FA497B0039EF4B /* MenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuButton.swift; sourceTree = ""; }; 9880723525FA4E440039EF4B /* menu_dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = menu_dark.json; sourceTree = ""; }; 9880723625FA4E450039EF4B /* menu_light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = menu_light.json; sourceTree = ""; }; + 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsViewController.swift; sourceTree = ""; }; 9887DC242354D2AA005C85F5 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 9888F77A2224980500C46159 /* FeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewController.swift; sourceTree = ""; }; 988AC354257E47C100793C64 /* RequeryLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequeryLogic.swift; sourceTree = ""; }; @@ -2374,22 +2364,10 @@ CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; - D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; - D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; - D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsGeneralView.swift; sourceTree = ""; }; - D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSyncView.swift; sourceTree = ""; }; - D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLoginsView.swift; sourceTree = ""; }; - D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppeareanceView.swift; sourceTree = ""; }; - D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHostingController.swift; sourceTree = ""; }; - D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; - D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; - D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCustomizeView.swift; sourceTree = ""; }; - D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; - D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsMoreView.swift; sourceTree = ""; }; - D6E83C632B238432006C8AFB /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; - D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDebugView.swift; sourceTree = ""; }; - D6E83C672B23B6A3006C8AFB /* FontSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSettings.swift; sourceTree = ""; }; + D6E35F122B10E9890028C8E7 /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = ""; }; + D6E35F132B10EBDE0028C8E7 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Account; sourceTree = ""; }; + D6E35F142B10EBE30028C8E7 /* Purchase */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Purchase; sourceTree = ""; }; + D6E35F152B10EBE60028C8E7 /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -2519,6 +2497,7 @@ F1A568391E70F98E0081082E /* AutocompleteRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteRequest.swift; sourceTree = ""; }; F1A886771F29394E0096251E /* WebCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCacheManager.swift; sourceTree = ""; }; F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; + F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; F1B745211E549D550072547E /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIColorExtension.swift; path = ../Core/UIColorExtension.swift; sourceTree = ""; }; F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialSettings.swift; sourceTree = ""; }; @@ -2553,6 +2532,7 @@ F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptView.swift; sourceTree = ""; }; F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModel.swift; sourceTree = ""; }; F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewController.swift; sourceTree = ""; }; + F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireButtonAnimationSettingsViewController.swift; sourceTree = ""; }; F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainItemsDebugViewController.swift; sourceTree = ""; }; F47E53D8250A97330037C686 /* OnboardingDefaultBroswerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDefaultBroswerViewController.swift; sourceTree = ""; }; F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Onboarding.xcassets; sourceTree = ""; }; @@ -3369,6 +3349,10 @@ 31E69A60280F4BAD00478327 /* LocalPackages */ = { isa = PBXGroup; children = ( + D6E35F132B10EBDE0028C8E7 /* Account */, + D6E35F152B10EBE60028C8E7 /* Subscription */, + D6E35F142B10EBE30028C8E7 /* Purchase */, + D6E35F122B10E9890028C8E7 /* SubscriptionUI */, 85875B5F29912A2D00115F05 /* SyncUI */, 37FCAACB2993149A000E420A /* Waitlist */, 31794BFF2821DFB600F18633 /* DuckUI */, @@ -3804,23 +3788,35 @@ name = Renderers; sourceTree = ""; }; - 85449EF623FDA03100512AAF /* UIkit */ = { + 85449EF623FDA03100512AAF /* UI */ = { isa = PBXGroup; children = ( - F176699D1E40BC86003D3222 /* Settings.storyboard */, F1CDD3F11F16911700BE0581 /* AboutViewController.swift */, + 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */, AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */, AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */, 98F0FC1F21FF18E700CE77AB /* AutoClearSettingsViewController.swift */, 1EE7C298294227EC0026C8CB /* AutoconsentSettingsViewController.swift */, 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */, + F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */, 85449EF423FDA02800512AAF /* KeyboardSettingsViewController.swift */, 8540BD5523D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift */, + F176699D1E40BC86003D3222 /* Settings.storyboard */, + F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */, 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */, + 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */, 8531A08D1F9950E6000484F0 /* UnprotectedSitesViewController.swift */, - D6E83C672B23B6A3006C8AFB /* FontSettings.swift */, ); - name = UIkit; + name = UI; + sourceTree = ""; + }; + 85449EF723FDA03D00512AAF /* Model */ = { + isa = PBXGroup; + children = ( + 85449EFC23FDA71F00512AAF /* KeyboardSettings.swift */, + 4B53648926718D0E001AA041 /* EmailWaitlist.swift */, + ); + name = Model; sourceTree = ""; }; 85482D892462DCD100EDEDD1 /* OpenAction */ = { @@ -4441,46 +4437,6 @@ name = Resources; sourceTree = ""; }; - D6E83C322B1F1279006C8AFB /* Sections */ = { - isa = PBXGroup; - children = ( - D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */, - D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */, - D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */, - D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, - D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, - D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, - D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */, - D6E83C632B238432006C8AFB /* SettingsAboutView.swift */, - D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */, - ); - name = Sections; - sourceTree = ""; - }; - D6E83C3B2B1F27BA006C8AFB /* Views */ = { - isa = PBXGroup; - children = ( - D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */, - D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */, - D6E83C302B1EA309006C8AFB /* SettingsCell.swift */, - D6E83C322B1F1279006C8AFB /* Sections */, - 85449EF623FDA03100512AAF /* UIkit */, - ); - name = Views; - sourceTree = ""; - }; - D6E83C492B20C883006C8AFB /* Model */ = { - isa = PBXGroup; - children = ( - D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */, - D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, - D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, - 85449EFC23FDA71F00512AAF /* KeyboardSettings.swift */, - 4B53648926718D0E001AA041 /* EmailWaitlist.swift */, - ); - name = Model; - sourceTree = ""; - }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5109,9 +5065,9 @@ F1AB2B401E3F75A000868554 /* Settings */ = { isa = PBXGroup; children = ( - D6E83C492B20C883006C8AFB /* Model */, - D6E83C3B2B1F27BA006C8AFB /* Views */, 858566F1252E55AE007501B8 /* Debug */, + 85449EF723FDA03D00512AAF /* Model */, + 85449EF623FDA03100512AAF /* UI */, ); name = Settings; sourceTree = ""; @@ -6287,13 +6243,12 @@ 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, - D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, 31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */, EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 0290472029E708B70008FE3C /* AppTPManageTrackersViewModel.swift in Sources */, + 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, - D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, @@ -6315,14 +6270,12 @@ B6BA95C328891E33004ABA20 /* BrowsingMenuAnimator.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, - D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 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 */, - D6E83C382B1F2236006C8AFB /* SettingsGeneralView.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, 314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */, 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */, @@ -6335,7 +6288,6 @@ F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */, - D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, @@ -6419,7 +6371,6 @@ F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */, 1EEC460627A9499600E75FCB /* DownloadsList.swift in Sources */, - D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, @@ -6459,8 +6410,6 @@ 85374D3C21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift in Sources */, 85DFEDF124C7EEA400973FE7 /* LargeOmniBarState.swift in Sources */, 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */, - D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */, - D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, @@ -6473,7 +6422,6 @@ 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, - D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, @@ -6520,7 +6468,6 @@ 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, 31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */, - D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */, @@ -6532,7 +6479,6 @@ 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */, F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */, 85F2FFCD2211F615006BB258 /* MainViewController+KeyCommands.swift in Sources */, - D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */, 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */, 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, @@ -6569,9 +6515,9 @@ 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, - D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, + F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */, 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */, 1EEF387D285B1A1100383393 /* TrackerImageCache.swift in Sources */, @@ -6587,7 +6533,6 @@ 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, - D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, @@ -6630,16 +6575,15 @@ 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */, 1EF24235273BB9D200DE3D02 /* IntervalSlider.swift in Sources */, 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, - D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, + 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, 1E7A711C2934EEBC00B7EA19 /* OmniBarNotification.swift in Sources */, 02EC02C429AFA33000557F1A /* AppTPBreakageFormView.swift in Sources */, F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */, - D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, @@ -6650,6 +6594,7 @@ AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, + F1AB2B421E3F7D5C00868554 /* SettingsViewController.swift in Sources */, 8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */, B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */, 0290471E29E708750008FE3C /* AppTPManageTrackersView.swift in Sources */, @@ -6665,7 +6610,6 @@ 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 9865DFF922A8220D00D27829 /* FavoritesOverlay.swift in Sources */, - D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 1f616e1038..0000000000 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,178 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "BloomFilter", - "repositoryURL": "https://github.com/duckduckgo/bloom_cpp.git", - "state": { - "branch": null, - "revision": "8076199456290b61b4544bf2f4caf296759906a0", - "version": "3.0.0" - } - }, - { - "package": "BrowserServicesKit", - "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", - "state": { - "branch": null, - "revision": "e4f4ae624174c1398d345cfc387db38f8f69986d", - "version": "94.0.0" - } - }, - { - "package": "CocoaAsyncSocket", - "repositoryURL": "https://github.com/robbiehanson/CocoaAsyncSocket", - "state": { - "branch": null, - "revision": "dbdc00669c1ced63b27c3c5f052ee4d28f10150c", - "version": "7.6.5" - } - }, - { - "package": "ContentScopeScripts", - "repositoryURL": "https://github.com/duckduckgo/content-scope-scripts", - "state": { - "branch": null, - "revision": "b7ad9843e70cede0c2ca9c4260d970f62cb28156", - "version": "4.52.0" - } - }, - { - "package": "DesignResourcesKit", - "repositoryURL": "https://github.com/duckduckgo/DesignResourcesKit", - "state": { - "branch": null, - "revision": "d7ea2561ec7624c224f52e1c9b349075ddf1c782", - "version": "2.0.0" - } - }, - { - "package": "Autofill", - "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", - "state": { - "branch": null, - "revision": "dbecae0df07650a21b5632a92fa2e498c96af7b5", - "version": "10.0.1" - } - }, - { - "package": "GRDB", - "repositoryURL": "https://github.com/duckduckgo/GRDB.swift.git", - "state": { - "branch": null, - "revision": "77d9a83191a74e319a5cfa27b0e3145d15607573", - "version": "2.2.0" - } - }, - { - "package": "FindInPageIOSJSSupport", - "repositoryURL": "https://github.com/duckduckgo/ios-js-support", - "state": { - "branch": null, - "revision": "6a6789ac8104a587316c58af75539753853b50d9", - "version": "2.0.0" - } - }, - { - "package": "Kingfisher", - "repositoryURL": "https://github.com/onevcat/Kingfisher.git", - "state": { - "branch": null, - "revision": "af4be924ad984cf4d16f4ae4df424e79a443d435", - "version": "7.6.2" - } - }, - { - "package": "Lottie", - "repositoryURL": "https://github.com/duckduckgo/lottie-ios.git", - "state": { - "branch": null, - "revision": "abf5510e261c85ffddd29de0bca9b72592ea2bdd", - "version": "3.3.0" - } - }, - { - "package": "OHHTTPStubs", - "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", - "state": { - "branch": null, - "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version": "9.1.0" - } - }, - { - "package": "PrivacyDashboardResources", - "repositoryURL": "https://github.com/duckduckgo/privacy-dashboard", - "state": { - "branch": null, - "revision": "38336a574e13090764ba09a6b877d15ee514e371", - "version": "3.1.1" - } - }, - { - "package": "Punycode", - "repositoryURL": "https://github.com/gumob/PunycodeSwift.git", - "state": { - "branch": null, - "revision": "4356ec54e073741449640d3d50a1fd24fd1e1b8b", - "version": "2.1.0" - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser", - "state": { - "branch": null, - "revision": "c8ed701b513cf5177118a175d85fbbbcd707ab41", - "version": "1.3.0" - } - }, - { - "package": "Swifter", - "repositoryURL": "https://github.com/httpswift/swifter.git", - "state": { - "branch": null, - "revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd", - "version": "1.5.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "https://github.com/scinfu/SwiftSoup", - "state": { - "branch": null, - "revision": "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886", - "version": "2.4.2" - } - }, - { - "package": "DDGSyncCrypto", - "repositoryURL": "https://github.com/duckduckgo/sync_crypto", - "state": { - "branch": null, - "revision": "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", - "version": "0.2.0" - } - }, - { - "package": "TrackerRadarKit", - "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", - "state": { - "branch": null, - "revision": "a6b7ba151d9dc6684484f3785293875ec01cc1ff", - "version": "1.2.2" - } - }, - { - "package": "WireGuardKit", - "repositoryURL": "https://github.com/duckduckgo/wireguard-apple", - "state": { - "branch": null, - "revision": "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", - "version": "1.1.1" - } - } - ] - }, - "version": 1 -} diff --git a/LocalPackages/Account/.gitignore b/LocalPackages/Account/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/LocalPackages/Account/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift new file mode 100644 index 0000000000..b5198dae91 --- /dev/null +++ b/LocalPackages/Account/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.8 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Account", + platforms: [ .macOS(.v11), .iOS(.v15) ], + products: [ + .library( + name: "Account", + targets: ["Account"]), + ], + dependencies: [ + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "85.0.0"), + ], + targets: [ + .target( + name: "Account", + dependencies: [ + .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), + ]), + .testTarget( + name: "AccountTests", + dependencies: ["Account"]), + ] +) diff --git a/LocalPackages/Account/Sources/Account/AccountManager.swift b/LocalPackages/Account/Sources/Account/AccountManager.swift new file mode 100644 index 0000000000..fb8261ca46 --- /dev/null +++ b/LocalPackages/Account/Sources/Account/AccountManager.swift @@ -0,0 +1,219 @@ +// +// AccountManager.swift +// +// 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 Common + +public extension Notification.Name { + static let accountDidSignIn = Notification.Name("com.duckduckgo.browserServicesKit.AccountDidSignIn") + static let accountDidSignOut = Notification.Name("com.duckduckgo.browserServicesKit.AccountDidSignOut") +} + +public protocol AccountManagerKeychainAccessDelegate: AnyObject { + func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) +} + +public class AccountManager { + + private let storage: AccountStorage + public weak var delegate: AccountManagerKeychainAccessDelegate? + + public var isUserAuthenticated: Bool { + return accessToken != nil + } + + public init(storage: AccountStorage = AccountKeychainStorage()) { + self.storage = storage + } + + public var authToken: String? { + do { + return try storage.getAuthToken() + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .getAuthToken, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + + return nil + } + } + + public var accessToken: String? { + do { + return try storage.getAccessToken() + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .getAccessToken, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + + return nil + } + } + + public var email: String? { + do { + return try storage.getEmail() + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .getEmail, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + + return nil + } + } + + public var externalID: String? { + do { + return try storage.getExternalID() + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .getExternalID, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + + return nil + } + } + + public func storeAuthToken(token: String) { + do { + try storage.store(authToken: token) + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .storeAuthToken, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + } + } + + public func storeAccount(token: String, email: String?, externalID: String?) { + do { + try storage.store(accessToken: token) + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .storeAccessToken, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + } + + do { + try storage.store(email: email) + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .storeEmail, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + } + + do { + try storage.store(externalID: externalID) + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .storeExternalID, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + } + NotificationCenter.default.post(name: .accountDidSignIn, object: self, userInfo: nil) + } + + public func signOut() { + do { + try storage.clearAuthenticationState() + } catch { + if let error = error as? AccountKeychainAccessError { + delegate?.accountManagerKeychainAccessFailed(accessType: .clearAuthenticationData, error: error) + } else { + assertionFailure("Expected AccountKeychainAccessError") + } + } + + NotificationCenter.default.post(name: .accountDidSignOut, object: self, userInfo: nil) + } + + // MARK: - + + public func hasEntitlement(for name: String) async -> Bool { + await fetchEntitlements().contains(name) + } + + public func fetchEntitlements() async -> [String] { + guard let accessToken else { return [] } + + switch await AuthService.validateToken(accessToken: accessToken) { + case .success(let response): + let entitlements = response.account.entitlements + return entitlements.map { $0.name } + + case .failure(let error): + os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) + return [] + } + } + + @discardableResult + public func exchangeAndStoreTokens(with authToken: String) async -> Result { + // Exchange short-lived auth token to a long-lived access token + let accessToken: String + switch await AuthService.getAccessToken(token: authToken) { + case .success(let response): + accessToken = response.accessToken + case .failure(let error): + os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) + return .failure(error) + } + + // Fetch entitlements and account details and store the data + switch await AuthService.validateToken(accessToken: accessToken) { + case .success(let response): + self.storeAuthToken(token: authToken) + self.storeAccount(token: accessToken, + email: response.account.email, + externalID: response.account.externalID) + + return .success(response.account.externalID) + + case .failure(let error): + os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) + return .failure(error) + } + } + + public func refreshAccountData() async { + guard let accessToken else { return } + + switch await AuthService.validateToken(accessToken: accessToken) { + case .success(let response): + self.storeAccount(token: accessToken, + email: response.account.email, + externalID: response.account.externalID) + case .failure: + break + } + } +} diff --git a/LocalPackages/Account/Sources/Account/AccountStorage/AccountKeychainStorage.swift b/LocalPackages/Account/Sources/Account/AccountStorage/AccountKeychainStorage.swift new file mode 100644 index 0000000000..19596f764e --- /dev/null +++ b/LocalPackages/Account/Sources/Account/AccountStorage/AccountKeychainStorage.swift @@ -0,0 +1,195 @@ +// +// AccountKeychainStorage.swift +// +// 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 + +public enum AccountKeychainAccessType: String { + case getAuthToken + case storeAuthToken + case getAccessToken + case storeAccessToken + case getEmail + case storeEmail + case getExternalID + case storeExternalID + case clearAuthenticationData +} + +public enum AccountKeychainAccessError: Error, Equatable { + case failedToDecodeKeychainValueAsData + case failedToDecodeKeychainDataAsString + case keychainSaveFailure(OSStatus) + case keychainDeleteFailure(OSStatus) + case keychainLookupFailure(OSStatus) + + public var errorDescription: String { + switch self { + case .failedToDecodeKeychainValueAsData: return "failedToDecodeKeychainValueAsData" + case .failedToDecodeKeychainDataAsString: return "failedToDecodeKeychainDataAsString" + case .keychainSaveFailure: return "keychainSaveFailure" + case .keychainDeleteFailure: return "keychainDeleteFailure" + case .keychainLookupFailure: return "keychainLookupFailure" + } + } +} + +public class AccountKeychainStorage: AccountStorage { + + public init() {} + + public func getAuthToken() throws -> String? { + try Self.getString(forField: .authToken) + } + + public func store(authToken: String) throws { + try Self.set(string: authToken, forField: .authToken) + } + + public func getAccessToken() throws -> String? { + try Self.getString(forField: .accessToken) + } + + public func store(accessToken: String) throws { + try Self.set(string: accessToken, forField: .accessToken) + } + + public func getEmail() throws -> String? { + try Self.getString(forField: .email) + } + + public func getExternalID() throws -> String? { + try Self.getString(forField: .externalID) + } + + public func store(externalID: String?) throws { + if let externalID = externalID, !externalID.isEmpty { + try Self.set(string: externalID, forField: .externalID) + } else { + try Self.deleteItem(forField: .externalID) + } + } + + public func store(email: String?) throws { + if let email = email, !email.isEmpty { + try Self.set(string: email, forField: .email) + } else { + try Self.deleteItem(forField: .email) + } + } + + public func clearAuthenticationState() throws { + try Self.deleteItem(forField: .authToken) + try Self.deleteItem(forField: .accessToken) + try Self.deleteItem(forField: .email) + try Self.deleteItem(forField: .externalID) + } + +} + +private extension AccountKeychainStorage { + + /* + Uses just kSecAttrService as the primary key, since we don't want to store + multiple accounts/tokens at the same time + */ + enum AccountKeychainField: String, CaseIterable { + case authToken = "account.authToken" + case accessToken = "account.accessToken" + case email = "account.email" + case externalID = "account.external_id" + + var keyValue: String { + (Bundle.main.bundleIdentifier ?? "com.duckduckgo") + "." + rawValue + } + } + + static func getString(forField field: AccountKeychainField) throws -> String? { + guard let data = try retrieveData(forField: field) else { + return nil + } + + if let decodedString = String(data: data, encoding: String.Encoding.utf8) { + return decodedString + } else { + throw AccountKeychainAccessError.failedToDecodeKeychainDataAsString + } + } + + static func retrieveData(forField field: AccountKeychainField, useDataProtectionKeychain: Bool = true) throws -> Data? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecMatchLimit as String: kSecMatchLimitOne, + kSecAttrService as String: field.keyValue, + kSecReturnData as String: true, + kSecUseDataProtectionKeychain as String: useDataProtectionKeychain + ] + + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + + if status == errSecSuccess { + if let existingItem = item as? Data { + return existingItem + } else { + throw AccountKeychainAccessError.failedToDecodeKeychainValueAsData + } + } else if status == errSecItemNotFound { + return nil + } else { + throw AccountKeychainAccessError.keychainLookupFailure(status) + } + } + + static func set(string: String, forField field: AccountKeychainField) throws { + guard let stringData = string.data(using: .utf8) else { + return + } + + try deleteItem(forField: field) + try store(data: stringData, forField: field) + } + + static func store(data: Data, forField field: AccountKeychainField, useDataProtectionKeychain: Bool = true) throws { + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrSynchronizable: false, + kSecAttrService: field.keyValue, + kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock, + kSecValueData: data, + kSecUseDataProtectionKeychain: useDataProtectionKeychain] as [String: Any] + + let status = SecItemAdd(query as CFDictionary, nil) + + if status != errSecSuccess { + throw AccountKeychainAccessError.keychainSaveFailure(status) + } + } + + static func deleteItem(forField field: AccountKeychainField, useDataProtectionKeychain: Bool = true) throws { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: field.keyValue, + kSecUseDataProtectionKeychain as String: useDataProtectionKeychain] + + let status = SecItemDelete(query as CFDictionary) + + if status != errSecSuccess && status != errSecItemNotFound { + throw AccountKeychainAccessError.keychainDeleteFailure(status) + } + } +} diff --git a/LocalPackages/Account/Sources/Account/AccountStorage/AccountStorage.swift b/LocalPackages/Account/Sources/Account/AccountStorage/AccountStorage.swift new file mode 100644 index 0000000000..06b5e05cb5 --- /dev/null +++ b/LocalPackages/Account/Sources/Account/AccountStorage/AccountStorage.swift @@ -0,0 +1,31 @@ +// +// AccountStorage.swift +// +// 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 + +public protocol AccountStorage: AnyObject { + func getAuthToken() throws -> String? + func store(authToken: String) throws + func getAccessToken() throws -> String? + func store(accessToken: String) throws + func getEmail() throws -> String? + func store(email: String?) throws + func getExternalID() throws -> String? + func store(externalID: String?) throws + func clearAuthenticationState() throws +} diff --git a/LocalPackages/Account/Sources/Account/Logging.swift b/LocalPackages/Account/Sources/Account/Logging.swift new file mode 100644 index 0000000000..b15ec8e1fa --- /dev/null +++ b/LocalPackages/Account/Sources/Account/Logging.swift @@ -0,0 +1,56 @@ +// +// Logging.swift +// +// 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 Common + +struct Logging { + + static let subsystem = "com.duckduckgo.macos.browser.account" + + fileprivate static let accountLoggingEnabled = true + fileprivate static let account: OSLog = OSLog(subsystem: subsystem, category: "Account") + + fileprivate static let authServiceLoggingEnabled = true + fileprivate static let authService: OSLog = OSLog(subsystem: subsystem, category: "Account : AuthService") + + fileprivate static let subscriptionServiceLoggingEnabled = true + fileprivate static let subscriptionService: OSLog = OSLog(subsystem: subsystem, category: "Account : SubscriptionService") + + fileprivate static let errorsLoggingEnabled = true + fileprivate static let error: OSLog = OSLog(subsystem: subsystem, category: "Account : Errors") +} + +extension OSLog { + + public static var account: OSLog { + Logging.accountLoggingEnabled ? Logging.account : .disabled + } + + public static var authService: OSLog { + Logging.authServiceLoggingEnabled ? Logging.authService : .disabled + } + + public static var subscriptionService: OSLog { + Logging.subscriptionServiceLoggingEnabled ? Logging.subscriptionService : .disabled + } + + public static var error: OSLog { + Logging.errorsLoggingEnabled ? Logging.error : .disabled + } +} diff --git a/LocalPackages/Account/Sources/Account/Services/APIService.swift b/LocalPackages/Account/Sources/Account/Services/APIService.swift new file mode 100644 index 0000000000..5a7d8f0d1b --- /dev/null +++ b/LocalPackages/Account/Sources/Account/Services/APIService.swift @@ -0,0 +1,110 @@ +// +// APIService.swift +// +// 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 Common + +public enum APIServiceError: Swift.Error { + case decodingError + case encodingError + case serverError(description: String) + case unknownServerError + case connectionError +} + +struct ErrorResponse: Decodable { + let error: String +} + +public protocol APIService { + static var logger: OSLog { get } + static var baseURL: URL { get } + static var session: URLSession { get } + static func executeAPICall(method: String, endpoint: String, headers: [String: String]?, body: Data?) async -> Result where T: Decodable +} + +public extension APIService { + + static func executeAPICall(method: String, endpoint: String, headers: [String: String]? = nil, body: Data? = nil) async -> Result where T: Decodable { + let request = makeAPIRequest(method: method, endpoint: endpoint, headers: headers, body: body) + + do { + let (data, urlResponse) = try await session.data(for: request) + + printDebugInfo(method: method, endpoint: endpoint, data: data, response: urlResponse) + + if let httpResponse = urlResponse as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) { + if let decodedResponse = decode(T.self, from: data) { + return .success(decodedResponse) + } else { + return .failure(.decodingError) + } + } else { + if let decodedResponse = decode(ErrorResponse.self, from: data) { + let errorDescription = "[\(endpoint)] \(urlResponse.httpStatusCodeAsString ?? ""): \(decodedResponse.error)" + return .failure(.serverError(description: errorDescription)) + } else { + return .failure(.unknownServerError) + } + } + } catch { + os_log("Service error: %{public}@", log: .error, error.localizedDescription) + return .failure(.connectionError) + } + } + + private static func makeAPIRequest(method: String, endpoint: String, headers: [String: String]?, body: Data?) -> URLRequest { + let url = baseURL.appendingPathComponent(endpoint) + var request = URLRequest(url: url) + request.httpMethod = method + if let headers = headers { + request.allHTTPHeaderFields = headers + } + if let body = body { + request.httpBody = body + } + + return request + } + + private static func decode(_: T.Type, from data: Data) -> T? where T: Decodable { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .millisecondsSince1970 + + return try? decoder.decode(T.self, from: data) + } + + private static func printDebugInfo(method: String, endpoint: String, data: Data, response: URLResponse) { + let statusCode = (response as? HTTPURLResponse)!.statusCode + let stringData = String(data: data, encoding: .utf8) ?? "" + os_log("[%d] %{public}@ /%{public}@ :: %{public}@", log: logger, statusCode, method, endpoint, stringData) + } + + static func makeAuthorizationHeader(for token: String) -> [String: String] { + ["Authorization": "Bearer " + token] + } +} + +extension URLResponse { + + var httpStatusCodeAsString: String? { + guard let httpStatusCode = (self as? HTTPURLResponse)?.statusCode else { return nil } + return String(httpStatusCode) + } +} diff --git a/LocalPackages/Account/Sources/Account/Services/AuthService.swift b/LocalPackages/Account/Sources/Account/Services/AuthService.swift new file mode 100644 index 0000000000..33a18262ad --- /dev/null +++ b/LocalPackages/Account/Sources/Account/Services/AuthService.swift @@ -0,0 +1,112 @@ +// +// AuthService.swift +// +// 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 Common + +public struct AuthService: APIService { + + public static let logger: OSLog = .authService + public static let session = { + let configuration = URLSessionConfiguration.ephemeral + return URLSession(configuration: configuration) + }() + public static let baseURL = URL(string: "https://quackdev.duckduckgo.com/api/auth")! + + // MARK: - + + public static func getAccessToken(token: String) async -> Result { + await executeAPICall(method: "GET", endpoint: "access-token", headers: makeAuthorizationHeader(for: token)) + } + + public struct AccessTokenResponse: Decodable { + public let accessToken: String + } + + // MARK: - + + public static func validateToken(accessToken: String) async -> Result { + await executeAPICall(method: "GET", endpoint: "validate-token", headers: makeAuthorizationHeader(for: accessToken)) + } + + // swiftlint:disable nesting + public struct ValidateTokenResponse: Decodable { + public let account: Account + + public struct Account: Decodable { + public let email: String? + let entitlements: [Entitlement] + public let externalID: String + + enum CodingKeys: String, CodingKey { + case email, entitlements, externalID = "externalId" // no underscores due to keyDecodingStrategy = .convertFromSnakeCase + } + } + + struct Entitlement: Decodable { + let id: Int + let name: String + let product: String + } + } + // swiftlint:enable nesting + + // MARK: - + + public static func createAccount(emailAccessToken: String?) async -> Result { + var headers: [String: String]? + + if let emailAccessToken { + headers = makeAuthorizationHeader(for: emailAccessToken) + } + + return await executeAPICall(method: "POST", endpoint: "account/create", headers: headers) + } + + public struct CreateAccountResponse: Decodable { + public let authToken: String + public let externalID: String + public let status: String + + enum CodingKeys: String, CodingKey { + case authToken = "authToken", externalID = "externalId", status // no underscores due to keyDecodingStrategy = .convertFromSnakeCase + } + } + + // MARK: - + + public static func storeLogin(signature: String) async -> Result { + let bodyDict = ["signature": signature, + "store": "apple_app_store"] + + guard let bodyData = try? JSONEncoder().encode(bodyDict) else { return .failure(.encodingError) } + return await executeAPICall(method: "POST", endpoint: "store-login", body: bodyData) + } + + public struct StoreLoginResponse: Decodable { + public let authToken: String + public let email: String + public let externalID: String + public let id: Int + public let status: String + + enum CodingKeys: String, CodingKey { + case authToken = "authToken", email, externalID = "externalId", id, status // no underscores due to keyDecodingStrategy = .convertFromSnakeCase + } + } +} diff --git a/LocalPackages/Account/Sources/Account/Services/SubscriptionService.swift b/LocalPackages/Account/Sources/Account/Services/SubscriptionService.swift new file mode 100644 index 0000000000..ad34078a27 --- /dev/null +++ b/LocalPackages/Account/Sources/Account/Services/SubscriptionService.swift @@ -0,0 +1,44 @@ +// +// SubscriptionService.swift +// +// 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 Common + +public struct SubscriptionService: APIService { + + public static let logger: OSLog = .subscriptionService + public static let session = { + let configuration = URLSessionConfiguration.ephemeral + return URLSession(configuration: configuration) + }() + public static let baseURL = URL(string: "https://subscriptions-dev.duckduckgo.com/api")! + + // MARK: - + + public static func getSubscriptionInfo(token: String) async -> Result { + await executeAPICall(method: "GET", endpoint: "subscription", headers: makeAuthorizationHeader(for: token)) + } + + public struct GetSubscriptionInfoResponse: Decodable { + public let productId: String + public let startedAt: Date + public let expiresOrRenewsAt: Date + public let platform: String + public let status: String + } +} diff --git a/LocalPackages/Account/Tests/AccountTests/AccountsTests.swift b/LocalPackages/Account/Tests/AccountTests/AccountsTests.swift new file mode 100644 index 0000000000..09f797525f --- /dev/null +++ b/LocalPackages/Account/Tests/AccountTests/AccountsTests.swift @@ -0,0 +1,29 @@ +// +// AccountTests.swift +// +// 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 XCTest +@testable import Account + +final class AccountTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(Account().text, "Hello, World!") + } +} diff --git a/LocalPackages/Purchase/.gitignore b/LocalPackages/Purchase/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/LocalPackages/Purchase/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift new file mode 100644 index 0000000000..0cd9799bf2 --- /dev/null +++ b/LocalPackages/Purchase/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 5.8 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Purchase", + platforms: [ .macOS(.v11), .iOS(.v15) ], + products: [ + .library( + name: "Purchase", + targets: ["Purchase"]), + ], + dependencies: [ + ], + targets: [ + .target( + name: "Purchase", + dependencies: []), + .testTarget( + name: "PurchaseTests", + dependencies: ["Purchase"]), + ] +) diff --git a/LocalPackages/Purchase/Sources/Purchase/PurchaseManager.swift b/LocalPackages/Purchase/Sources/Purchase/PurchaseManager.swift new file mode 100644 index 0000000000..24c1c5a9f1 --- /dev/null +++ b/LocalPackages/Purchase/Sources/Purchase/PurchaseManager.swift @@ -0,0 +1,277 @@ +// +// PurchaseManager.swift +// +// 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 StoreKit + +@available(macOS 12.0, *) typealias Transaction = StoreKit.Transaction +@available(macOS 12.0, *) typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo +@available(macOS 12.0, *) typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState + +public enum StoreError: Error { + case failedVerification +} + +enum PurchaseManagerError: Error { + case productNotFound + case externalIDisNotAValidUUID + case purchaseFailed + case transactionCannotBeVerified + case transactionPendingAuthentication + case purchaseCancelledByUser + case unknownError +} + +@available(macOS 12.0, *) +public final class PurchaseManager: ObservableObject { + + static let productIdentifiers = ["subscription.1week", "subscription.1month", "subscription.1year", + "review.subscription.1week", "review.subscription.1month", "review.subscription.1year"] + + public static let shared = PurchaseManager() + + @Published public private(set) var availableProducts: [Product] = [] + @Published public private(set) var purchasedProductIDs: [String] = [] + @Published public private(set) var purchaseQueue: [String] = [] + + @Published private(set) var subscriptionGroupStatus: RenewalState? + + private var transactionUpdates: Task? + private var storefrontChanges: Task? + + public init() { + transactionUpdates = observeTransactionUpdates() + storefrontChanges = observeStorefrontChanges() + } + + deinit { + transactionUpdates?.cancel() + storefrontChanges?.cancel() + } + + @MainActor + public func hasProductsAvailable() async -> Bool { + do { + let availableProducts = try await Product.products(for: Self.productIdentifiers) + print(" -- [PurchaseManager] updateAvailableProducts(): fetched \(availableProducts.count)") + return !availableProducts.isEmpty + } catch { + print("Error fetching available products: \(error)") + return false + } + } + + @MainActor + @discardableResult + public func syncAppleIDAccount() async -> Result { + do { + purchaseQueue.removeAll() + + print("Before AppStore.sync()") + + try await AppStore.sync() + + print("After AppStore.sync()") + + await updatePurchasedProducts() + await updateAvailableProducts() + + return .success(()) + } catch { + print("AppStore.sync error: \(error)") + return .failure(error) + } + } + + @MainActor + public func updateAvailableProducts() async { + print(" -- [PurchaseManager] updateAvailableProducts()") + + do { + let availableProducts = try await Product.products(for: Self.productIdentifiers) + print(" -- [PurchaseManager] updateAvailableProducts(): fetched \(availableProducts.count) products") + + if self.availableProducts != availableProducts { + print("availableProducts changed!") + self.availableProducts = availableProducts + } + } catch { + print("Error updating available products: \(error)") + } + } + + @MainActor + func fetchAvailableProducts() async -> [Product] { + print(" -- [PurchaseManager] fetchAvailableProducts()") + + do { + let availableProducts = try await Product.products(for: Self.productIdentifiers) + print(" -- [PurchaseManager] fetchAvailableProducts(): fetched \(availableProducts.count) products") + + return availableProducts + } catch { + print("Error fetching available products: \(error)") + return [] + } + } + + @MainActor + public func updatePurchasedProducts() async { + print(" -- [PurchaseManager] updatePurchasedProducts()") + + var purchasedSubscriptions: [String] = [] + + do { + for await result in Transaction.currentEntitlements { + let transaction = try checkVerified(result) + + guard transaction.productType == .autoRenewable else { continue } + guard transaction.revocationDate == nil else { continue } + + if let expirationDate = transaction.expirationDate, expirationDate > .now { + purchasedSubscriptions.append(transaction.productID) + + if let token = transaction.appAccountToken { + print(" -- [PurchaseManager] updatePurchasedProducts(): \(transaction.productID) -- custom UUID: \(token)" ) + } + } + } + } catch { + print("Error updating purchased products: \(error)") + } + + print(" -- [PurchaseManager] updatePurchasedProducts(): have \(purchasedSubscriptions.count) active subscriptions") + + if self.purchasedProductIDs != purchasedSubscriptions { + print("purchasedSubscriptions changed!") + self.purchasedProductIDs = purchasedSubscriptions + } + + subscriptionGroupStatus = try? await availableProducts.first?.subscription?.status.first?.state + } + + @MainActor + public static func mostRecentTransaction() async -> String? { + print(" -- [PurchaseManager] mostRecentTransaction()") + + var transactions: [VerificationResult] = [] + + for await result in Transaction.all { + transactions.append(result) + } + + print(" -- [PurchaseManager] mostRecentTransaction(): fetched \(transactions.count) transactions") + + return transactions.first?.jwsRepresentation + } + + + @MainActor + public func purchaseSubscription(with identifier: String, externalID: String) async -> Result { + + guard let product = availableProducts.first(where: { $0.id == identifier }) else { return .failure(PurchaseManagerError.productNotFound) } + + print(" -- [PurchaseManager] buy: \(product.displayName) (customUUID: \(externalID))") + + print("purchaseQueue append!") + purchaseQueue.append(product.id) + + print(" -- [PurchaseManager] starting purchase") + + var options: Set = Set() + + if let token = UUID(uuidString: externalID) { + options.insert(.appAccountToken(token)) + } else { + print("Wrong UUID") + return .failure(PurchaseManagerError.externalIDisNotAValidUUID) + } + + let result: Product.PurchaseResult + do { + result = try await product.purchase(options: options) + } catch { + print("error \(error)") + return .failure(PurchaseManagerError.purchaseFailed) + } + + print(" -- [PurchaseManager] purchase complete") + + purchaseQueue.removeAll() + print("purchaseQueue removeAll!") + + switch result { + case let .success(.verified(transaction)): + // Successful purchase + await transaction.finish() + await self.updatePurchasedProducts() + return .success(()) + case let .success(.unverified(_, error)): + // Successful purchase but transaction/receipt can't be verified + // Could be a jailbroken phone + print("Error: \(error.localizedDescription)") + return .failure(PurchaseManagerError.transactionCannotBeVerified) + case .pending: + // Transaction waiting on SCA (Strong Customer Authentication) or + // approval from Ask to Buy + return .failure(PurchaseManagerError.transactionPendingAuthentication) + case .userCancelled: + return .failure(PurchaseManagerError.purchaseCancelledByUser) + @unknown default: + return .failure(PurchaseManagerError.unknownError) + } + } + + private func checkVerified(_ result: VerificationResult) throws -> T { + // Check whether the JWS passes StoreKit verification. + switch result { + case .unverified: + // StoreKit parses the JWS, but it fails verification. + throw StoreError.failedVerification + case .verified(let safe): + // The result is verified. Return the unwrapped value. + return safe + } + } + + private func observeTransactionUpdates() -> Task { + + Task.detached { [unowned self] in + for await result in Transaction.updates { + print(" -- [PurchaseManager] observeTransactionUpdates()") + + if case .verified(let transaction) = result { + await transaction.finish() + } + + await self.updatePurchasedProducts() + } + } + } + + private func observeStorefrontChanges() -> Task { + + Task.detached { [unowned self] in + for await result in Storefront.updates { + print(" -- [PurchaseManager] observeStorefrontChanges(): \(result.countryCode)") + await updatePurchasedProducts() + await updateAvailableProducts() + } + } + } +} diff --git a/LocalPackages/Purchase/Tests/PurchaseTests/PurchaseTests.swift b/LocalPackages/Purchase/Tests/PurchaseTests/PurchaseTests.swift new file mode 100644 index 0000000000..9c6b73d0a1 --- /dev/null +++ b/LocalPackages/Purchase/Tests/PurchaseTests/PurchaseTests.swift @@ -0,0 +1,29 @@ +// +// PurchaseTests.swift +// +// 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 XCTest +@testable import Purchase + +final class PurchaseTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(Purchase().text, "Hello, World!") + } +} diff --git a/LocalPackages/Subscription/.gitignore b/LocalPackages/Subscription/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/LocalPackages/Subscription/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift new file mode 100644 index 0000000000..270f16becf --- /dev/null +++ b/LocalPackages/Subscription/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version: 5.8 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Subscription", + platforms: [ .macOS(.v11), .iOS(.v15) ], + products: [ + .library( + name: "Subscription", + targets: ["Subscription"]), + ], + dependencies: [ + .package(path: "../Account"), + .package(path: "../Purchase"), + //.package(path: "../SwiftUIExtensions") + ], + targets: [ + .target( + name: "Subscription", + dependencies: [ + .product(name: "Account", package: "Account"), + .product(name: "Purchase", package: "Purchase"), + //.product(name: "SwiftUIExtensions", package: "SwiftUIExtensions") + ], + resources: [ + .process("Resources") + ]), + .testTarget( + name: "SubscriptionTests", + dependencies: ["Subscription"]), + ] +) diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift new file mode 100644 index 0000000000..5225d59cf3 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift @@ -0,0 +1,55 @@ +// +// DebugPurchaseModel.swift +// +// 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 StoreKit +import Purchase +import Account + +@available(macOS 12.0, *) +public final class DebugPurchaseModel: ObservableObject { + + var purchaseManager: PurchaseManager + var accountManager: AccountManager = AccountManager() + + @Published var subscriptions: [SubscriptionRowModel] + + init(manager: PurchaseManager, subscriptions: [SubscriptionRowModel] = []) { + self.purchaseManager = manager + self.subscriptions = subscriptions + } + + @MainActor + func purchase(_ product: Product) { + print("Attempting purchase: \(product.displayName)") + + Task { + await AppStorePurchaseFlow.purchaseSubscription(with: product.id, emailAccessToken: nil) + } + } +} + +@available(macOS 12.0, *) +public struct SubscriptionRowModel: Identifiable { + public var id: String { product.id + String(isPurchased) + String(isBeingPurchased) } + + public let product: Product + public let isPurchased: Bool + public let isBeingPurchased: Bool +} diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift new file mode 100644 index 0000000000..07250f3c00 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift @@ -0,0 +1,193 @@ +// +// DebugPurchaseView.swift +// +// 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 StoreKit + +@available(macOS 12.0, *) +public struct DebugPurchaseView: View { + + @ObservedObject var model: DebugPurchaseModel + public let dismissAction: () -> Void + + public var body: some View { + VStack { + if model.subscriptions.isEmpty { + loadingProductsView + } else { + purchaseSubscriptionSection + } + + Divider() + + HStack { + Spacer() + Button("Close") { + dismissAction() + } + } + } + .padding(20) + } + + private var loadingProductsView: some View { + VStack { + Text("Loading subscriptions...") + .font(.largeTitle) + ActivityIndicator(isAnimating: .constant(true), style: .spinning) + } + .padding(.all, 32) + } + + private var purchaseSubscriptionSection: some View { + VStack { + Text("Purchase Subscription") + .font(.largeTitle) + Spacer(minLength: 16) + VStack { + ForEach(model.subscriptions, id: \.id) { rowModel in + SubscriptionRow(product: rowModel.product, + isPurchased: rowModel.isPurchased, + isBeingPurchased: rowModel.isBeingPurchased, + buyButtonAction: { model.purchase(rowModel.product) }) + Divider() + } + .padding(10) + } + .roundedBorder() + Spacer(minLength: 16) + } + } +} + +struct ActivityIndicator: NSViewRepresentable { + + @Binding var isAnimating: Bool + + let style: NSProgressIndicator.Style + + func makeNSView(context: NSViewRepresentableContext) -> NSProgressIndicator { + let progressIndicator = NSProgressIndicator() + progressIndicator.style = self.style + progressIndicator.controlSize = .small + return progressIndicator + } + + func updateNSView(_ nsView: NSProgressIndicator, context: NSViewRepresentableContext) { + if isAnimating { + nsView.startAnimation(nil) + } else { + nsView.stopAnimation(nil) + } + } +} + +@available(macOS 12.0, *) +struct SubscriptionRow: View { + + var product: Product + @State var isPurchased: Bool = false + @State var isBeingPurchased: Bool = false + + var buyButtonAction: () -> Void + + var body: some View { + HStack(alignment: .center) { + VStack(alignment: .leading) { + Text(product.displayName) + .font(.title) + Text(product.description) + .font(.body) + Text("Price: \(product.displayPrice)") + .font(.caption) + } + + Spacer() + + Button { + buyButtonAction() + } label: { + if isPurchased { + Text(Image(systemName: "checkmark")) + .bold() + .foregroundColor(.white) + } else if isBeingPurchased { + ActivityIndicator(isAnimating: .constant(true), style: .spinning) + } else { + Text("Buy") + .bold() + .foregroundColor(.white) + } + + } + .buttonStyle(BuyButtonStyle(isPurchased: isPurchased)) + + } + .disabled(isPurchased) + } +} + +struct CapsuleButton: ButtonStyle { + + @ViewBuilder + func makeBody(configuration: Configuration) -> some View { + let background = configuration.isPressed ? Color(white: 0.25) : Color(white: 0.5) + + configuration.label + .padding(12) + .background(background) + .foregroundColor(.white) + .clipShape(Capsule()) + } +} + +@available(macOS 12.0, *) +extension Product { + + var isSubscription: Bool { + type == .nonRenewable || type == .autoRenewable + } +} + +extension String: Identifiable { + public typealias ID = Int + public var id: Int { + return hash + } +} + +@available(macOS 12.0, *) +struct BuyButtonStyle: ButtonStyle { + let isPurchased: Bool + + init(isPurchased: Bool = false) { + self.isPurchased = isPurchased + } + + func makeBody(configuration: Self.Configuration) -> some View { + var bgColor: Color = isPurchased ? Color.green : Color.blue + bgColor = configuration.isPressed ? bgColor.opacity(0.7) : bgColor.opacity(1) + + return configuration.label + .frame(width: 50) + .padding(10) + .background(bgColor) + .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) + .scaleEffect(configuration.isPressed ? 0.9 : 1.0) + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift new file mode 100644 index 0000000000..e34296b062 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift @@ -0,0 +1,82 @@ +// +// DebugPurchaseViewController.swift +// +// 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 AppKit +import SwiftUI +import Combine +import StoreKit +import Purchase + +@available(macOS 12.0, *) +public final class DebugPurchaseViewController: NSViewController { + + private let manager: PurchaseManager + private let model: DebugPurchaseModel + + private var cancellables = Set() + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public init() { + manager = PurchaseManager.shared + model = DebugPurchaseModel(manager: manager) + + super.init(nibName: nil, bundle: nil) + } + + public override func loadView() { + + let purchaseView = DebugPurchaseView(model: model, dismissAction: { [weak self] in + guard let self = self else { return } + self.presentingViewController?.dismiss(self) + }) + + let hostingView = NSHostingView(rootView: purchaseView) + + view = NSView(frame: NSRect(x: 0, y: 0, width: 480, height: 500)) + hostingView.frame = view.bounds + hostingView.autoresizingMask = [.height, .width] + hostingView.translatesAutoresizingMaskIntoConstraints = true + + view.addSubview(hostingView) + } + + public override func viewDidLoad() { + Task { + await manager.updatePurchasedProducts() + await manager.updateAvailableProducts() + } + + manager.$availableProducts.combineLatest(manager.$purchasedProductIDs, manager.$purchaseQueue).receive(on: RunLoop.main).sink { [weak self] availableProducts, purchasedProductIDs, purchaseQueue in + print(" -- got combineLatest -") + print(" -- got combineLatest - availableProducts: \(availableProducts.map { $0.id }.joined(separator: ","))") + print(" -- got combineLatest - purchasedProducts: \(purchasedProductIDs.joined(separator: ","))") + print(" -- got combineLatest - purchaseQueue: \(purchaseQueue.joined(separator: ","))") + + let sortedProducts = availableProducts.sorted(by: { $0.price > $1.price }) + + self?.model.subscriptions = sortedProducts.map { SubscriptionRowModel(product: $0, + isPurchased: purchasedProductIDs.contains($0.id), + isBeingPurchased: purchaseQueue.contains($0.id)) } + }.store(in: &cancellables) + } +} +*/ diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift new file mode 100644 index 0000000000..5a3babbf5b --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift @@ -0,0 +1,75 @@ +// +// PurchaseInProgressViewController.swift +// +// 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 AppKit +import SwiftUI + +public final class PurchaseInProgressViewController: NSViewController { + + private var purchaseInProgressView: PurchaseInProgressView? + private var viewModel: PurchaseInProgressViewModel + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public init(title: String) { + self.viewModel = PurchaseInProgressViewModel(title: title) + super.init(nibName: nil, bundle: nil) + } + + public override func loadView() { + + let purchaseInProgressView = PurchaseInProgressView(viewModel: viewModel) + let hostingView = NSHostingView(rootView: purchaseInProgressView) + + self.purchaseInProgressView = purchaseInProgressView + + view = NSView(frame: NSRect(x: 0, y: 0, width: 360, height: 160)) + hostingView.frame = view.bounds + hostingView.autoresizingMask = [.height, .width] + hostingView.translatesAutoresizingMaskIntoConstraints = true + + view.addSubview(hostingView) + } + + public func updateTitleText(_ text: String) { + self.viewModel.title = text + } +} + +final class PurchaseInProgressViewModel: ObservableObject { + @Published var title: String + + init(title: String) { + self.title = title + } +} + +struct PurchaseInProgressView: View { + + @ObservedObject var viewModel: PurchaseInProgressViewModel + + public var body: some View { + VStack { + Text(viewModel.title).font(.title) + Spacer().frame(height: 32) + ActivityIndicator(isAnimating: .constant(true), style: .spinning) + } + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift new file mode 100644 index 0000000000..f54e106c5b --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift @@ -0,0 +1,211 @@ +// +// SubscriptionDebugMenu.swift +// +// 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 AppKit +import Account +import Purchase + +public final class SubscriptionDebugMenu: NSMenuItem { + + var currentViewController: () -> NSViewController? + private let accountManager = AccountManager() + + private var _purchaseManager: Any? + @available(macOS 12.0, *) + fileprivate var purchaseManager: PurchaseManager { + if _purchaseManager == nil { + _purchaseManager = PurchaseManager() + } + // swiftlint:disable:next force_cast + return _purchaseManager as! PurchaseManager + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public init(currentViewController: @escaping () -> NSViewController?) { + self.currentViewController = currentViewController + super.init(title: "Subscription", action: nil, keyEquivalent: "") + self.submenu = submenuItem + } + + private lazy var submenuItem: NSMenu = { + let menu = NSMenu(title: "") + + menu.addItem(NSMenuItem(title: "Simulate Subscription Active State (fake token)", action: #selector(simulateSubscriptionActiveState), target: self)) + menu.addItem(NSMenuItem(title: "Clear Subscription Authorization Data", action: #selector(signOut), target: self)) + menu.addItem(NSMenuItem(title: "Show account details", action: #selector(showAccountDetails), target: self)) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Validate Token", action: #selector(validateToken), target: self)) + menu.addItem(NSMenuItem(title: "Check Entitlements", action: #selector(checkEntitlements), target: self)) + menu.addItem(NSMenuItem(title: "Get Subscription Info", action: #selector(getSubscriptionInfo), target: self)) + if #available(macOS 12.0, *) { + menu.addItem(NSMenuItem(title: "Check Purchase Products Availability", action: #selector(checkProductsAvailability), target: self)) + } + menu.addItem(NSMenuItem(title: "Restore Subscription from App Store transaction", action: #selector(restorePurchases), target: self)) + menu.addItem(.separator()) + if #available(macOS 12.0, *) { + menu.addItem(NSMenuItem(title: "Sync App Store AppleID Account (re- sign-in)", action: #selector(syncAppleIDAccount), target: self)) + menu.addItem(NSMenuItem(title: "Purchase Subscription from App Store", action: #selector(showPurchaseView), target: self)) + } + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Error message #1", action: #selector(testError1), target: self)) + menu.addItem(NSMenuItem(title: "Error message #2", action: #selector(testError2), target: self)) + return menu + }() + + @objc + func simulateSubscriptionActiveState() { + accountManager.storeAccount(token: "fake-token", email: "fake@email.com", externalID: "123") + } + + @objc + func signOut() { + accountManager.signOut() + } + + @objc + func showAccountDetails() { + let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" + let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", + "AccessToken: \(accountManager.accessToken ?? "")", + "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil + showAlert(title: title, message: message) + } + + @objc + func validateToken() { + Task { + guard let token = accountManager.accessToken else { return } + switch await AuthService.validateToken(accessToken: token) { + case .success(let response): + showAlert(title: "Validate token", message: "\(response)") + case .failure(let error): + showAlert(title: "Validate token", message: "\(error)") + } + } + } + + @objc + func checkEntitlements() { + Task { + var results: [String] = [] + + for entitlementName in ["fake", "dummy1", "dummy2", "dummy3"] { + let result = await AccountManager().hasEntitlement(for: entitlementName) + let resultSummary = "Entitlement check for \(entitlementName): \(result)" + results.append(resultSummary) + print(resultSummary) + } + + showAlert(title: "Check Entitlements", message: results.joined(separator: "\n")) + } + } + + @objc + func getSubscriptionInfo() { + Task { + guard let token = accountManager.accessToken else { return } + switch await SubscriptionService.getSubscriptionInfo(token: token) { + case .success(let response): + showAlert(title: "Subscription info", message: "\(response)") + case .failure(let error): + showAlert(title: "Subscription info", message: "\(error)") + } + } + } + + @available(macOS 12.0, *) + @objc + func syncAppleIDAccount() { + Task { + await purchaseManager.syncAppleIDAccount() + } + } + + @available(macOS 12.0, *) + @objc + func checkProductsAvailability() { + Task { + + let result = await purchaseManager.hasProductsAvailable() + showAlert(title: "Check App Store Product Availability", + message: "Can purchase: \(result ? "YES" : "NO")") + } + } + + @objc + func restorePurchases(_ sender: Any?) { + if #available(macOS 12.0, *) { + Task { + await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + } + } + } + + @objc + func testError1(_ sender: Any?) { + Task { @MainActor in + let alert = NSAlert.init() + alert.messageText = "Something Went Wrong" + alert.informativeText = "The App Store was not able to process your purchase. Please try again later." + alert.addButton(withTitle: "OK") + alert.runModal() + } + } + + @objc + func testError2(_ sender: Any?) { + Task { @MainActor in + let alert = NSAlert.init() + alert.messageText = "Subscription Not Found" + alert.informativeText = "The subscription associated with this Apple ID is no longer active." + alert.addButton(withTitle: "View Plans") + alert.addButton(withTitle: "Cancel") + alert.runModal() + } + } + + @IBAction func showPurchaseView(_ sender: Any?) { + if #available(macOS 12.0, *) { + currentViewController()?.presentAsSheet(DebugPurchaseViewController()) + } + } + + private func showAlert(title: String, message: String? = nil) { + Task { @MainActor in + let alert = NSAlert.init() + alert.messageText = title + if let message = message { + alert.informativeText = message + } + alert.addButton(withTitle: "OK") + alert.runModal() + } + } +} + +extension NSMenuItem { + + convenience init(title string: String, action selector: Selector?, target: AnyObject?, keyEquivalent charCode: String = "", representedObject: Any? = nil) { + self.init(title: string, action: selector, keyEquivalent: charCode) + self.target = target + self.representedObject = representedObject + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift b/LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift new file mode 100644 index 0000000000..2151f80638 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift @@ -0,0 +1,30 @@ +// +// RoundedBorder.swift +// +// 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 + +extension View { + func roundedBorder() -> some View { + background(ZStack { + RoundedRectangle(cornerRadius: 8) + .stroke(Color("BlackWhite10"), lineWidth: 1) + RoundedRectangle(cornerRadius: 8) + .fill(Color("BlackWhite1")) + }) + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift new file mode 100644 index 0000000000..b94ea34da2 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift @@ -0,0 +1,120 @@ +// +// PreferencesSubscriptionModel.swift +// +// 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 Account + +public final class PreferencesSubscriptionModel: ObservableObject { + + @Published var isUserAuthenticated: Bool = false + @Published var hasEntitlements: Bool = false + lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() + + private let accountManager: AccountManager + private var actionHandler: PreferencesSubscriptionActionHandlers + private let sheetActionHandler: SubscriptionAccessActionHandlers + + public init(accountManager: AccountManager = AccountManager(), actionHandler: PreferencesSubscriptionActionHandlers, sheetActionHandler: SubscriptionAccessActionHandlers) { + self.accountManager = accountManager + self.actionHandler = actionHandler + self.sheetActionHandler = sheetActionHandler + + let isUserAuthenticated = accountManager.isUserAuthenticated + self.isUserAuthenticated = isUserAuthenticated + + NotificationCenter.default.addObserver(forName: .accountDidSignIn, object: nil, queue: .main) { _ in + self.updateUserAuthenticatedState(true) + } + + NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { _ in + self.updateUserAuthenticatedState(false) + } + } + + private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { + if accountManager.isUserAuthenticated { + ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email) + } else { + ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler) + } + } + + private func updateUserAuthenticatedState(_ isUserAuthenticated: Bool) { + self.isUserAuthenticated = isUserAuthenticated + sheetModel = makeSubscriptionAccessModel() + } + + @MainActor + func learnMoreAction() { + actionHandler.openURL(.purchaseSubscription) + } + + @MainActor + func changePlanOrBillingAction() { + actionHandler.manageSubscriptionInAppStore() + } + + @MainActor + func removeFromThisDeviceAction() { + accountManager.signOut() + } + + @MainActor + func openVPN() { + actionHandler.openVPN() + } + + @MainActor + func openPersonalInformationRemoval() { + actionHandler.openPersonalInformationRemoval() + } + + @MainActor + func openIdentityTheftRestoration() { + actionHandler.openIdentityTheftRestoration() + } + + @MainActor + func openFAQ() { + actionHandler.openURL(.subscriptionFAQ) + } + + @MainActor + func fetchEntitlements() { + print("Entitlements!") + Task { + self.hasEntitlements = await AccountManager().hasEntitlement(for: "dummy1") + } + } +} + +public final class PreferencesSubscriptionActionHandlers { + var openURL: (URL) -> Void + var manageSubscriptionInAppStore: () -> Void + var openVPN: () -> Void + var openPersonalInformationRemoval: () -> Void + var openIdentityTheftRestoration: () -> Void + + public init(openURL: @escaping (URL) -> Void, manageSubscriptionInAppStore: @escaping () -> Void, openVPN: @escaping () -> Void, openPersonalInformationRemoval: @escaping () -> Void, openIdentityTheftRestoration: @escaping () -> Void) { + self.openURL = openURL + self.manageSubscriptionInAppStore = manageSubscriptionInAppStore + self.openVPN = openVPN + self.openPersonalInformationRemoval = openPersonalInformationRemoval + self.openIdentityTheftRestoration = openIdentityTheftRestoration + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift b/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift new file mode 100644 index 0000000000..a770e7a642 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift @@ -0,0 +1,278 @@ +// +// PreferencesSubscriptionView.swift +// +// 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 SwiftUIExtensions + +public struct PreferencesSubscriptionView: View { + @ObservedObject var model: PreferencesSubscriptionModel + @State private var showingSheet = false + @State private var showingRemoveConfirmationDialog = false + + public init(model: PreferencesSubscriptionModel) { + self.model = model + } + + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + + TextMenuTitle(text: UserText.preferencesTitle) + .sheet(isPresented: $showingSheet) { + SubscriptionAccessView(model: model.sheetModel) + } + .sheet(isPresented: $showingRemoveConfirmationDialog) { + Dialog(spacing: 20) { + Image("Placeholder-96x64", bundle: .module) + Text(UserText.removeSubscriptionDialogTitle) + .font(.title2) + .bold() + .foregroundColor(Color("TextPrimary", bundle: .module)) + Text(UserText.removeSubscriptionDialogDescription) + .font(.body) + .multilineTextAlignment(.center) + .fixMultilineScrollableText() + .foregroundColor(Color("TextPrimary", bundle: .module)) + } buttons: { + Button(UserText.removeSubscriptionDialogCancel) { showingRemoveConfirmationDialog = false } + Button(action: { + showingRemoveConfirmationDialog = false + model.removeFromThisDeviceAction() + }, label: { + Text(UserText.removeSubscriptionDialogConfirm) + .foregroundColor(.red) + }) + } + .frame(width: 320) + } + + Spacer() + .frame(height: 20) + + VStack { + if model.isUserAuthenticated { + UniversalHeaderView { + Image("subscription-active-icon", bundle: .module) + .padding(4) + } content: { + TextMenuItemHeader(text: UserText.preferencesSubscriptionActiveHeader) + TextMenuItemCaption(text: UserText.preferencesSubscriptionActiveCaption) + } buttons: { + Button(UserText.addToAnotherDeviceButton) { showingSheet.toggle() } + + Menu { + Button(UserText.changePlanOrBillingButton, action: { model.changePlanOrBillingAction() }) + Button(UserText.removeFromThisDeviceButton, action: { + showingRemoveConfirmationDialog.toggle() + }) + } label: { + Text(UserText.manageSubscriptionButton) + } + .fixedSize() + } + .onAppear { + model.fetchEntitlements() + } + + } else { + UniversalHeaderView { + Image("subscription-inactive-icon", bundle: .module) + .padding(4) + .background(Color.black.opacity(0.06)) + .cornerRadius(4) + } content: { + TextMenuItemHeader(text: UserText.preferencesSubscriptionInactiveHeader) + TextMenuItemCaption(text: UserText.preferencesSubscriptionInactiveCaption) + } buttons: { + Button(UserText.learnMoreButton) { model.learnMoreAction() } + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + Button(UserText.haveSubscriptionButton) { showingSheet.toggle() } + } + } + + Divider() + .foregroundColor(Color.secondary) + .padding(.horizontal, -10) + + SectionView(iconName: "vpn-service-icon", + title: UserText.vpnServiceTitle, + description: UserText.vpnServiceDescription, + buttonName: model.isUserAuthenticated ? "Manage" : nil, + buttonAction: { model.openVPN() }, + enabled: model.hasEntitlements) + + Divider() + .foregroundColor(Color.secondary) + + SectionView(iconName: "pir-service-icon", + title: UserText.personalInformationRemovalServiceTitle, + description: UserText.personalInformationRemovalServiceDescription, + buttonName: model.isUserAuthenticated ? "View" : nil, + buttonAction: { model.openPersonalInformationRemoval() }, + enabled: model.hasEntitlements) + + Divider() + .foregroundColor(Color.secondary) + + SectionView(iconName: "itr-service-icon", + title: UserText.identityTheftRestorationServiceTitle, + description: UserText.identityTheftRestorationServiceDescription, + buttonName: model.isUserAuthenticated ? "View" : nil, + buttonAction: { model.openIdentityTheftRestoration() }, + enabled: model.hasEntitlements) + } + .padding(10) + .roundedBorder() + + PreferencePaneSection { + TextMenuItemHeader(text: UserText.preferencesSubscriptionFooterTitle) + HStack(alignment: .top, spacing: 6) { + TextMenuItemCaption(text: UserText.preferencesSubscriptionFooterCaption) + Button(UserText.viewFaqsButton) { model.openFAQ() } + } + } + } + } +} + +struct UniversalHeaderView: View where Icon: View, Content: View, Buttons: View { + + @ViewBuilder let icon: () -> Icon + @ViewBuilder let content: () -> Content + @ViewBuilder let buttons: () -> Buttons + + init(@ViewBuilder icon: @escaping () -> Icon, @ViewBuilder content: @escaping () -> Content, @ViewBuilder buttons: @escaping () -> Buttons) { + self.icon = icon + self.content = content + self.buttons = buttons + } + + public var body: some View { + HStack(alignment: .top) { + icon() + VStack(alignment: .leading, spacing: 8) { + + content() + HStack { + buttons() + } + .padding(.top, 10) + } + Spacer() + } + .padding(.vertical, 10) + } +} + +public struct SectionView: View { + public var iconName: String + public var title: String + public var description: String + public var buttonName: String? + public var buttonAction: (() -> Void)? + public var enabled: Bool + + public init(iconName: String, title: String, description: String, buttonName: String? = nil, buttonAction: (() -> Void)? = nil, enabled: Bool = true) { + self.iconName = iconName + self.title = title + self.description = description + self.buttonName = buttonName + self.buttonAction = buttonAction + self.enabled = enabled + } + + public var body: some View { + VStack(alignment: .center) { + VStack { + HStack(alignment: .center, spacing: 8) { + Image(iconName, bundle: .module) + .padding(4) + .background(Color("BadgeBackground", bundle: .module)) + .cornerRadius(4) + + VStack(alignment: .leading) { + Text(title) + .frame(maxWidth: .infinity, alignment: .leading) + .fixMultilineScrollableText() + .font(.body) + .foregroundColor(Color("TextPrimary", bundle: .module)) + Text(description) + .frame(maxWidth: .infinity, alignment: .leading) + .fixMultilineScrollableText() + .font(.system(size: 11, weight: .regular, design: .default)) + .foregroundColor(Color("TextSecondary", bundle: .module)) + } + + if let name = buttonName, !name.isEmpty, let action = buttonAction { + Button(name) { action() } + } + } + } + } + .padding(.vertical, 7) + .disabled(!enabled) + .opacity(enabled ? 1.0 : 0.6) + } +} + +enum Const { + + static let pickerHorizontalOffset: CGFloat = { + if #available(macOS 12.0, *) { + return -8 + } else { + return 0 + } + }() + + enum Fonts { + static let popUpButton: NSFont = .preferredFont(forTextStyle: .title1, options: [:]) + static let sideBarItem: Font = .body + static let preferencePaneTitle: Font = .title2.weight(.semibold) + static let preferencePaneSectionHeader: Font = .title3.weight(.semibold) + static let preferencePaneDisclaimer: Font = .subheadline + } +} + +struct TextMenuTitle: View { + let text: String + + var body: some View { + Text(text) + .font(Const.Fonts.preferencePaneTitle) + } +} + +struct TextMenuItemHeader: View { + let text: String + + var body: some View { + Text(text) + .font(Const.Fonts.preferencePaneSectionHeader) + } +} + +struct TextMenuItemCaption: View { + let text: String + + var body: some View { + Text(text) + .frame(maxWidth: .infinity, alignment: .leading) + .fixMultilineScrollableText() + .foregroundColor(Color("GreyTextColor")) + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift new file mode 100644 index 0000000000..ab951e46f1 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift @@ -0,0 +1,57 @@ +// +// AppStoreAccountManagementFlow.swift +// +// 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 StoreKit +import Purchase +import Account + +public final class AppStoreAccountManagementFlow { + + public enum Error: Swift.Error { + case noPastTransaction + case authenticatingWithTransactionFailed + } + + @discardableResult + public static func refreshAuthTokenIfNeeded() async -> Result { + var authToken = AccountManager().authToken ?? "" + + // Check if auth token if still valid + if case let .failure(error) = await AuthService.validateToken(accessToken: authToken) { + print(error) + + if #available(macOS 12.0, *) { + // In case of invalid token attempt store based authentication to obtain a new one + guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.noPastTransaction) } + + switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { + case .success(let response): + if response.externalID == AccountManager().externalID { + authToken = response.authToken + AccountManager().storeAuthToken(token: authToken) + } + case .failure: + return .failure(.authenticatingWithTransactionFailed) + } + } + } + + return .success(authToken) + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift new file mode 100644 index 0000000000..b2d0cbe997 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift @@ -0,0 +1,102 @@ +// +// AppStorePurchaseFlow.swift +// +// 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 StoreKit +import Purchase +import Account + +@available(macOS 12.0, *) +public final class AppStorePurchaseFlow { + + public enum Error: Swift.Error { + case appStoreAuthenticationFailed + case authenticatingWithTransactionFailed + case accountCreationFailed + case purchaseFailed + case somethingWentWrong + } + + public static func purchaseSubscription(with identifier: String, emailAccessToken: String?) async -> Result { + // Trigger sign in pop-up + switch await PurchaseManager.shared.syncAppleIDAccount() { + case .success: + break + case .failure: + return .failure(.appStoreAuthenticationFailed) + } + + let externalID: String + + // Check for past transactions most recent + switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { + case .success(let existingExternalID): + externalID = existingExternalID + case .failure(let error): + switch error { + case .missingAccountOrTransactions: + // No history, create new account + switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { + case .success(let response): + externalID = response.externalID + await AccountManager().exchangeAndStoreTokens(with: response.authToken) + case .failure: + return .failure(.accountCreationFailed) + } + default: + return .failure(.authenticatingWithTransactionFailed) + } + } + + // Make the purchase + switch await PurchaseManager.shared.purchaseSubscription(with: identifier, externalID: externalID) { + case .success: + return .success(()) + case .failure(let error): + print("Something went wrong, reason: \(error)") + AccountManager().signOut() + return .failure(.purchaseFailed) + } + } + + @discardableResult + public static func checkForEntitlements(wait waitTime: Double, retry retryCount: Int) async -> Bool { + var count = 0 + var hasEntitlements = false + + repeat { + hasEntitlements = await !AccountManager().fetchEntitlements().isEmpty + + if hasEntitlements { + break + } else { + count += 1 + try? await Task.sleep(seconds: waitTime) + } + } while !hasEntitlements && count < retryCount + + return hasEntitlements + } +} + +extension Task where Success == Never, Failure == Never { + static func sleep(seconds: Double) async throws { + let duration = UInt64(seconds * 1_000_000_000) + try await Task.sleep(nanoseconds: duration) + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift new file mode 100644 index 0000000000..d628c7e17b --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift @@ -0,0 +1,54 @@ +// +// AppStorePurchaseFlow.swift +// +// 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 StoreKit +import Purchase +import Account + +@available(macOS 12.0, *) +public final class AppStoreRestoreFlow { + + public enum Error: Swift.Error { + case missingAccountOrTransactions + case pastTransactionAuthenticationFailure + case accessTokenObtainingError + case somethingWentWrong + } + + public static func restoreAccountFromPastPurchase() async -> Result { + guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } + + // Do the store login to get short-lived token + let authToken: String + + switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { + case .success(let response): + authToken = response.authToken + case .failure: + return .failure(.pastTransactionAuthenticationFailure) + } + + switch await AccountManager().exchangeAndStoreTokens(with: authToken) { + case .success(let externalID): + return .success(externalID) + case .failure: + return .failure(.accessTokenObtainingError) + } + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json new file mode 100644 index 0000000000..5d30e17341 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.940", + "green" : "0.940", + "red" : "0.940" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.260", + "green" : "0.260", + "red" : "0.260" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json new file mode 100644 index 0000000000..5a03d5f0a8 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.840", + "blue" : "0", + "green" : "0", + "red" : "0" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.840", + "blue" : "255", + "green" : "255", + "red" : "255" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json new file mode 100644 index 0000000000..809dad5e64 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json new file mode 100644 index 0000000000..1d8c180e47 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "apple-id-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/apple-id-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/apple-id-icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..40cb42fd90457f8dda988e8dac4309633f267090 GIT binary patch literal 3923 zcmZvfTaOb*5QX38SIi3%JkZcfbEJ&GWNo^V#dKhqvz!tHWmePwb!dYWA)r&u@PI zeEgX<+Y2?D!ae&RH``B#&vOJkXW_Iz+-z3Y)7h)d-)a*Bfrc2mt?V`CP-YR=y>G1KDfyDCJ(XH&615gr zvh<3zxSV<|oG7j}->W^v+JnBu-`-0$YeFDmwGJ-&6sxFjy`--1gUg}URu#)ZYPYze z&(#|iQ%t@ctskIQx~GJ$D>Pn@-H0OQ#F=1Zy9jTg4JmAG?yk1reH!7g3QqxZEU}Lm zysrj@*d!%2m5?$eZm!{Cqjs9dp4dkKQz>B-q19Si0J2l<`~|8mOKYhN(*-Q9hY+*X zueX@UW^pNs1WY-Vf(C`W;L_3IQI+g+>pqZ5ElG?xoL#7qiYnAK{swew8lXs3G9_12 z=|Y|Yu2YW!l&e_6f-5!msE~zar+y1s;ybS3Uve=^YF4-2D0&2vn3OUc(Rvkf5_yQh z8wUYX7wQvTEip%1SIK|~5LLohO>3dB1xSn27Sl}fpi3MbG1wAfjBSQW8if^mNdP?5 z+~ra1;(f*gsH?TG9Ea!w1*KD)4~ciG86lBaPhPw9rD7?kTog1?L((8(($A^s_1o*M^oAyyC}V-lzZ6yzt0nXYA}ThA0(ky zUui{6?^J+O(dAWOvAWktD;$HA!;aAD7ivWasTFi^jj)rHh@Y9n7H@@I#ssM@zGp4S zf>er~k!-VDFbHs5B|Y}&p%?K%VC*s}HzDIqBVs+T7X;M=$5_jgOQ^DTIYLq@?=`T_ z11&?)L+&&+%m^Ye)$plNpXee??$(vs7L1idAN8VhflUUaRg5)+dmx4Nl>uSvMjg>0 zU%HC94i`IMJ&_k`JG@)uI6KNsCUo60aG)H0id3t^nKWpQ3b|z)bJ(=9LFt9Qtcw+G zDL^XrwB;?Nrj7p4iFjI938z96g{*u+%fzBmWv327(avJVi%=EO5DU$S5EoXz^WAY&h z#HHM&x3$M~9!l~^l%b>)8|xAiJXU5V@0gnpZyAgAgS*Go^qD-goNOvG6rdG!zgCcf zFbg@!W?@!!N|I3qg)$iq3Pq%q6BT0GX=hgE9k&B>J<(}YjFQktN*L}7I{mv=lMIim z%mQgOX-zca{yLF}j@>oI3!z*FG5_?`f^NmYL@|&-9yub_kac(<7M`7xLOCo4EHhqB zzX7ilq-CO=yNKi6b<0?~3l++r2c3`mG10~(!Mj8kv0FTHve2bm^+*$Ql+-~OqcdkJ zn1(ngcZKBX4U3UJS{yo)L%pccoV{lXV01>|lttOm{yL(nV|Yis#^rGbY0x>L=6ZXg zIdvGSDHAN)i~(!`YU}9ZCcrgJ*Fkg@%uW*pCbdBw9;%#j%SsVZnuR+}ps@?<=rHkg zJ-c;olxlhlnd+*-{SSC`57cQx&(bl;#v{=#YC~t~b%P_w$#GKRUtmNUs8nczsR*Qm zvRq5X6_E>Hsr<1@w8+iK5Ng>37ZY(Q^F4(T|z&g%oMN(}xcO{mzHe zciZjmu%AEt#)lNYvtR%Db2Fd4TYcIrz)zct%hikB*ZITkygCgoJVqxQlTz!B7BVP~ zJDqR#yDvBE&3cXrYM_ooAI1AnCvOtKfn18HgXrf literal 0 HcmV?d00001 diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json new file mode 100644 index 0000000000..118ae3c7ce --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "email-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/email-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/email-icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fa392436929480cbee4d0191adad490061a9da6a GIT binary patch literal 1828 zcmZvd+iu%142JLZ6uc?W4yYqqXP_vs9x-ggx^#ENE<8tVGbHvFyF<69ALZCo(pCr% z<+JpY{3SC7Hy2mOSU4dNB(y(%3V@Rnm|Z^i*SCGu$L%j1zUm5O;kBdzr>EPGh|MBx z6Xo9iPqBIEA3+hE(?Zepn^>*I>>~bI)^UD)2IsfCzv@o>7PCu)hfZLTB2WxDO^$i? z`WZi}*MjG-CtckLRbXk7FqV_@nxd%?5O*Q(Mp;-Xeb`CG7v2pTb&kfmtIV?Oq|d6n zoJlPxqfwXPN}wLX`;eVLJ7t~&Fk)fSW5@}U9>cLPQqD25DLI+U>bKlmiAPZu%6nfr zAWaiOFdo(fEo!fPf;L#AB~XD@S!WX(9F)NMX0+=>rj$CZRaTXQ;<%ld@&+vxv5k`A zDT<8f6{lmM7QT?qdbD7R(g&k7tP)db4JKx^qO@HRtXEd=ky2p@d0+ijy~zeFw3SMi z5DKTflgR}-qS6E-iD@rOr1U7(aJ55$-fBmwPlV5jESMM@LWjv!q2O|TD1@RTc0=aUPy2v-R!R42$|3jpIcmMzZ literal 0 HcmV?d00001 diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json new file mode 100644 index 0000000000..08e3f66b26 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "itr-service-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/itr-service-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/itr-service-icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8cdf1fed11c45441d74ad6702fe8acf45780f6a1 GIT binary patch literal 4560 zcmcIo%WfM-5WLS<%*B9x;Bwv%zz`5ivJoUf6e>3(2WGXF8AxP8(t(n%clGSfW6c1e z4TvzTRjGTrySjR3hA(ce-Y#V48IxS_-S2-e&R@Ug%j>7(yW8XXIE=sf^rK&M(FG&$ z;TIRMJozlI!`YVZY)f^vr9Im+o^5H)w$x`^%5y9)E{Nq}dq-_yYSy10hwc6GBi8^| zWOX?1hxH>{UJZY5`eF6%4Zpl?f9ntIFSfjv{QkgXM;$~DMSfV6r_;Yl)ded=47`72 z#qRXs&~I7n9M*-Y>fk~l9+`xxG+N3E0@CA>s*}zrX?TSo#^lR;a-Hl%1|Mdi*476{ z&QpkzR>`CwJ+hO*hG1&lA(w_g<5CS$(kNI-?SU1nR8UqgwU-WrR^#7A0ZI<@JWIoWG7EOd;GHe$2 z0#D;qNs|YyVkk$*WnXRUOe1=ZQ|7#8gf%OQ>ZWn4E9xwlCU(K#UGpp`GO867 zw5hztY1t^PYbVG&Z@^{fS_lW{8Ji4A%~>xk$Lty>qlmQ4=uUeT zlp@qy6NKg@-sqzVbIb`VZ0il7%3|rVH-su7fYH|FNDXGY7`)Kff2udgiAEYlyBD^G zC<2}_ct%KNB4w0wZ?R~DLlv;A-nB}^hPM?w4Rf+ZMK0T-@{D9Dm1WpC?2%C%6Q5ynvl+t2pqEkXEZo8mdK$;Dbvb8`aKhpkG z77h&FDP!T63q{DWP+Q7`)mSEFk?jg0^QrOA+mNKuAhn-5Vlh9Mnxt@LGOrHvGs8fv z$NV(jiP?bpNeah&kawa;Sw#}1p-X`hlM)=I*#zOZVi4UEgh7a+am9K`$v%46fnIG9 zxIlD9jloKcTM{?<0t3MYqmz=s7?ATQIVvz^GsPJNW+1gRm{Mpli7sI@!e7ucADHm< z{Ry4%PC}|@oCicFK{Xf@Bv&D6UT}6XN*kyXq7NQ#5IU*>5_D+mG~RtE7m!pD$pdoM zIPbwm#AfY+2lrhxn3jNpm4?zWRtXX5s3ly~)N%OfD98^RiFN7SiXFn?$$&&`12Rz8 za59L>IWP}qPcxniI(|K2=!_7E@6KVYQ{G_2(L|ifp@OJDneIRZw8L>K#B0S$ns$ZR zi#{O#(0PqFBq>953WCMs0+o1)SGfa9D)WNMre$%L@gORJ*3pyzG{IO$QihOa#9t|X z5Xc4mp?kuN(n2lJFfKz7y+`}#9R~~|Q`EiF?5p1B(l_1c^7W5ydgJw6Tjk5G5cAXA z+P~XwcgF*N|J!&sU;g^fp95dstnY^j_-VM?tl#XO_^|-L z;lSy8g4ML#xqwI1#+O Z-TXTs`*i1D?~kb*qjg{}UR?kD_A{{#h&})S literal 0 HcmV?d00001 diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json new file mode 100644 index 0000000000..45cd94e1bb --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "pir-service-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/pir-service-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/pir-service-icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d52adbe6c3c4b8b3d038c8ef8e77add3075923e8 GIT binary patch literal 4271 zcmc(j%Who95r+5YDS9Ko4!~yLFMuH+mSiJHf+$pWMi!INSY|-ZB%E=e`04XipEGq1 z&5brj2tgYD(_Q!at6Hz#zy0pU+d56&ICJ{PKPO|p`NrJ7`+WKS!)13l%)h1a&wgi| zZOQRtUcct#i=W$f;fqhkFFxtNtlK!B@-~<1Tr`SAE7!4XKPi@Nia`XZB(+HODYBoU?J0;*#llse zN>XH_PcgMhC8@KjPm%rf)9SUiVVG~kYwv4G-YC|rAK#k5sTy-PifpV{G6wOY(gvMGn= zlVCOU>a)2iHu&nuCAUFqE2#;ZT#A(pgT*FA3sxK-gJ8w?l7nD1R~OTKJ3Aj8x(m7; zO66qqtpsmwv|zzTtzeI0dwOpqbPGNJrs!ke-gY*mR^2dhQQ|CGr8>6OP{Tg(XJfX| zKxida!4~%fa)D3-X&g}7ypPWUhT2>uG8$KED(38gHVKF}ghFj^5=GE!?u13#N=r@= zDLHZ%8f4wgjh6W%MA1r)q)d`m`M#0)P~i-67j5#*jb_U&#yHF?sg}}7NR`~=iag17 zE~%JeTZk=iRjYk524@wLhU=k_xD5zj^Z-|Fq($pXDU}ioR<#DqETgj2I^xq(f#X&m zyiLUyYH9U#RPT!jJUnm4_?tFkr;>6E(s-W8whq^I>r&1X4OnT>JL2ysmBPd#VZKpg zAdLnTB&JdBgW)5o%V5Q&+T~QvJ&(3--8DXRXfR183BB{KOB;bzR|8!pB}Xhd)dF7* zwk3*)1gRmBM$W*ZVV)=*GtJvS2wjk#;nv)nSRsm#1kyc!g3m|#|gBC7!wSsC|D^p2+TzA9gXaa zU%oGagiwXtE+q;#6`NB@j55KeJZJ|{PS!4zuN#II&V2u_?TMU_LjDh*~vh5%8zX86t> z6hf$NT=gx=tY!A)lW`B3;K`uA2HR42Y0IH&FN?E`WN<}K{k@u7&el`!Lusl&AmAOW_Q^= zoIX9j`uXYb=b+r)olX}c4vzreez^bTfc4{%zI(b1cPQRI Ly?XWTFW>zS3|3Il literal 0 HcmV?d00001 diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json new file mode 100644 index 0000000000..72547fd84b --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "subscription-active-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/subscription-active-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/subscription-active-icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..823d59197d6e9e41b60651d9ba177c011c3d8b03 GIT binary patch literal 3346 zcmb7GO>g5i5WVwP@DgCRKqP*X0u}+f&Za2ZMN_A@q6e+4I4*1{mE|JsV*h&|DUp&k zZjK1m9mV5 zkMbzI2KjAdxo!{Tt~pB+x7?hXs^7KEFMmDOR6+0RiX8i{E)V20g?S;Q76q-Jst5cs zi(lL@>(sEeIXzC!ZhQK9fFIk(Gs0r?@$`va;#3BorH9jgvRZC7XV^yt&a`u4R2Uum zPX%U#ReDqyeNPHs=q9C?S@`vU%7pp^xh?y??wXJ+w>DJ8AzJ7bmD9lV=k;lM>&A&Fnsqd(x1fUmIZ+-P9Q`v}FYi5gxl$YE(GQ z(4%~WZRV782)EAAg@rNnKwQR@l1czm)=JGO5G`DxfuMgnCn@xd01ZX&F-&nwLwE-a z(}M?WkB|#vrSKQSS)o)xWyaC)&?4anNS&bq?MwNF>JcTm0hh>}#@*V0X{-D$i$p0< z`x)n&DF(zy#-!I1+!k6w4~qi^QWLN}@yQ4>!MtrN1crx)6ef=Vq@2?%IB2AwL^3m0 z3Bv-L1PQWI;<$`)Z^}3&0+qvQbjVy&6jpgXP~xaQ5{y|53Z5Zs!g0Dsq`9qc;%X3jT%AS$EQhPlZ>S%K|FpomQ2O02_OYv)smauXMm!UVyaq%om0>C9@PfpX%W%q5NF zR8mNY&%e>VgeBqmQX-h;)tUE-9|^;Ig=@jm_(0*zUbNpfP21ze5MP;bAZ_bm8s#7B z&8~dkE*>z+x9Px>6q(lbu|0KFeWd=06ranhw!x@{ML)$Ncw2VBD0)YLX0y&i!x{YR z>W5u;-1rl$X|%IyPtfI~yOaM%ATuVnV7e{0a4Z_c$Q~#0s_e^syIrb2b@e@Av>-!P z`M=a?jUfnl{5YdQqD_Rf?{of}LB9JeAkNWhmXNR-el2z(fxo5~zI!frN#Cm?)OY1> zUw6cZ?fYxIY~CE(L;aS%*=^nqq0858+fyHwa1Ht0?(Z6BM97YP+4bSjk~u>zEoo>e zNQ}>PcU7Hps(K!sy?p&teVrzC;=GG;c2VA6x`HXW_Kl-_EO>>Ez}7&((T9fAhkfzWw~K^=|rmI(en; zayO~B2*i-Pho{GA3FehuwNJ6`xwDH9E~cm0DVJH-N2aZ^6J;ddupn zPqEgJoE9%lN-jXX*Ix2~YUdQUo|@0O1*r>IJ2`i+8hVoDv=mbS%F=u6BWfm6XRC_W z7FsJ#ldm;tW{db*2t$6!Cru@hf%;fNjA~ej(o3`5>U%Dc{6WP^VP){8hFB>COkxh? z6~Uq^Z9Euj9E(p$VziV^lp?)>p=*oLT@Nh6@xY3$yGGC6FbG!m z5w;-EuvlXe+bg1xa;h((gjN?MsUbpukPrJPem!heGdWt!3=^DXR!g1wMjnCk468+Q zwLC0e62G1jUDgJ}OI3x6drbwC9!f`KUo|(BTNr}SHTKUz=+6U@u{i4-WM|``J2m6Q z>VShq7FZrTMM~%cY!ruW#fRQC0Yx8M?9CdoW)&U2!fKr-{NT0Y{DX0%!z8&?t>``c zCAkmDLW*G+7HT`WttSqSDrtCINSxPZXQD1p##{-gB=W^uR%)Wx*@`t|%FB7Y#dD7K zRfHVwF+U;9HyVCDeLz!`A)uY8Qngp+z&+?2{YbxFTbGugtt-5@MRZuBJrus_EzJQ3 zL6FdQJ&=NM^3vcDGZpg?NzfZ*{A zC4<14IxwEPnCX*`sn|#W%OMw<^3dK0Kw^vpQmiy6yk;3{f|XKjv_x7pK;l@Ju%i*n zo=PV(Pc4pR%jpOTCs=;OX{C{w_^wUbuuNsDp#ZxXYpJQZVUQwZ(J^+-1^pr=Flr<9 zeQreG^##h0 znjqQOnz4X&j){ja2*$O5wZ_Oq<7{dhWLFETFax$9TN8s7cFi=js3k`O+eBHM#iV9D z6q3|~0qGTMuFeI~t-Rq=<0DpFWy^<|BZKQp!cB(v-%Xq8*U7H-!@YfX$=iLtd?YBw zINtr~2-~;A_nXajzjNv`wwYvufK-060+pEpFbPmnwCY;_Cf7|8?T6XdQ2gYG|ce!RLM zRc95PALE>EKeAW9%6|v)*-6ao&rY%ecgKMDg`ckWtLyFM!>ylg=l7GiIxV#h3QEo~ zJK)2Q5i_3L<_J~twBHfpW~82nkoo)zC}8O?pvV}ygZ8(ptLyo8GWC9V2_27j+nf1` rdwg~AWFh6`e7oH{D|irm^7iV#ne4}x-RgEf%Aq;a^yty6Uta$Y0Y4zk literal 0 HcmV?d00001 diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json new file mode 100644 index 0000000000..8933667242 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "sync-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/sync-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/sync-icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..17fe5ef4551ab2eec45d5af6d009b88bd011e867 GIT binary patch literal 2952 zcmcJR%WfM-5JmU%6}>TFFVyUJbpwWgSbiW#f+%u!MivY;mK8{3BGQ4Aug|F&&J1N` zkxjUenwQn}yj9iDE?>Vr^H#df8)tUE{?-}u@}*h5`Eq#o{;)pu^pDOH@tJx;cPxV{Y`9hKBR0m_uVY^^swJ-yJAIJY{hVhlEj(&uG-+G zxL^chzP4(b6&Ivtm1@Cp7j4Knhh&n?*$0;t zYQDJAOtdjo$@tl!FG|)3XYfuz7g`Nr0ll+>n98|QTa0Fbf|Y=lLEkJFP+YUf;PR*~ zSl0%-i`5yzl{~pJgSe8>?YuNIgT73)SWKKiiz_oIHjMtE?*z=pIA#+97f1hQS7vDN z@0gVt^l8e9*h$+7bhOIV8B8{|P*O0sLOmqp5~y17NgAmNwn{3nq1fUCRI`^$^VPu$ zN}IfB)gYCxo^!P>desMcvNY<*p}LS8Dsqmo=*3n_Es={!Bdm}{Y{@Z|LOBD>%4$&> zL=X~<;+)GsCa1bLu(?4nTum^oKuH=RDE~6zC8QR!wn&1l^cij7GAi{Rr!t7=EZ#E{ z@Uu92L=UcHde!FE5+qlZ#bpg}v=tvUho`(6SF`8uffOUebc-oi7t6f&{r+H={|KI zyKVQ=FfhXZIxrd>b|lW6QPK=Dx7JqP&H~_eXT*k3b&)~8A-uwV2qK9 zV%<22sXzlEB`|>g#1*f_*BJzwa&8OVXhLhP&koL`EC?rQJ^fOEG8Y7y=5Y+w}DzaQWJgjf;`iD-d_w8%a@qE9#?_Zeb qx7RPmq^z!XyMxhyhXk+Q-~Qd>{qUk)KOBZ|Sii1&_Uz4%Z~pAwo?p(|+v#uZr|o^$-cMf++nxMuVz5!=KA7xsQL@Em7eiFW z_OS#Vi*kJmAt$d)oCC=7%K~jXc3AHX_7>EHKAGrzX!f!KE7^GrPJWYOGhp%f2K31~ z?^afTtaq+Ff?V>YWZAk3%cN)}($Gg+tO-~%*6C2a21_F1z!sN{9=$q~*aH}SG|9+T z?W1)WsDx+=)_k9Ai7BblTVsk1veox7dTX43(PvF`KqeI(fE*s+P9Uvq)9e&~ve&&g#9_oYXtY{Z>fQt> z9&<<~I?1{itiZJ)g&3V=ot+E0W{qcfk48XhqOH0UgM{q~vh+#k08x^k3!2)Az36fc zA*4(j$&WT=-bW(5$wtbJX`?L`YgmpN384~yEkgkk_D*w~E*r#NJ6rx#+Jx;SDq?K2h2CoYxF@13s&Q<6gwUmLQ z+9AvM1ZL3c%1Ir3sax(dy$gACWqlxz9GF!n^E+XM6daub!X#!VTsu!rvQLM^n2)&! zwk4H%sLYU6dQAzL(nR-&Etco31MD?q+YwhVg)smd`MDD;(;beWGa^c*89nOhvw{lR zODEN+a8hg}hZtgQ?NAhDiin10l`x{ICS)3EkJLN#hsw~E+A&e=OIT+nC=&auE0U_l zW|c?AA~7w@ye8R#UUXi~3nA{cb6tcuP=Sw)@{K#;_638Ja#xCX6}>2|$?gBkl>B@yfNRdALwP4rya79?17MY8rHX%-c6ebL6lGE(a35E(u? zCZRqeu8hzsPW102_B%3SCC3AmWsK^>IZ8&>Nl6=q*!2-`{Fdrk@yWILATFRJad60F8pknKwU(Zev`=%8T7CGY+jT$J ztBC#oSCKT+@$Xj=#BZYR%+7C+xS$+Dv?f$RM(Zk~hGDbOv#gY}q6QLI+goSOUgX4`LIY|+E?|pX?9D5u6eF9b zTvtpiu{6%A28g1|qZWoKcSG66)kSV&47TN7Afh+zSPI*qT3AjndSJEA%{vxr4O-mR zy>l=vNQ%&cS<-ogw$=~{mUZyAA>uc+^DuL)I223KnKbGiS-UUL%v0c2%~`m8KTf`{gi z@2)BF%gu3fx4(J3_4C85k?LGSv2g!-_A0EhN=>F!cN|N4HPJsEp% zPlBuZNK_TyXJ=-sNK#wDHMUGsM0qYV* zXyhdR2?2w^RUB|R5Lm(`p`P#_#X^2k1o@Oj`4I~k0CPxjtynX32I z+iFw&{_R`WsuP`_0$<;{p|PV4gT;1Zq!IEfQ1yN=-T;ef&2aXOb$+m1H=51{FN2|l z^_+tBX?f^?P(Wvp8cD3_>!ipB$nuTFY}WxBC@Sv$K$Z2lWHUlXbJ~nB#BBsK=9<3m zENlo(KR|**lEsiyHi<#@vgKie+OfXJ%#nt{qt^HlvXRhrN0uQf2H8FD-kgU}+)gjj)SdO5> zs!#LbKWtq+BaEv2`p*4+>3+X-<$~1$>-(k4E93vTbhRSZv+RaydBjl^pW5Ey$~DW? zrlMJ3_p$cOH-}E1PN{(63bD93cyK|0NK)H$xgxCCWVvEGIGs`fye@>Ws?p_Yur61m zJQBaju_i2f%m^hjiWxD~6rkG0Iov!$_PS~CcWOFx6&Vs;T+2>FydYG0vk0QD z!)f^h&!M z``MSKW=DLU+s-GS56>V5DaEk?V!$nTEiO9PE!Cdm~W&mPZz zUrzJ+vxoJgi>ml*+N0_{gQj=;>I?=w-o`rkn=;4d%sFmg$8Kybx5xDX8`vD{vFYvD zJC?x;I^6btXe}ELRAnQ|%uXi_YPO-pe+7mlP&s)e3aczg-_P^QtS=;GIVi969nsmx zo?1~c4(TasSUpM#iX2c-2rXbT9byz@&lD-L+md&6AXA0dg*+>LU6yTL7hTM4qlW@8 zpz1<Qx$3kw0X&x+xcgB~Bc9woD) zh+c;@2nMxlM$^a#oB&8SYg$yX4#oR0xPaOfSrCSR5i&ykV+xr)P6XRq&!fRPgZ_%; zH<))A${xq-`Q4`X)&-W&;u03!wsma76|?Gx+eZLJb(BokER`_#!Z2Fnc~V{7o=R4%Hf@-20;2O*o$po(>bliId6_Gr!$I+Knunu%8TcS{mCncBJ z4>zA$knL2#8OoBe6_rpn8e1o$Dm!Z_W|e5hup4jiA;X8y1s!_Gp>_c$jSa0sb_aYd z@($g)8yhzcJ^V8^V91;<&WSAY)En?~UI}-=c!zCLo<7L<&~^j9`-vK~jL3NFO@~k? zdj{+hWOurlI^;aE-x?Pi$s0^3BkV&vK*%C%v|xnR#|>dHV=c05`pUsS1ajz@kNc(Hmn z3-H_g=5lr0FY=U>Kkl;%TYgb^wA~!$4K%WJTEG|l;owl?)QwP4U=d0j_^ZV)mv3Nd zO$O)nV)3KxC%kJ;?BAzf29{C{A7~3nB)C_FnfRmCVYS}AJAU=^Zhl+gV@j2-BL2Vb zy?4F_ERVtE#ivG*6(1M)@rO?VIg}vB& String { + switch channel { + case .appleID: + return UserText.activateModalAppleIDDescription + case .email: + return UserText.activateModalEmailDescription + case .sync: + return UserText.activateModalSyncDescription + } + } + + public func buttonTitle(for channel: AccessChannel) -> String? { + switch channel { + case .appleID: + return UserText.restorePurchasesButton + case .email: + return UserText.enterEmailButton + case .sync: + return UserText.goToSyncSettingsButton + } + } + + public func handleAction(for channel: AccessChannel) { + switch channel { + case .appleID: + actionHandlers.restorePurchases() + case .email: + actionHandlers.openURLHandler(.activateSubscriptionViaEmail) + case .sync: + actionHandlers.goToSyncPreferences() + } + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift new file mode 100644 index 0000000000..8ece29fe02 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift @@ -0,0 +1,79 @@ +// +// ShareSubscriptionAccessModel.swift +// +// 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 + +public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { + public var title = UserText.shareModalTitle + public var description = UserText.shareModalDescription + + private var actionHandlers: SubscriptionAccessActionHandlers + + private var email: String? + private var hasEmail: Bool { !(email?.isEmpty ?? true) } + + public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?) { + self.actionHandlers = actionHandlers + self.email = email + } + + public func descriptionHeader(for channel: AccessChannel) -> String? { + hasEmail && channel == .email ? email : nil + } + + public func description(for channel: AccessChannel) -> String { + switch channel { + case .appleID: + return UserText.shareModalAppleIDDescription + case .email: + return hasEmail ? UserText.shareModalNoEmailDescription : UserText.shareModalHasEmailDescription + case .sync: + return UserText.shareModalSyncDescription + } + } + + public func buttonTitle(for channel: AccessChannel) -> String? { + switch channel { + case .appleID: + return nil + case .email: + return hasEmail ? UserText.manageEmailButton : UserText.enterEmailButton + case .sync: + return UserText.goToSyncSettingsButton + } + } + + public func handleAction(for channel: AccessChannel) { + switch channel { + case .appleID: + actionHandlers.restorePurchases() + case .email: + let url: URL = hasEmail ? .manageSubscriptionEmail : .addEmailToSubscription + + Task { + await AppStoreAccountManagementFlow.refreshAuthTokenIfNeeded() + + DispatchQueue.main.async { + self.actionHandlers.openURLHandler(url) + } + } + case .sync: + actionHandlers.goToSyncPreferences() + } + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift new file mode 100644 index 0000000000..aee70c96a9 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift @@ -0,0 +1,49 @@ +// +// SubscriptionAccessModel.swift +// +// 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 + +public protocol SubscriptionAccessModel { + var items: [AccessChannel] { get } + + var title: String { get } + var description: String { get } + + func descriptionHeader(for channel: AccessChannel) -> String? + func description(for channel: AccessChannel) -> String + func buttonTitle(for channel: AccessChannel) -> String? + func handleAction(for channel: AccessChannel) +} + +extension SubscriptionAccessModel { + public var items: [AccessChannel] { AccessChannel.allCases } + + public func descriptionHeader(for channel: AccessChannel) -> String? { nil } +} + +public final class SubscriptionAccessActionHandlers { + var restorePurchases: () -> Void + var openURLHandler: (URL) -> Void + var goToSyncPreferences: () -> Void + + public init(restorePurchases: @escaping () -> Void, openURLHandler: @escaping (URL) -> Void, goToSyncPreferences: @escaping () -> Void) { + self.restorePurchases = restorePurchases + self.openURLHandler = openURLHandler + self.goToSyncPreferences = goToSyncPreferences + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift new file mode 100644 index 0000000000..523fb91fcc --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift @@ -0,0 +1,99 @@ +// +// SubscriptionAccessRow.swift +// +// 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 SwiftUIExtensions + +public struct SubscriptionAccessRow: View { + let iconName: String + let name: String + let descriptionHeader: String? + let description: String + let isExpanded: Bool + let buttonTitle: String? + let buttonAction: (() -> Void)? + + public init(iconName: String, name: String, descriptionHeader: String? = nil, description: String, isExpanded: Bool, buttonTitle: String? = nil, buttonAction: (() -> Void)? = nil) { + self.iconName = iconName + self.name = name + self.descriptionHeader = descriptionHeader + self.description = description + self.isExpanded = isExpanded + self.buttonTitle = buttonTitle + self.buttonAction = buttonAction + } + + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .center, spacing: 8) { + Image(iconName, bundle: .module) + + Text(name) + .font(.system(size: 14, weight: .regular, design: .default)) + + Spacer() + .contentShape(Rectangle()) + + Image(systemName: "chevron.down") + .rotationEffect(Angle(degrees: isExpanded ? -180 : 0)) + + } + .padding([.top, .bottom], 8) + .drawingGroup() + + if isExpanded { + VStack(alignment: .leading, spacing: 4) { + + if let header = descriptionHeader, !header.isEmpty { + Text(header) + .bold() + .foregroundColor(Color("TextPrimary", bundle: .module)) + } + + Text(description) + .font(.system(size: 13, weight: .regular, design: .default)) + .foregroundColor(Color("TextSecondary", bundle: .module)) + .fixMultilineScrollableText() + + if let title = buttonTitle, let action = buttonAction { + Spacer() + .frame(height: 8) + Button(title) { action() } + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + } + + Spacer() + .frame(height: 4) + } + .transition(.asymmetric(insertion: .opacity.animation(.easeIn(duration: Constants.Animation.contentShowingDuration).delay(Constants.Animation.contentShowingDelay)), + removal: .opacity.animation(.easeOut(duration: Constants.Animation.contentHidingDuration)))) + } + } + .animation(.easeOut(duration: Constants.Animation.duration), value: isExpanded) + } +} + +private enum Constants { + + enum Animation { + static let duration: CGFloat = 0.3 + static let contentHidingDuration = duration * 0.6 + static let contentShowingDuration = duration + static let contentShowingDelay = duration * 0.3 + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift new file mode 100644 index 0000000000..e25354b7e1 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift @@ -0,0 +1,111 @@ +// +// SubscriptionAccessView.swift +// +// 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 SwiftUIExtensions + +public struct SubscriptionAccessView: View { + + @Environment(\.presentationMode) private var presentationMode: Binding + private let model: SubscriptionAccessModel + + private let dismissAction: (() -> Void)? + + @State private var selection: AccessChannel? = .appleID + @State var fullHeight: CGFloat = 0.0 + + public init(model: SubscriptionAccessModel, dismiss: (() -> Void)? = nil) { + self.model = model + self.dismissAction = dismiss + } + + public var body: some View { + VStack(spacing: 8) { + VStack(spacing: 8) { + Text(model.title) + .font(.title2) + .bold() + .foregroundColor(Color("TextPrimary", bundle: .module)) + Text(model.description) + .font(.body) + .multilineTextAlignment(.center) + .fixMultilineScrollableText() + .foregroundColor(Color("TextPrimary", bundle: .module)) + } + .padding(4) + + VStack(spacing: 0) { + ForEach(model.items) { item in + SubscriptionAccessRow(iconName: item.iconName, + name: item.title, + descriptionHeader: model.descriptionHeader(for: item), + description: model.description(for: item), + isExpanded: self.selection == item, + buttonTitle: model.buttonTitle(for: item), + buttonAction: { + dismiss { + model.handleAction(for: item) + } + }) + .contentShape(Rectangle()) + .onTapGesture { + self.selection = item + } + .padding(.vertical, 10) + + if model.items.last != item { + Divider() + } + } + .padding(.horizontal, 20) + + } + .roundedBorder() + .animation(.easeOut(duration: 0.3)) + + Spacer() + .frame(minHeight: 4, idealHeight: 60) + + Divider() + + Spacer() + .frame(height: 8) + + HStack { + Spacer() + + Button("Cancel") { + dismiss() + } + } + } + .padding(20) + .frame(width: 480) + } + + private func dismiss(completion: (() -> Void)? = nil) { + dismissAction?() + presentationMode.wrappedValue.dismiss() + + if let completion = completion { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { + completion() + } + } + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift new file mode 100644 index 0000000000..4b96a8764e --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift @@ -0,0 +1,63 @@ +// +// SubscriptionAccessViewController.swift +// +// 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 AppKit +import Account +import SwiftUI + +public final class SubscriptionAccessViewController: NSViewController { + + private let accountManager: AccountManager + private var actionHandlers: SubscriptionAccessActionHandlers + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public init(accountManager: AccountManager = AccountManager(), actionHandlers: SubscriptionAccessActionHandlers) { + self.accountManager = accountManager + self.actionHandlers = actionHandlers + super.init(nibName: nil, bundle: nil) + } + + public override func loadView() { + let subscriptionAccessView = SubscriptionAccessView(model: makeSubscriptionAccessModel(), + dismiss: { [weak self] in + guard let self = self else { return } + self.presentingViewController?.dismiss(self) + }) + + let hostingView = NSHostingView(rootView: subscriptionAccessView) + let size = hostingView.fittingSize + + view = NSView(frame: NSRect(x: 0, y: 0, width: size.width, height: size.height)) + hostingView.frame = view.bounds + hostingView.autoresizingMask = [.height, .width] + hostingView.translatesAutoresizingMaskIntoConstraints = true + + view.addSubview(hostingView) + } + + private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { + if accountManager.isUserAuthenticated { + ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email) + } else { + ActivateSubscriptionAccessModel(actionHandlers: actionHandlers) + } + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift b/LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift new file mode 100644 index 0000000000..dbb018b53e --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift @@ -0,0 +1,44 @@ +// +// URL+Subscription.swift +// +// 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 + +public extension URL { + + static var purchaseSubscription: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/welcome")! + } + + static var subscriptionFAQ: URL { + URL(string: "https://duckduckgo.com/about")! + } + + + // MARK: - Subscription Email + static var activateSubscriptionViaEmail: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/activate")! + } + + static var addEmailToSubscription: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/add-email")! + } + + static var manageSubscriptionEmail: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/manage")! + } +} diff --git a/LocalPackages/Subscription/Sources/Subscription/UserText.swift b/LocalPackages/Subscription/Sources/Subscription/UserText.swift new file mode 100644 index 0000000000..dc53af7b28 --- /dev/null +++ b/LocalPackages/Subscription/Sources/Subscription/UserText.swift @@ -0,0 +1,90 @@ +// +// UserText.swift +// +// 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 + +enum UserText { + // MARK: - Subscription preferences + + static let preferencesTitle = NSLocalizedString("subscription.preferences.title", value: "Privacy Pro", comment: "Title for the preferences pane for the subscription") + + static let vpnServiceTitle = NSLocalizedString("subscription.preferences.services.vpn.title", value: "VPN", comment: "Title for the VPN service listed in the subscription preferences pane") + static let vpnServiceDescription = NSLocalizedString("subscription.preferences.services.vpn.description", value: "Full-device protection with the VPN built for speed and security.", comment: "Description for the VPN service listed in the subscription preferences pane") + + static let personalInformationRemovalServiceTitle = NSLocalizedString("subscription.preferences.services.personal.information.removal.title", value: "Personal Information Removal", comment: "Title for the Personal Information Removal service listed in the subscription preferences pane") + static let personalInformationRemovalServiceDescription = NSLocalizedString("subscription.preferences.services.personal.information.removal.description", value: "Find and remove your personal information from sites that store and sell it.", comment: "Description for the Personal Information Removal service listed in the subscription preferences pane") + + static let identityTheftRestorationServiceTitle = NSLocalizedString("subscription.preferences.services.identity.theft.restoration.title", value: "Identity Theft Restoration", comment: "Title for the Identity Theft Restoration service listed in the subscription preferences pane") + static let identityTheftRestorationServiceDescription = NSLocalizedString("subscription.preferences.services.identity.theft.restoration.description", value: "Restore stolen accounts and financial losses in the event of identity theft.", comment: "Description for the Identity Theft Restoration service listed in the subscription preferences pane") + + // MARK: Preferences footer + static let preferencesSubscriptionFooterTitle = NSLocalizedString("subscription.preferences.subscription.footer.title", value: "Questions about Privacy Pro?", comment: "Title for the subscription preferences pane footer") + static let preferencesSubscriptionFooterCaption = NSLocalizedString("subscription.preferences.subscription.footer.caption", value: "Visit our Privacy Pro help pages for answers to frequently asked questions.", comment: "Caption for the subscription preferences pane footer") + static let viewFaqsButton = NSLocalizedString("subscription.preferences.view.faqs.button", value: "View FAQs", comment: "Button to open page for FAQs") + + // MARK: Preferences when subscription is active + static let preferencesSubscriptionActiveHeader = NSLocalizedString("subscription.preferences.subscription.active.header", value: "Privacy Pro is active on this device", comment: "Header for the subscription preferences pane when the subscription is active") + static let preferencesSubscriptionActiveCaption = NSLocalizedString("subscription.preferences.subscription.active.caption", value: "Your monthly Privacy Pro subscription renews on April 20, 2027.", comment: "Caption for the subscription preferences pane when the subscription is active") + + static let addToAnotherDeviceButton = NSLocalizedString("subscription.preferences.add.to.another.device.button", value: "Add to Another Device…", comment: "Button to add subscription to another device") + static let manageSubscriptionButton = NSLocalizedString("subscription.preferences.manage.subscription.button", value: "Manage Subscription", comment: "Button to manage subscription") + static let changePlanOrBillingButton = NSLocalizedString("subscription.preferences.change.plan.or.billing.button", value: "Change Plan or Billing...", comment: "Button to add subscription to another device") + static let removeFromThisDeviceButton = NSLocalizedString("subscription.preferences.remove.from.this.device.button", value: "Remove From This Device...", comment: "Button to remove subscription from this device") + + // MARK: Preferences when subscription is inactive + static let preferencesSubscriptionInactiveHeader = NSLocalizedString("subscription.preferences.subscription.inactive.header", value: "One subscription, three advanced protections", comment: "Header for the subscription preferences pane when the subscription is inactive") + static let preferencesSubscriptionInactiveCaption = NSLocalizedString("subscription.preferences.subscription.inactive.caption", value: "Get enhanced protection across all your devices and reduce your online footprint for as little as $9.99/mo.", comment: "Caption for the subscription preferences pane when the subscription is inactive") + + static let learnMoreButton = NSLocalizedString("subscription.preferences.learn.more.button", value: "Learn More", comment: "Button to open a page where user can learn more and purchase the subscription") + static let haveSubscriptionButton = NSLocalizedString("subscription.preferences.i.have.a.subscription.button", value: "I Have a Subscription", comment: "Button enabling user to activate a subscription user bought earlier or on another device") + + // MARK: - Remove from this device dialog + static let removeSubscriptionDialogTitle = NSLocalizedString("subscription.dialog.remove.title", value: "Remove From This Device?", comment: "Remove subscription from device dialog title") + static let removeSubscriptionDialogDescription = NSLocalizedString("subscription.dialog.remove.description", value: "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices.", comment: "Remove subscription from device dialog subtitle description") + static let removeSubscriptionDialogCancel = NSLocalizedString("subscription.dialog.remove.cancel.button", value: "Cancel", comment: "Button to cancel removing subscription from device") + static let removeSubscriptionDialogConfirm = NSLocalizedString("subscription.dialog.remove.confirm", value: "Remove Subscription", comment: "Button to confirm removing subscription from device") + + // MARK: - Services for accessing the subscription + static let appleID = NSLocalizedString("subscription.access.channel.appleid.name", value: "Apple ID", comment: "Service name displayed when accessing subscription using AppleID account") + static let email = NSLocalizedString("subscription.access.channel.email.name", value: "Email", comment: "Service name displayed when accessing subscription using email address") + static let sync = NSLocalizedString("subscription.access.channel.sync.name", value: "Sync", comment: "Service name displayed when accessing sync feature") + + // MARK: - Activate subscription modal + static let activateModalTitle = NSLocalizedString("subscription.activate.modal.title", value: "Activate your subscription on this device", comment: "Activate subscription modal view title") + static let activateModalDescription = NSLocalizedString("subscription.activate.modal.description", value: "Access your Privacy Pro subscription on this device via Sync, Apple ID or an email address.", comment: "Activate subscription modal view subtitle description") + + static let activateModalAppleIDDescription = NSLocalizedString("subscription.activate.modal.appleid.description", value: "Your subscription is automatically available on any device signed in to the same Apple ID.", comment: "Activate subscription modal description for Apple ID channel") + static let activateModalEmailDescription = NSLocalizedString("subscription.activate.modal.email.description", value: "Use your email to access your subscription on this device.", comment: "Activate subscription modal description for email address channel") + static let activateModalSyncDescription = NSLocalizedString("subscription.activate.modal.sync.description", value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", comment: "Activate subscription modal description for sync service channel") + + // MARK: - Share subscription modal + static let shareModalTitle = NSLocalizedString("subscription.share.modal.title", value: "Use your subscription on all your devices", comment: "Share subscription modal view title") + static let shareModalDescription = NSLocalizedString("subscription.share.modal.description", value: "Access your Privacy Pro subscription on any of your devices via Sync, Apple ID or by adding an email address.", comment: "Share subscription modal view subtitle description") + + static let shareModalAppleIDDescription = NSLocalizedString("subscription.share.modal.appleid.description", value: "Your subscription is automatically available on any device signed in to the same Apple ID.", comment: "Share subscription modal description for Apple ID channel") + static let shareModalHasEmailDescription = NSLocalizedString("subscription.share.modal.has.email.description", value: "You can use this email to activate your subscription on your other devices.", comment: "Share subscription modal description for email address channel") + static let shareModalNoEmailDescription = NSLocalizedString("subscription.share.modal.no.email.description", value: "Add an email address to access your subscription on your other devices. We’ll only use this address to verify your subscription.", comment: "Share subscription modal description for email address channel") + static let shareModalSyncDescription = NSLocalizedString("subscription.share.modal.sync.description", value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", comment: "Share subscription modal description for sync service channel") + + // MARK: - Activate/share modal buttons + static let restorePurchasesButton = NSLocalizedString("subscription.modal.restore.purchases.button", value: "Restore Purchases", comment: "Button for restoring past subscription purchases") + static let manageEmailButton = NSLocalizedString("subscription.modal.manage.email.button", value: "Manage", comment: "Button for opening manage email address page") + static let enterEmailButton = NSLocalizedString("subscription.modal.enter.email.button", value: "Enter Email", comment: "Button for opening page to enter email address") + static let goToSyncSettingsButton = NSLocalizedString("subscription.modal.sync.settings.button", value: "Go to Sync Settings", comment: "Button to open sync settings") +} +*/ diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift new file mode 100644 index 0000000000..16c144c04f --- /dev/null +++ b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift @@ -0,0 +1,29 @@ +// +// SubscriptionTests.swift +// +// 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 XCTest +@testable import Subscription + +final class SubscriptionTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(Subscription().text, "Hello, World!") + } +} diff --git a/LocalPackages/SubscriptionUI/.gitignore b/LocalPackages/SubscriptionUI/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/LocalPackages/SubscriptionUI/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift new file mode 100644 index 0000000000..cba0bb90cd --- /dev/null +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -0,0 +1,45 @@ +// swift-tools-version:5.7 +// Package.swift +// DuckDuckGo +// +// Copyright © 2022 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 PackageDescription + +let package = Package( + name: "SubscriptionUI", + platforms: [ + .iOS(.v14) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "SubscriptionUI", + targets: ["SubscriptionUI"]) + ], + dependencies: [ + .package(path: "../DuckUI"), + .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") + ], + targets: [ + .target( + name: "SubscriptionUI", + dependencies: [ + .product(name: "DuckUI", package: "DuckUI"), + "DesignResourcesKit" + ]) + ] +) diff --git a/LocalPackages/SubscriptionUI/README.md b/LocalPackages/SubscriptionUI/README.md new file mode 100644 index 0000000000..b247174116 --- /dev/null +++ b/LocalPackages/SubscriptionUI/README.md @@ -0,0 +1 @@ +# Subscription UI diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift new file mode 100644 index 0000000000..daac719e9d --- /dev/null +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift @@ -0,0 +1,20 @@ +// +// SubscriptionActionbar.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 SwiftUI diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift new file mode 100644 index 0000000000..456906a107 --- /dev/null +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift @@ -0,0 +1,20 @@ +// +// SubscriptionContainerView.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 SwiftUI diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift new file mode 100644 index 0000000000..fae2f26566 --- /dev/null +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift @@ -0,0 +1,55 @@ +// +// SubscriptionWebView.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 SwiftUI +import WebKit + +struct WebView: UIViewRepresentable { + let urlString: String + + func makeUIView(context: Context) -> WKWebView { + guard let url = URL(string: urlString) else { + return WKWebView() + } + let request = URLRequest(url: url) + let webView = WKWebView() + webView.load(request) + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) { + + } +} + +struct ContentView: View { + var body: some View { + WebView(urlString: "https://www.google.com") + .edgesIgnoringSafeArea(.all) // Use this to make the web view full screen + } +} + +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} From a3c991e8f313a4093fd2035e2ec95eff26ec8ecb Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 27 Nov 2023 11:08:58 +0100 Subject: [PATCH 47/99] Added subscription local packages --- DuckDuckGo.xcodeproj/project.pbxproj | 53 +- DuckDuckGo/OmniBar.swift | 4 +- DuckDuckGo/SettingsViewController.swift | 708 ++++++++++++++++++ LocalPackages/Account/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 5 - .../AppStoreAccountManagementFlow.swift | 3 +- .../PurchaseFlows/AppStorePurchaseFlow.swift | 3 +- .../PurchaseFlows/AppStoreRestoreFlow.swift | 3 +- .../DebugMenu/DebugPurchaseModel.swift | 55 -- .../DebugMenu/DebugPurchaseView.swift | 193 ----- .../DebugPurchaseViewController.swift | 82 -- .../PurchaseInProgressViewController.swift | 75 -- .../DebugMenu/SubscriptionDebugMenu.swift | 211 ------ .../Extensions/RoundedBorder.swift | 30 - .../PreferencesSubscriptionModel.swift | 120 --- .../PreferencesSubscriptionView.swift | 278 ------- .../BadgeBackground.colorset/Contents.json | 38 - .../Colors/Contents.json | 6 - .../Colors/TextPrimary.colorset/Contents.json | 38 - .../TextSecondary.colorset/Contents.json | 38 - .../Subscription.xcassets/Contents.json | 6 - .../Subscription.xcassets/Icons/Contents.json | 6 - .../apple-id-icon.imageset/Contents.json | 15 - .../apple-id-icon.imageset/apple-id-icon.pdf | Bin 3923 -> 0 bytes .../Icons/email-icon.imageset/Contents.json | 15 - .../Icons/email-icon.imageset/email-icon.pdf | Bin 1828 -> 0 bytes .../itr-service-icon.imageset/Contents.json | 15 - .../itr-service-icon.pdf | Bin 4560 -> 0 bytes .../pir-service-icon.imageset/Contents.json | 15 - .../pir-service-icon.pdf | Bin 4271 -> 0 bytes .../Contents.json | 12 - .../subscription-active-icon.pdf | Bin 3346 -> 0 bytes .../Contents.json | 15 - .../subscription-inactive-icon.pdf | Bin 2646 -> 0 bytes .../Icons/sync-icon.imageset/Contents.json | 15 - .../Icons/sync-icon.imageset/sync-icon.pdf | Bin 2952 -> 0 bytes .../vpn-service-icon.imageset/Contents.json | 15 - .../vpn-service-icon.pdf | Bin 3763 -> 0 bytes .../Images/Contents.json | 6 - .../Placeholder-96x64.imageset/Contents.json | 12 - .../Placeholder-96x64.pdf | Bin 6729 -> 0 bytes .../Model/AccessChannel.swift | 47 -- .../ActivateSubscriptionAccessModel.swift | 62 -- .../Model/ShareSubscriptionAccessModel.swift | 79 -- .../Model/SubscriptionAccessModel.swift | 49 -- .../SubscriptionAccessRow.swift | 99 --- .../SubscriptionAccessView.swift | 111 --- .../SubscriptionAccessViewController.swift | 63 -- .../Sources/Subscription/UserText.swift | 90 --- LocalPackages/SubscriptionUI/Package.swift | 6 +- .../Sources/SubscriptionUI/UserText.swift | 257 +++++++ .../Views/SubscriptionContainerView.swift | 70 +- 52 files changed, 1073 insertions(+), 1952 deletions(-) create mode 100644 DuckDuckGo/SettingsViewController.swift rename LocalPackages/Subscription/Sources/{Subscription => }/PurchaseFlows/AppStoreAccountManagementFlow.swift (97%) rename LocalPackages/Subscription/Sources/{Subscription => }/PurchaseFlows/AppStorePurchaseFlow.swift (98%) rename LocalPackages/Subscription/Sources/{Subscription => }/PurchaseFlows/AppStoreRestoreFlow.swift (97%) delete mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/apple-id-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/email-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/itr-service-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/pir-service-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/subscription-active-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-inactive-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-inactive-icon.imageset/subscription-inactive-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/sync-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/vpn-service-icon.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/vpn-service-icon.imageset/vpn-service-icon.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Images/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Images/Placeholder-96x64.imageset/Contents.json delete mode 100644 LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Images/Placeholder-96x64.imageset/Placeholder-96x64.pdf delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/AccessChannel.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ActivateSubscriptionAccessModel.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift delete mode 100644 LocalPackages/Subscription/Sources/Subscription/UserText.swift create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7f588d7f73..2e4be90389 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2364,10 +2364,10 @@ CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; - D6E35F122B10E9890028C8E7 /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = ""; }; - D6E35F132B10EBDE0028C8E7 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Account; sourceTree = ""; }; - D6E35F142B10EBE30028C8E7 /* Purchase */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Purchase; sourceTree = ""; }; - D6E35F152B10EBE60028C8E7 /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = ""; }; + D649903F2B14A18300AFD57B /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Account; sourceTree = ""; }; + D64990402B14A18800AFD57B /* Purchase */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Purchase; sourceTree = ""; }; + D64990412B14A18C00AFD57B /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = ""; }; + D64990422B14A20700AFD57B /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -3349,10 +3349,10 @@ 31E69A60280F4BAD00478327 /* LocalPackages */ = { isa = PBXGroup; children = ( - D6E35F132B10EBDE0028C8E7 /* Account */, - D6E35F152B10EBE60028C8E7 /* Subscription */, - D6E35F142B10EBE30028C8E7 /* Purchase */, - D6E35F122B10E9890028C8E7 /* SubscriptionUI */, + D64990422B14A20700AFD57B /* Subscription */, + D649903F2B14A18300AFD57B /* Account */, + D64990402B14A18800AFD57B /* Purchase */, + D64990412B14A18C00AFD57B /* SubscriptionUI */, 85875B5F29912A2D00115F05 /* SyncUI */, 37FCAACB2993149A000E420A /* Waitlist */, 31794BFF2821DFB600F18633 /* DuckUI */, @@ -7903,7 +7903,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7931,7 +7931,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8055,11 +8055,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTIONS"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8111,7 +8111,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = NETWORK_PROTECTION; @@ -8136,6 +8136,7 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8158,6 +8159,7 @@ CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8223,7 +8225,7 @@ DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8260,7 +8262,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8351,7 +8353,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8380,7 +8382,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8461,7 +8463,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8492,7 +8494,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8585,11 +8587,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTIONS"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8609,6 +8611,7 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8705,7 +8708,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8864,7 +8867,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8893,7 +8896,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8923,7 +8926,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index 9498074044..2436604fb8 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -162,7 +162,7 @@ class OmniBar: UIView { private func configureEditingMenu() { let title = UserText.actionPasteAndGo - UIMenuController.shared.menuItems = [UIMenuItem(title: title, action: #selector(pasteAndGo))] + UIMenuController.shared.menuItems = [UIMenuItem(title: title, action: #selector(self.pasteURLAndGo))] } var textFieldBottomSpacing: CGFloat { @@ -179,7 +179,7 @@ class OmniBar: UIView { } } - @objc func pasteAndGo(sender: UIMenuItem) { + @objc func pasteURLAndGo(sender: UIMenuItem) { guard let pastedText = UIPasteboard.general.string else { return } textField.text = pastedText onQuerySubmitted() diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift new file mode 100644 index 0000000000..ea2a89ed79 --- /dev/null +++ b/DuckDuckGo/SettingsViewController.swift @@ -0,0 +1,708 @@ +// +// SettingsViewController.swift +// DuckDuckGo +// +// Copyright © 2017 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Core +import BrowserServicesKit +import Persistence +import SwiftUI +import Common +import DDGSync +import Combine + +#if APP_TRACKING_PROTECTION +import NetworkExtension +#endif + +#if NETWORK_PROTECTION +import NetworkProtection +#endif + +// swiftlint:disable file_length type_body_length +class SettingsViewController: UITableViewController { + + @IBOutlet weak var defaultBrowserCell: UITableViewCell! + @IBOutlet weak var themeAccessoryText: UILabel! + @IBOutlet weak var fireButtonAnimationAccessoryText: UILabel! + @IBOutlet weak var addressBarPositionCell: UITableViewCell! + @IBOutlet weak var addressBarPositionAccessoryText: UILabel! + @IBOutlet weak var appIconCell: UITableViewCell! + @IBOutlet weak var appIconImageView: UIImageView! + @IBOutlet weak var autocompleteToggle: UISwitch! + @IBOutlet weak var authenticationToggle: UISwitch! + @IBOutlet weak var autoClearAccessoryText: UILabel! + @IBOutlet weak var versionText: UILabel! + @IBOutlet weak var openUniversalLinksToggle: UISwitch! + @IBOutlet weak var longPressPreviewsToggle: UISwitch! + @IBOutlet weak var rememberLoginsCell: UITableViewCell! + @IBOutlet weak var rememberLoginsAccessoryText: UILabel! + @IBOutlet weak var doNotSellCell: UITableViewCell! + @IBOutlet weak var doNotSellAccessoryText: UILabel! + @IBOutlet weak var autoconsentCell: UITableViewCell! + @IBOutlet weak var autoconsentAccessoryText: UILabel! + @IBOutlet weak var emailProtectionCell: UITableViewCell! + @IBOutlet weak var emailProtectionAccessoryText: UILabel! + @IBOutlet weak var macBrowserWaitlistCell: UITableViewCell! + @IBOutlet weak var macBrowserWaitlistAccessoryText: UILabel! + @IBOutlet weak var windowsBrowserWaitlistCell: UITableViewCell! + @IBOutlet weak var windowsBrowserWaitlistAccessoryText: UILabel! + @IBOutlet weak var netPCell: UITableViewCell! + @IBOutlet weak var longPressCell: UITableViewCell! + @IBOutlet weak var versionCell: UITableViewCell! + @IBOutlet weak var textSizeCell: UITableViewCell! + @IBOutlet weak var textSizeAccessoryText: UILabel! + @IBOutlet weak var widgetEducationCell: UITableViewCell! + @IBOutlet weak var syncCell: UITableViewCell! + @IBOutlet weak var autofillCell: UITableViewCell! + @IBOutlet weak var debugCell: UITableViewCell! + @IBOutlet weak var voiceSearchCell: UITableViewCell! + @IBOutlet weak var voiceSearchToggle: UISwitch! + @IBOutlet weak var privacyProSignupCell: UITableViewCell! + + @IBOutlet var labels: [UILabel]! + @IBOutlet var accessoryLabels: [UILabel]! + + private let syncSectionIndex = 1 + private let autofillSectionIndex = 2 + private let appearanceSectionIndex = 3 + private let moreFromDDGSectionIndex = 6 + private let debugSectionIndex = 8 + + private let bookmarksDatabase: CoreDataDatabase + + private lazy var emailManager = EmailManager() + + private lazy var versionProvider: AppVersion = AppVersion.shared + fileprivate lazy var privacyStore = PrivacyUserDefaults() + fileprivate lazy var appSettings = AppDependencyProvider.shared.appSettings + fileprivate lazy var variantManager = AppDependencyProvider.shared.variantManager + fileprivate lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger + fileprivate let syncService: DDGSyncing + fileprivate let syncDataProviders: SyncDataProviders + fileprivate let internalUserDecider: InternalUserDecider +#if NETWORK_PROTECTION + private let connectionObserver = ConnectionStatusObserverThroughSession() +#endif + private var cancellables: Set = [] + + private var shouldShowDebugCell: Bool { + return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild + } + + private var shouldShowVoiceSearchCell: Bool { + AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable + } + + private var shouldShowAutofillCell: Bool { + return featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) + } + + private var shouldShowSyncCell: Bool { + return featureFlagger.isFeatureOn(.sync) + } + + private var shouldShowTextSizeCell: Bool { + return UIDevice.current.userInterfaceIdiom != .pad + } + + private var shouldShowAddressBarPositionCell: Bool { + return UIDevice.current.userInterfaceIdiom != .pad + } + + private lazy var shouldShowNetPCell: Bool = { +#if NETWORK_PROTECTION + if #available(iOS 15, *) { + return featureFlagger.isFeatureOn(.networkProtection) + } else { + return false + } +#else + return false +#endif + }() + + override func viewDidLoad() { + super.viewDidLoad() + + configureAutofillCell() + configureSyncCell() + configureThemeCellAccessory() + configureFireButtonAnimationCellAccessory() + configureAddressBarPositionCell() + configureTextSizeCell() + configureDisableAutocompleteToggle() + configureSecurityToggles() + configureVersionText() + configureUniversalLinksToggle() + configureLinkPreviewsToggle() + configureRememberLogins() + configureDebugCell() + configureVoiceSearchCell() + configureNetPCell() + applyTheme(ThemeManager.shared.currentTheme) + + internalUserDecider.isInternalUserPublisher.dropFirst().sink(receiveValue: { [weak self] _ in + self?.configureAutofillCell() + self?.configureSyncCell() + self?.configureDebugCell() + self?.tableView.reloadData() + + // Scroll to force-redraw section headers and footers + self?.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + }) + .store(in: &cancellables) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + configureFireButtonAnimationCellAccessory() + configureAddressBarPositionCell() + configureTextSizeCell() + configureAutoClearCellAccessory() + configureRememberLogins() + configureDoNotSell() + configureAutoconsent() + configureIconViews() + configureEmailProtectionAccessoryText() + configureMacBrowserWaitlistCell() + configureWindowsBrowserWaitlistCell() + configureSyncCell() + +#if NETWORK_PROTECTION + updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) +#endif + + // Make sure multiline labels are correctly presented + tableView.setNeedsLayout() + tableView.layoutIfNeeded() + } + + init?(coder: NSCoder, + bookmarksDatabase: CoreDataDatabase, + syncService: DDGSyncing, + syncDataProviders: SyncDataProviders, + internalUserDecider: InternalUserDecider) { + + self.bookmarksDatabase = bookmarksDatabase + self.syncService = syncService + self.syncDataProviders = syncDataProviders + self.internalUserDecider = internalUserDecider + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + fatalError("Not implemented") + } + + func openLogins() { + showAutofill() + } + + func openLogins(accountDetails: SecureVaultModels.WebsiteAccount) { + showAutofillAccountDetails(accountDetails) + } + + func openCookiePopupManagement() { + showCookiePopupManagement(animated: true) + } + + @IBSegueAction func onCreateRootDebugScreen(_ coder: NSCoder, sender: Any?, segueIdentifier: String?) -> RootDebugViewController { + guard let controller = RootDebugViewController(coder: coder, + sync: syncService, + bookmarksDatabase: bookmarksDatabase, + internalUserDecider: AppDependencyProvider.shared.internalUserDecider) else { + fatalError("Failed to create controller") + } + + return controller + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.destination is DoNotSellSettingsViewController { + Pixel.fire(pixel: .settingsDoNotSellShown) + return + } else if segue.destination is AutoconsentSettingsViewController { + Pixel.fire(pixel: .settingsAutoconsentShown) + return + } else if let textSizeSettings = segue.destination as? TextSizeSettingsViewController { + Pixel.fire(pixel: .textSizeSettingsShown) + presentationController?.delegate = textSizeSettings + return + } + + if let navController = segue.destination as? UINavigationController, navController.topViewController is FeedbackViewController { + if UIDevice.current.userInterfaceIdiom == .pad { + segue.destination.modalPresentationStyle = .formSheet + } + } + } + + private func configureAutofillCell() { + autofillCell.isHidden = !shouldShowAutofillCell + } + + private func configureSyncCell() { + syncCell.textLabel?.text = "Sync & Backup" + if SyncBookmarksAdapter.isSyncBookmarksPaused || SyncCredentialsAdapter.isSyncCredentialsPaused { + syncCell.textLabel?.text = "⚠️ " + "Sync & Backup" + } + syncCell.isHidden = !shouldShowSyncCell + } + + private func configureVoiceSearchCell() { + voiceSearchCell.isHidden = !shouldShowVoiceSearchCell + voiceSearchToggle.isOn = appSettings.voiceSearchEnabled + } + + private func configureThemeCellAccessory() { + switch appSettings.currentThemeName { + case .systemDefault: + themeAccessoryText.text = UserText.themeAccessoryDefault + case .light: + themeAccessoryText.text = UserText.themeAccessoryLight + case .dark: + themeAccessoryText.text = UserText.themeAccessoryDark + } + } + + private func configureFireButtonAnimationCellAccessory() { + fireButtonAnimationAccessoryText.text = appSettings.currentFireButtonAnimation.descriptionText + } + + private func configureAddressBarPositionCell() { + addressBarPositionCell.isHidden = !shouldShowAddressBarPositionCell + addressBarPositionAccessoryText.text = appSettings.currentAddressBarPosition.descriptionText + } + + private func configureTextSizeCell() { + textSizeCell.isHidden = !shouldShowTextSizeCell + textSizeAccessoryText.text = "\(appSettings.textSize)%" + } + + private func configureIconViews() { + if AppIconManager.shared.isAppIconChangeSupported { + appIconImageView.image = AppIconManager.shared.appIcon.smallImage + } else { + appIconCell.isHidden = true + } + } + + private func configureDisableAutocompleteToggle() { + autocompleteToggle.isOn = appSettings.autocomplete + } + + private func configureSecurityToggles() { + authenticationToggle.isOn = privacyStore.authenticationEnabled + } + + private func configureAutoClearCellAccessory() { + if AutoClearSettingsModel(settings: appSettings) != nil { + autoClearAccessoryText.text = UserText.autoClearAccessoryOn + } else { + autoClearAccessoryText.text = UserText.autoClearAccessoryOff + } + } + + private func configureDoNotSell() { + doNotSellAccessoryText.text = appSettings.sendDoNotSell ? UserText.doNotSellEnabled : UserText.doNotSellDisabled + } + + private func configureAutoconsent() { + autoconsentAccessoryText.text = appSettings.autoconsentEnabled ? UserText.autoconsentEnabled : UserText.autoconsentDisabled + } + + private func configureRememberLogins() { + rememberLoginsAccessoryText.text = PreserveLogins.shared.allowedDomains.isEmpty ? "" : "\(PreserveLogins.shared.allowedDomains.count)" + } + + private func configureVersionText() { + versionText.text = versionProvider.versionAndBuildNumber + } + + private func configureUniversalLinksToggle() { + openUniversalLinksToggle.isOn = appSettings.allowUniversalLinks + } + + private func configureLinkPreviewsToggle() { + longPressCell.isHidden = false + longPressPreviewsToggle.isOn = appSettings.longPressPreviews + } + + private func configureMacBrowserWaitlistCell() { + macBrowserWaitlistCell.detailTextLabel?.text = MacBrowserWaitlist.shared.settingsSubtitle + } + + private func configureWindowsBrowserWaitlistCell() { + windowsBrowserWaitlistCell.isHidden = !WindowsBrowserWaitlist.shared.isAvailable + + if WindowsBrowserWaitlist.shared.isAvailable { + windowsBrowserWaitlistCell.detailTextLabel?.text = WindowsBrowserWaitlist.shared.settingsSubtitle + } + } + + private func configureNetPCell() { + netPCell.isHidden = !shouldShowNetPCell +#if NETWORK_PROTECTION + updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) + connectionObserver.publisher + .receive(on: DispatchQueue.main) + .sink { [weak self] status in + self?.updateNetPCellSubtitle(connectionStatus: status) + } + .store(in: &cancellables) +#endif + } + +#if NETWORK_PROTECTION + private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { + switch NetworkProtectionAccessController().networkProtectionAccessType() { + case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: + netPCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle + case .waitlistInvited, .inviteCodeInvited: + switch connectionStatus { + case .connected: netPCell.detailTextLabel?.text = UserText.netPCellConnected + default: netPCell.detailTextLabel?.text = UserText.netPCellDisconnected + } + } + } +#endif + + private func configureDebugCell() { + debugCell.isHidden = !shouldShowDebugCell + } + + func showSync(animated: Bool = true) { + let controller = SyncSettingsViewController(syncService: syncService, syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) + navigationController?.pushViewController(controller, animated: animated) + } + + private func showAutofill(animated: Bool = true) { + let autofillController = AutofillLoginSettingsListViewController( + appSettings: appSettings, + syncService: syncService, + syncDataProviders: syncDataProviders + ) + autofillController.delegate = self + Pixel.fire(pixel: .autofillSettingsOpened) + navigationController?.pushViewController(autofillController, animated: animated) + } + + func showAutofillAccountDetails(_ account: SecureVaultModels.WebsiteAccount) { + let autofillController = AutofillLoginSettingsListViewController( + appSettings: appSettings, + syncService: syncService, + syncDataProviders: syncDataProviders + ) + autofillController.delegate = self + let detailsController = autofillController.makeAccountDetailsScreen(account) + + var controllers = navigationController?.viewControllers ?? [] + controllers.append(autofillController) + controllers.append(detailsController) + navigationController?.viewControllers = controllers + } + + private func configureEmailProtectionAccessoryText() { + if let userEmail = emailManager.userEmail { + emailProtectionAccessoryText.text = userEmail + } else { + emailProtectionAccessoryText.text = UserText.emailSettingsSubtitle + } + } + + private func showEmailWebDashboard() { + UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) + } + + private func showMacBrowserWaitlistViewController() { + navigationController?.pushViewController(MacWaitlistViewController(nibName: nil, bundle: nil), animated: true) + } + +#if NETWORK_PROTECTION + @available(iOS 15, *) + private func showNetP() { + switch NetworkProtectionAccessController().networkProtectionAccessType() { + case .inviteCodeInvited, .waitlistInvited: + // This will be tidied up as part of https://app.asana.com/0/0/1205084446087078/f + let rootViewController = NetworkProtectionRootViewController { [weak self] in + self?.navigationController?.popViewController(animated: true) + let newRootViewController = NetworkProtectionRootViewController() + self?.pushNetP(newRootViewController) + } + + pushNetP(rootViewController) + default: + navigationController?.pushViewController(VPNWaitlistViewController(nibName: nil, bundle: nil), animated: true) + } + } + + @available(iOS 15, *) + private func pushNetP(_ rootViewController: NetworkProtectionRootViewController) { + navigationController?.pushViewController( + rootViewController, + animated: true + ) + } +#endif + + private func showWindowsBrowserWaitlistViewController() { + navigationController?.pushViewController(WindowsWaitlistViewController(nibName: nil, bundle: nil), animated: true) + } + + func showCookiePopupManagement(animated: Bool = true) { + navigationController?.pushViewController(AutoconsentSettingsViewController.loadFromStoryboard(), animated: animated) + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let cell = tableView.cellForRow(at: indexPath) + + switch cell { + + case defaultBrowserCell: + Pixel.fire(pixel: .defaultBrowserButtonPressedSettings) + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + + case emailProtectionCell: + showEmailWebDashboard() + + case macBrowserWaitlistCell: + showMacBrowserWaitlistViewController() + + case windowsBrowserWaitlistCell: + showWindowsBrowserWaitlistViewController() + + case autofillCell: + showAutofill() + + case syncCell: + showSync() + + case netPCell: + if #available(iOS 15, *) { +#if NETWORK_PROTECTION + showNetP() +#else + break +#endif + } + default: break + } + + } + + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + let theme = ThemeManager.shared.currentTheme + cell.backgroundColor = theme.tableCellBackgroundColor + + if cell == netPCell { + DailyPixel.fire(pixel: .networkProtectionSettingsRowDisplayed) + } + } + + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection: Int) { + if let view = view as? UITableViewHeaderFooterView { + let theme = ThemeManager.shared.currentTheme + view.textLabel?.textColor = theme.tableHeaderTextColor + } + } + + override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection: Int) { + if let view = view as? UITableViewHeaderFooterView { + let theme = ThemeManager.shared.currentTheme + view.textLabel?.textColor = theme.tableHeaderTextColor + } + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let cell = super.tableView(tableView, cellForRowAt: indexPath) + return cell.isHidden ? 0 : UITableView.automaticDimension + } + + override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } + + /// Only use this to hide the header if the entire section can be conditionally hidden. + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if syncSectionIndex == section && !shouldShowSyncCell { + return CGFloat.leastNonzeroMagnitude + } else if autofillSectionIndex == section && !shouldShowAutofillCell { + return CGFloat.leastNonzeroMagnitude + } else if debugSectionIndex == section && !shouldShowDebugCell { + return CGFloat.leastNonzeroMagnitude + } else { + return super.tableView(tableView, heightForHeaderInSection: section) + } + } + + /// Only use this to hide the footer if the entire section can be conditionally hidden. + override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + if syncSectionIndex == section && !shouldShowSyncCell { + return CGFloat.leastNonzeroMagnitude + } else if autofillSectionIndex == section && !shouldShowAutofillCell { + return CGFloat.leastNonzeroMagnitude + } else if debugSectionIndex == section && !shouldShowDebugCell { + return CGFloat.leastNonzeroMagnitude + } else { + return super.tableView(tableView, heightForFooterInSection: section) + } + } + + /// Only use this if the *last cell* in the section is to be conditionally hidden in order to retain the section rounding. + /// If your cell is not the last you don't need to modify the number of rows. + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let rows = super.tableView(tableView, numberOfRowsInSection: section) + if section == moreFromDDGSectionIndex && !shouldShowNetPCell { + return rows - 1 + } else if section == appearanceSectionIndex && UIDevice.current.userInterfaceIdiom == .pad { + // Both the text size and bottom bar settings are at the end of the section so need to reduce the section size appropriately + return rows - 2 + } else { + return rows + } + } + + @IBAction func onVoiceSearchToggled(_ sender: UISwitch) { + var enableVoiceSearch = sender.isOn + let isFirstTimeAskingForPermission = SpeechRecognizer.recordPermission == .undetermined + + SpeechRecognizer.requestMicAccess { permission in + if !permission { + enableVoiceSearch = false + sender.setOn(false, animated: true) + if !isFirstTimeAskingForPermission { + self.showNoMicrophonePermissionAlert() + } + } + + AppDependencyProvider.shared.voiceSearchHelper.enableVoiceSearch(enableVoiceSearch) + } + } + + @IBAction func onAboutTapped() { + navigationController?.pushViewController(AboutViewController(), animated: true) + } + + private func showNoMicrophonePermissionAlert() { + let alertController = NoMicPermissionAlert.buildAlert() + present(alertController, animated: true, completion: nil) + } + + @IBAction func onAuthenticationToggled(_ sender: UISwitch) { + privacyStore.authenticationEnabled = sender.isOn + } + + @IBAction func onDonePressed(_ sender: Any) { + dismiss(animated: true, completion: nil) + } + + @IBAction func onAutocompleteToggled(_ sender: UISwitch) { + appSettings.autocomplete = sender.isOn + } + + @IBAction func onAllowUniversalLinksToggled(_ sender: UISwitch) { + appSettings.allowUniversalLinks = sender.isOn + } + + @IBAction func onLinkPreviewsToggle(_ sender: UISwitch) { + appSettings.longPressPreviews = sender.isOn + } +} + +extension SettingsViewController: Themable { + + func decorate(with theme: Theme) { + view.backgroundColor = theme.backgroundColor + + decorateNavigationBar(with: theme) + configureThemeCellAccessory() + + for label in labels { + label.textColor = theme.tableCellTextColor + } + + for label in accessoryLabels { + label.textColor = theme.tableCellAccessoryTextColor + } + + versionText.textColor = theme.tableCellTextColor + + autocompleteToggle.onTintColor = theme.buttonTintColor + authenticationToggle.onTintColor = theme.buttonTintColor + openUniversalLinksToggle.onTintColor = theme.buttonTintColor + longPressPreviewsToggle.onTintColor = theme.buttonTintColor + voiceSearchToggle.onTintColor = theme.buttonTintColor + + tableView.backgroundColor = theme.backgroundColor + tableView.separatorColor = theme.tableCellSeparatorColor + + UIView.transition(with: view, + duration: 0.2, + options: .transitionCrossDissolve, animations: { + self.tableView.reloadData() + }, completion: nil) + } +} + +extension SettingsViewController { + static var fontSizeForHeaderView: CGFloat { + let contentSize = UIApplication.shared.preferredContentSizeCategory + switch contentSize { + case .extraSmall: + return 12 + case .small: + return 12 + case .medium: + return 12 + case .large: + return 13 + case .extraLarge: + return 15 + case .extraExtraLarge: + return 17 + case .extraExtraExtraLarge: + return 19 + case .accessibilityMedium: + return 23 + case .accessibilityLarge: + return 27 + case .accessibilityExtraLarge: + return 33 + case .accessibilityExtraExtraLarge: + return 38 + case .accessibilityExtraExtraExtraLarge: + return 44 + default: + return 13 + } + } +} + +// MARK: - AutofillLoginSettingsListViewControllerDelegate + +extension SettingsViewController: AutofillLoginSettingsListViewControllerDelegate { + func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { + navigationController?.popViewController(animated: true) + } +} +// swiftlint:enable file_length type_body_length diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index b5198dae91..5e4b768925 100644 --- a/LocalPackages/Account/Package.swift +++ b/LocalPackages/Account/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Account"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "85.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "85.0.2"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 270f16becf..827ef93fa8 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -14,7 +14,6 @@ let package = Package( dependencies: [ .package(path: "../Account"), .package(path: "../Purchase"), - //.package(path: "../SwiftUIExtensions") ], targets: [ .target( @@ -22,10 +21,6 @@ let package = Package( dependencies: [ .product(name: "Account", package: "Account"), .product(name: "Purchase", package: "Purchase"), - //.product(name: "SwiftUIExtensions", package: "SwiftUIExtensions") - ], - resources: [ - .process("Resources") ]), .testTarget( name: "SubscriptionTests", diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift b/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreAccountManagementFlow.swift similarity index 97% rename from LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift rename to LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreAccountManagementFlow.swift index ab951e46f1..39b0351603 100644 --- a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift +++ b/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreAccountManagementFlow.swift @@ -18,7 +18,6 @@ import Foundation import StoreKit -import Purchase import Account public final class AppStoreAccountManagementFlow { @@ -36,7 +35,7 @@ public final class AppStoreAccountManagementFlow { if case let .failure(error) = await AuthService.validateToken(accessToken: authToken) { print(error) - if #available(macOS 12.0, *) { + if #available(macOS 12.0, iOS 15.0, *) { // In case of invalid token attempt store based authentication to obtain a new one guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.noPastTransaction) } diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift b/LocalPackages/Subscription/Sources/PurchaseFlows/AppStorePurchaseFlow.swift similarity index 98% rename from LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift rename to LocalPackages/Subscription/Sources/PurchaseFlows/AppStorePurchaseFlow.swift index b2d0cbe997..1e1e90e67a 100644 --- a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift +++ b/LocalPackages/Subscription/Sources/PurchaseFlows/AppStorePurchaseFlow.swift @@ -18,10 +18,9 @@ import Foundation import StoreKit -import Purchase import Account -@available(macOS 12.0, *) +@available(macOS 12.0, iOS 15.0, *) public final class AppStorePurchaseFlow { public enum Error: Swift.Error { diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift b/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreRestoreFlow.swift similarity index 97% rename from LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift rename to LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreRestoreFlow.swift index d628c7e17b..42caa8a4c8 100644 --- a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift +++ b/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreRestoreFlow.swift @@ -18,10 +18,9 @@ import Foundation import StoreKit -import Purchase import Account -@available(macOS 12.0, *) +@available(macOS 12.0, iOS 15.0, *) public final class AppStoreRestoreFlow { public enum Error: Swift.Error { diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift deleted file mode 100644 index 5225d59cf3..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseModel.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// DebugPurchaseModel.swift -// -// 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 StoreKit -import Purchase -import Account - -@available(macOS 12.0, *) -public final class DebugPurchaseModel: ObservableObject { - - var purchaseManager: PurchaseManager - var accountManager: AccountManager = AccountManager() - - @Published var subscriptions: [SubscriptionRowModel] - - init(manager: PurchaseManager, subscriptions: [SubscriptionRowModel] = []) { - self.purchaseManager = manager - self.subscriptions = subscriptions - } - - @MainActor - func purchase(_ product: Product) { - print("Attempting purchase: \(product.displayName)") - - Task { - await AppStorePurchaseFlow.purchaseSubscription(with: product.id, emailAccessToken: nil) - } - } -} - -@available(macOS 12.0, *) -public struct SubscriptionRowModel: Identifiable { - public var id: String { product.id + String(isPurchased) + String(isBeingPurchased) } - - public let product: Product - public let isPurchased: Bool - public let isBeingPurchased: Bool -} diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift deleted file mode 100644 index 07250f3c00..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseView.swift +++ /dev/null @@ -1,193 +0,0 @@ -// -// DebugPurchaseView.swift -// -// 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 StoreKit - -@available(macOS 12.0, *) -public struct DebugPurchaseView: View { - - @ObservedObject var model: DebugPurchaseModel - public let dismissAction: () -> Void - - public var body: some View { - VStack { - if model.subscriptions.isEmpty { - loadingProductsView - } else { - purchaseSubscriptionSection - } - - Divider() - - HStack { - Spacer() - Button("Close") { - dismissAction() - } - } - } - .padding(20) - } - - private var loadingProductsView: some View { - VStack { - Text("Loading subscriptions...") - .font(.largeTitle) - ActivityIndicator(isAnimating: .constant(true), style: .spinning) - } - .padding(.all, 32) - } - - private var purchaseSubscriptionSection: some View { - VStack { - Text("Purchase Subscription") - .font(.largeTitle) - Spacer(minLength: 16) - VStack { - ForEach(model.subscriptions, id: \.id) { rowModel in - SubscriptionRow(product: rowModel.product, - isPurchased: rowModel.isPurchased, - isBeingPurchased: rowModel.isBeingPurchased, - buyButtonAction: { model.purchase(rowModel.product) }) - Divider() - } - .padding(10) - } - .roundedBorder() - Spacer(minLength: 16) - } - } -} - -struct ActivityIndicator: NSViewRepresentable { - - @Binding var isAnimating: Bool - - let style: NSProgressIndicator.Style - - func makeNSView(context: NSViewRepresentableContext) -> NSProgressIndicator { - let progressIndicator = NSProgressIndicator() - progressIndicator.style = self.style - progressIndicator.controlSize = .small - return progressIndicator - } - - func updateNSView(_ nsView: NSProgressIndicator, context: NSViewRepresentableContext) { - if isAnimating { - nsView.startAnimation(nil) - } else { - nsView.stopAnimation(nil) - } - } -} - -@available(macOS 12.0, *) -struct SubscriptionRow: View { - - var product: Product - @State var isPurchased: Bool = false - @State var isBeingPurchased: Bool = false - - var buyButtonAction: () -> Void - - var body: some View { - HStack(alignment: .center) { - VStack(alignment: .leading) { - Text(product.displayName) - .font(.title) - Text(product.description) - .font(.body) - Text("Price: \(product.displayPrice)") - .font(.caption) - } - - Spacer() - - Button { - buyButtonAction() - } label: { - if isPurchased { - Text(Image(systemName: "checkmark")) - .bold() - .foregroundColor(.white) - } else if isBeingPurchased { - ActivityIndicator(isAnimating: .constant(true), style: .spinning) - } else { - Text("Buy") - .bold() - .foregroundColor(.white) - } - - } - .buttonStyle(BuyButtonStyle(isPurchased: isPurchased)) - - } - .disabled(isPurchased) - } -} - -struct CapsuleButton: ButtonStyle { - - @ViewBuilder - func makeBody(configuration: Configuration) -> some View { - let background = configuration.isPressed ? Color(white: 0.25) : Color(white: 0.5) - - configuration.label - .padding(12) - .background(background) - .foregroundColor(.white) - .clipShape(Capsule()) - } -} - -@available(macOS 12.0, *) -extension Product { - - var isSubscription: Bool { - type == .nonRenewable || type == .autoRenewable - } -} - -extension String: Identifiable { - public typealias ID = Int - public var id: Int { - return hash - } -} - -@available(macOS 12.0, *) -struct BuyButtonStyle: ButtonStyle { - let isPurchased: Bool - - init(isPurchased: Bool = false) { - self.isPurchased = isPurchased - } - - func makeBody(configuration: Self.Configuration) -> some View { - var bgColor: Color = isPurchased ? Color.green : Color.blue - bgColor = configuration.isPressed ? bgColor.opacity(0.7) : bgColor.opacity(1) - - return configuration.label - .frame(width: 50) - .padding(10) - .background(bgColor) - .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) - .scaleEffect(configuration.isPressed ? 0.9 : 1.0) - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift deleted file mode 100644 index e34296b062..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/DebugPurchaseViewController.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// DebugPurchaseViewController.swift -// -// 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 AppKit -import SwiftUI -import Combine -import StoreKit -import Purchase - -@available(macOS 12.0, *) -public final class DebugPurchaseViewController: NSViewController { - - private let manager: PurchaseManager - private let model: DebugPurchaseModel - - private var cancellables = Set() - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public init() { - manager = PurchaseManager.shared - model = DebugPurchaseModel(manager: manager) - - super.init(nibName: nil, bundle: nil) - } - - public override func loadView() { - - let purchaseView = DebugPurchaseView(model: model, dismissAction: { [weak self] in - guard let self = self else { return } - self.presentingViewController?.dismiss(self) - }) - - let hostingView = NSHostingView(rootView: purchaseView) - - view = NSView(frame: NSRect(x: 0, y: 0, width: 480, height: 500)) - hostingView.frame = view.bounds - hostingView.autoresizingMask = [.height, .width] - hostingView.translatesAutoresizingMaskIntoConstraints = true - - view.addSubview(hostingView) - } - - public override func viewDidLoad() { - Task { - await manager.updatePurchasedProducts() - await manager.updateAvailableProducts() - } - - manager.$availableProducts.combineLatest(manager.$purchasedProductIDs, manager.$purchaseQueue).receive(on: RunLoop.main).sink { [weak self] availableProducts, purchasedProductIDs, purchaseQueue in - print(" -- got combineLatest -") - print(" -- got combineLatest - availableProducts: \(availableProducts.map { $0.id }.joined(separator: ","))") - print(" -- got combineLatest - purchasedProducts: \(purchasedProductIDs.joined(separator: ","))") - print(" -- got combineLatest - purchaseQueue: \(purchaseQueue.joined(separator: ","))") - - let sortedProducts = availableProducts.sorted(by: { $0.price > $1.price }) - - self?.model.subscriptions = sortedProducts.map { SubscriptionRowModel(product: $0, - isPurchased: purchasedProductIDs.contains($0.id), - isBeingPurchased: purchaseQueue.contains($0.id)) } - }.store(in: &cancellables) - } -} -*/ diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift deleted file mode 100644 index 5a3babbf5b..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/PurchaseInProgressViewController.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// PurchaseInProgressViewController.swift -// -// 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 AppKit -import SwiftUI - -public final class PurchaseInProgressViewController: NSViewController { - - private var purchaseInProgressView: PurchaseInProgressView? - private var viewModel: PurchaseInProgressViewModel - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public init(title: String) { - self.viewModel = PurchaseInProgressViewModel(title: title) - super.init(nibName: nil, bundle: nil) - } - - public override func loadView() { - - let purchaseInProgressView = PurchaseInProgressView(viewModel: viewModel) - let hostingView = NSHostingView(rootView: purchaseInProgressView) - - self.purchaseInProgressView = purchaseInProgressView - - view = NSView(frame: NSRect(x: 0, y: 0, width: 360, height: 160)) - hostingView.frame = view.bounds - hostingView.autoresizingMask = [.height, .width] - hostingView.translatesAutoresizingMaskIntoConstraints = true - - view.addSubview(hostingView) - } - - public func updateTitleText(_ text: String) { - self.viewModel.title = text - } -} - -final class PurchaseInProgressViewModel: ObservableObject { - @Published var title: String - - init(title: String) { - self.title = title - } -} - -struct PurchaseInProgressView: View { - - @ObservedObject var viewModel: PurchaseInProgressViewModel - - public var body: some View { - VStack { - Text(viewModel.title).font(.title) - Spacer().frame(height: 32) - ActivityIndicator(isAnimating: .constant(true), style: .spinning) - } - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift b/LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift deleted file mode 100644 index f54e106c5b..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/DebugMenu/SubscriptionDebugMenu.swift +++ /dev/null @@ -1,211 +0,0 @@ -// -// SubscriptionDebugMenu.swift -// -// 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 AppKit -import Account -import Purchase - -public final class SubscriptionDebugMenu: NSMenuItem { - - var currentViewController: () -> NSViewController? - private let accountManager = AccountManager() - - private var _purchaseManager: Any? - @available(macOS 12.0, *) - fileprivate var purchaseManager: PurchaseManager { - if _purchaseManager == nil { - _purchaseManager = PurchaseManager() - } - // swiftlint:disable:next force_cast - return _purchaseManager as! PurchaseManager - } - - required init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public init(currentViewController: @escaping () -> NSViewController?) { - self.currentViewController = currentViewController - super.init(title: "Subscription", action: nil, keyEquivalent: "") - self.submenu = submenuItem - } - - private lazy var submenuItem: NSMenu = { - let menu = NSMenu(title: "") - - menu.addItem(NSMenuItem(title: "Simulate Subscription Active State (fake token)", action: #selector(simulateSubscriptionActiveState), target: self)) - menu.addItem(NSMenuItem(title: "Clear Subscription Authorization Data", action: #selector(signOut), target: self)) - menu.addItem(NSMenuItem(title: "Show account details", action: #selector(showAccountDetails), target: self)) - menu.addItem(.separator()) - menu.addItem(NSMenuItem(title: "Validate Token", action: #selector(validateToken), target: self)) - menu.addItem(NSMenuItem(title: "Check Entitlements", action: #selector(checkEntitlements), target: self)) - menu.addItem(NSMenuItem(title: "Get Subscription Info", action: #selector(getSubscriptionInfo), target: self)) - if #available(macOS 12.0, *) { - menu.addItem(NSMenuItem(title: "Check Purchase Products Availability", action: #selector(checkProductsAvailability), target: self)) - } - menu.addItem(NSMenuItem(title: "Restore Subscription from App Store transaction", action: #selector(restorePurchases), target: self)) - menu.addItem(.separator()) - if #available(macOS 12.0, *) { - menu.addItem(NSMenuItem(title: "Sync App Store AppleID Account (re- sign-in)", action: #selector(syncAppleIDAccount), target: self)) - menu.addItem(NSMenuItem(title: "Purchase Subscription from App Store", action: #selector(showPurchaseView), target: self)) - } - menu.addItem(.separator()) - menu.addItem(NSMenuItem(title: "Error message #1", action: #selector(testError1), target: self)) - menu.addItem(NSMenuItem(title: "Error message #2", action: #selector(testError2), target: self)) - return menu - }() - - @objc - func simulateSubscriptionActiveState() { - accountManager.storeAccount(token: "fake-token", email: "fake@email.com", externalID: "123") - } - - @objc - func signOut() { - accountManager.signOut() - } - - @objc - func showAccountDetails() { - let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" - let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", - "AccessToken: \(accountManager.accessToken ?? "")", - "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil - showAlert(title: title, message: message) - } - - @objc - func validateToken() { - Task { - guard let token = accountManager.accessToken else { return } - switch await AuthService.validateToken(accessToken: token) { - case .success(let response): - showAlert(title: "Validate token", message: "\(response)") - case .failure(let error): - showAlert(title: "Validate token", message: "\(error)") - } - } - } - - @objc - func checkEntitlements() { - Task { - var results: [String] = [] - - for entitlementName in ["fake", "dummy1", "dummy2", "dummy3"] { - let result = await AccountManager().hasEntitlement(for: entitlementName) - let resultSummary = "Entitlement check for \(entitlementName): \(result)" - results.append(resultSummary) - print(resultSummary) - } - - showAlert(title: "Check Entitlements", message: results.joined(separator: "\n")) - } - } - - @objc - func getSubscriptionInfo() { - Task { - guard let token = accountManager.accessToken else { return } - switch await SubscriptionService.getSubscriptionInfo(token: token) { - case .success(let response): - showAlert(title: "Subscription info", message: "\(response)") - case .failure(let error): - showAlert(title: "Subscription info", message: "\(error)") - } - } - } - - @available(macOS 12.0, *) - @objc - func syncAppleIDAccount() { - Task { - await purchaseManager.syncAppleIDAccount() - } - } - - @available(macOS 12.0, *) - @objc - func checkProductsAvailability() { - Task { - - let result = await purchaseManager.hasProductsAvailable() - showAlert(title: "Check App Store Product Availability", - message: "Can purchase: \(result ? "YES" : "NO")") - } - } - - @objc - func restorePurchases(_ sender: Any?) { - if #available(macOS 12.0, *) { - Task { - await AppStoreRestoreFlow.restoreAccountFromPastPurchase() - } - } - } - - @objc - func testError1(_ sender: Any?) { - Task { @MainActor in - let alert = NSAlert.init() - alert.messageText = "Something Went Wrong" - alert.informativeText = "The App Store was not able to process your purchase. Please try again later." - alert.addButton(withTitle: "OK") - alert.runModal() - } - } - - @objc - func testError2(_ sender: Any?) { - Task { @MainActor in - let alert = NSAlert.init() - alert.messageText = "Subscription Not Found" - alert.informativeText = "The subscription associated with this Apple ID is no longer active." - alert.addButton(withTitle: "View Plans") - alert.addButton(withTitle: "Cancel") - alert.runModal() - } - } - - @IBAction func showPurchaseView(_ sender: Any?) { - if #available(macOS 12.0, *) { - currentViewController()?.presentAsSheet(DebugPurchaseViewController()) - } - } - - private func showAlert(title: String, message: String? = nil) { - Task { @MainActor in - let alert = NSAlert.init() - alert.messageText = title - if let message = message { - alert.informativeText = message - } - alert.addButton(withTitle: "OK") - alert.runModal() - } - } -} - -extension NSMenuItem { - - convenience init(title string: String, action selector: Selector?, target: AnyObject?, keyEquivalent charCode: String = "", representedObject: Any? = nil) { - self.init(title: string, action: selector, keyEquivalent: charCode) - self.target = target - self.representedObject = representedObject - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift b/LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift deleted file mode 100644 index 2151f80638..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Extensions/RoundedBorder.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// RoundedBorder.swift -// -// 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 - -extension View { - func roundedBorder() -> some View { - background(ZStack { - RoundedRectangle(cornerRadius: 8) - .stroke(Color("BlackWhite10"), lineWidth: 1) - RoundedRectangle(cornerRadius: 8) - .fill(Color("BlackWhite1")) - }) - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift deleted file mode 100644 index b94ea34da2..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionModel.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// PreferencesSubscriptionModel.swift -// -// 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 Account - -public final class PreferencesSubscriptionModel: ObservableObject { - - @Published var isUserAuthenticated: Bool = false - @Published var hasEntitlements: Bool = false - lazy var sheetModel: SubscriptionAccessModel = makeSubscriptionAccessModel() - - private let accountManager: AccountManager - private var actionHandler: PreferencesSubscriptionActionHandlers - private let sheetActionHandler: SubscriptionAccessActionHandlers - - public init(accountManager: AccountManager = AccountManager(), actionHandler: PreferencesSubscriptionActionHandlers, sheetActionHandler: SubscriptionAccessActionHandlers) { - self.accountManager = accountManager - self.actionHandler = actionHandler - self.sheetActionHandler = sheetActionHandler - - let isUserAuthenticated = accountManager.isUserAuthenticated - self.isUserAuthenticated = isUserAuthenticated - - NotificationCenter.default.addObserver(forName: .accountDidSignIn, object: nil, queue: .main) { _ in - self.updateUserAuthenticatedState(true) - } - - NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { _ in - self.updateUserAuthenticatedState(false) - } - } - - private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { - if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: sheetActionHandler, email: accountManager.email) - } else { - ActivateSubscriptionAccessModel(actionHandlers: sheetActionHandler) - } - } - - private func updateUserAuthenticatedState(_ isUserAuthenticated: Bool) { - self.isUserAuthenticated = isUserAuthenticated - sheetModel = makeSubscriptionAccessModel() - } - - @MainActor - func learnMoreAction() { - actionHandler.openURL(.purchaseSubscription) - } - - @MainActor - func changePlanOrBillingAction() { - actionHandler.manageSubscriptionInAppStore() - } - - @MainActor - func removeFromThisDeviceAction() { - accountManager.signOut() - } - - @MainActor - func openVPN() { - actionHandler.openVPN() - } - - @MainActor - func openPersonalInformationRemoval() { - actionHandler.openPersonalInformationRemoval() - } - - @MainActor - func openIdentityTheftRestoration() { - actionHandler.openIdentityTheftRestoration() - } - - @MainActor - func openFAQ() { - actionHandler.openURL(.subscriptionFAQ) - } - - @MainActor - func fetchEntitlements() { - print("Entitlements!") - Task { - self.hasEntitlements = await AccountManager().hasEntitlement(for: "dummy1") - } - } -} - -public final class PreferencesSubscriptionActionHandlers { - var openURL: (URL) -> Void - var manageSubscriptionInAppStore: () -> Void - var openVPN: () -> Void - var openPersonalInformationRemoval: () -> Void - var openIdentityTheftRestoration: () -> Void - - public init(openURL: @escaping (URL) -> Void, manageSubscriptionInAppStore: @escaping () -> Void, openVPN: @escaping () -> Void, openPersonalInformationRemoval: @escaping () -> Void, openIdentityTheftRestoration: @escaping () -> Void) { - self.openURL = openURL - self.manageSubscriptionInAppStore = manageSubscriptionInAppStore - self.openVPN = openVPN - self.openPersonalInformationRemoval = openPersonalInformationRemoval - self.openIdentityTheftRestoration = openIdentityTheftRestoration - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift b/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift deleted file mode 100644 index a770e7a642..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Preferences/PreferencesSubscriptionView.swift +++ /dev/null @@ -1,278 +0,0 @@ -// -// PreferencesSubscriptionView.swift -// -// 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 SwiftUIExtensions - -public struct PreferencesSubscriptionView: View { - @ObservedObject var model: PreferencesSubscriptionModel - @State private var showingSheet = false - @State private var showingRemoveConfirmationDialog = false - - public init(model: PreferencesSubscriptionModel) { - self.model = model - } - - public var body: some View { - VStack(alignment: .leading, spacing: 0) { - - TextMenuTitle(text: UserText.preferencesTitle) - .sheet(isPresented: $showingSheet) { - SubscriptionAccessView(model: model.sheetModel) - } - .sheet(isPresented: $showingRemoveConfirmationDialog) { - Dialog(spacing: 20) { - Image("Placeholder-96x64", bundle: .module) - Text(UserText.removeSubscriptionDialogTitle) - .font(.title2) - .bold() - .foregroundColor(Color("TextPrimary", bundle: .module)) - Text(UserText.removeSubscriptionDialogDescription) - .font(.body) - .multilineTextAlignment(.center) - .fixMultilineScrollableText() - .foregroundColor(Color("TextPrimary", bundle: .module)) - } buttons: { - Button(UserText.removeSubscriptionDialogCancel) { showingRemoveConfirmationDialog = false } - Button(action: { - showingRemoveConfirmationDialog = false - model.removeFromThisDeviceAction() - }, label: { - Text(UserText.removeSubscriptionDialogConfirm) - .foregroundColor(.red) - }) - } - .frame(width: 320) - } - - Spacer() - .frame(height: 20) - - VStack { - if model.isUserAuthenticated { - UniversalHeaderView { - Image("subscription-active-icon", bundle: .module) - .padding(4) - } content: { - TextMenuItemHeader(text: UserText.preferencesSubscriptionActiveHeader) - TextMenuItemCaption(text: UserText.preferencesSubscriptionActiveCaption) - } buttons: { - Button(UserText.addToAnotherDeviceButton) { showingSheet.toggle() } - - Menu { - Button(UserText.changePlanOrBillingButton, action: { model.changePlanOrBillingAction() }) - Button(UserText.removeFromThisDeviceButton, action: { - showingRemoveConfirmationDialog.toggle() - }) - } label: { - Text(UserText.manageSubscriptionButton) - } - .fixedSize() - } - .onAppear { - model.fetchEntitlements() - } - - } else { - UniversalHeaderView { - Image("subscription-inactive-icon", bundle: .module) - .padding(4) - .background(Color.black.opacity(0.06)) - .cornerRadius(4) - } content: { - TextMenuItemHeader(text: UserText.preferencesSubscriptionInactiveHeader) - TextMenuItemCaption(text: UserText.preferencesSubscriptionInactiveCaption) - } buttons: { - Button(UserText.learnMoreButton) { model.learnMoreAction() } - .buttonStyle(DefaultActionButtonStyle(enabled: true)) - Button(UserText.haveSubscriptionButton) { showingSheet.toggle() } - } - } - - Divider() - .foregroundColor(Color.secondary) - .padding(.horizontal, -10) - - SectionView(iconName: "vpn-service-icon", - title: UserText.vpnServiceTitle, - description: UserText.vpnServiceDescription, - buttonName: model.isUserAuthenticated ? "Manage" : nil, - buttonAction: { model.openVPN() }, - enabled: model.hasEntitlements) - - Divider() - .foregroundColor(Color.secondary) - - SectionView(iconName: "pir-service-icon", - title: UserText.personalInformationRemovalServiceTitle, - description: UserText.personalInformationRemovalServiceDescription, - buttonName: model.isUserAuthenticated ? "View" : nil, - buttonAction: { model.openPersonalInformationRemoval() }, - enabled: model.hasEntitlements) - - Divider() - .foregroundColor(Color.secondary) - - SectionView(iconName: "itr-service-icon", - title: UserText.identityTheftRestorationServiceTitle, - description: UserText.identityTheftRestorationServiceDescription, - buttonName: model.isUserAuthenticated ? "View" : nil, - buttonAction: { model.openIdentityTheftRestoration() }, - enabled: model.hasEntitlements) - } - .padding(10) - .roundedBorder() - - PreferencePaneSection { - TextMenuItemHeader(text: UserText.preferencesSubscriptionFooterTitle) - HStack(alignment: .top, spacing: 6) { - TextMenuItemCaption(text: UserText.preferencesSubscriptionFooterCaption) - Button(UserText.viewFaqsButton) { model.openFAQ() } - } - } - } - } -} - -struct UniversalHeaderView: View where Icon: View, Content: View, Buttons: View { - - @ViewBuilder let icon: () -> Icon - @ViewBuilder let content: () -> Content - @ViewBuilder let buttons: () -> Buttons - - init(@ViewBuilder icon: @escaping () -> Icon, @ViewBuilder content: @escaping () -> Content, @ViewBuilder buttons: @escaping () -> Buttons) { - self.icon = icon - self.content = content - self.buttons = buttons - } - - public var body: some View { - HStack(alignment: .top) { - icon() - VStack(alignment: .leading, spacing: 8) { - - content() - HStack { - buttons() - } - .padding(.top, 10) - } - Spacer() - } - .padding(.vertical, 10) - } -} - -public struct SectionView: View { - public var iconName: String - public var title: String - public var description: String - public var buttonName: String? - public var buttonAction: (() -> Void)? - public var enabled: Bool - - public init(iconName: String, title: String, description: String, buttonName: String? = nil, buttonAction: (() -> Void)? = nil, enabled: Bool = true) { - self.iconName = iconName - self.title = title - self.description = description - self.buttonName = buttonName - self.buttonAction = buttonAction - self.enabled = enabled - } - - public var body: some View { - VStack(alignment: .center) { - VStack { - HStack(alignment: .center, spacing: 8) { - Image(iconName, bundle: .module) - .padding(4) - .background(Color("BadgeBackground", bundle: .module)) - .cornerRadius(4) - - VStack(alignment: .leading) { - Text(title) - .frame(maxWidth: .infinity, alignment: .leading) - .fixMultilineScrollableText() - .font(.body) - .foregroundColor(Color("TextPrimary", bundle: .module)) - Text(description) - .frame(maxWidth: .infinity, alignment: .leading) - .fixMultilineScrollableText() - .font(.system(size: 11, weight: .regular, design: .default)) - .foregroundColor(Color("TextSecondary", bundle: .module)) - } - - if let name = buttonName, !name.isEmpty, let action = buttonAction { - Button(name) { action() } - } - } - } - } - .padding(.vertical, 7) - .disabled(!enabled) - .opacity(enabled ? 1.0 : 0.6) - } -} - -enum Const { - - static let pickerHorizontalOffset: CGFloat = { - if #available(macOS 12.0, *) { - return -8 - } else { - return 0 - } - }() - - enum Fonts { - static let popUpButton: NSFont = .preferredFont(forTextStyle: .title1, options: [:]) - static let sideBarItem: Font = .body - static let preferencePaneTitle: Font = .title2.weight(.semibold) - static let preferencePaneSectionHeader: Font = .title3.weight(.semibold) - static let preferencePaneDisclaimer: Font = .subheadline - } -} - -struct TextMenuTitle: View { - let text: String - - var body: some View { - Text(text) - .font(Const.Fonts.preferencePaneTitle) - } -} - -struct TextMenuItemHeader: View { - let text: String - - var body: some View { - Text(text) - .font(Const.Fonts.preferencePaneSectionHeader) - } -} - -struct TextMenuItemCaption: View { - let text: String - - var body: some View { - Text(text) - .frame(maxWidth: .infinity, alignment: .leading) - .fixMultilineScrollableText() - .foregroundColor(Color("GreyTextColor")) - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json deleted file mode 100644 index 5d30e17341..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/BadgeBackground.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.940", - "green" : "0.940", - "red" : "0.940" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.260", - "green" : "0.260", - "red" : "0.260" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json deleted file mode 100644 index 5a03d5f0a8..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextPrimary.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.840", - "blue" : "0", - "green" : "0", - "red" : "0" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.840", - "blue" : "255", - "green" : "255", - "red" : "255" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json deleted file mode 100644 index 809dad5e64..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Colors/TextSecondary.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.600", - "blue" : "0.000", - "green" : "0.000", - "red" : "0.000" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.600", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json deleted file mode 100644 index 1d8c180e47..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "apple-id-icon.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/apple-id-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/apple-id-icon.imageset/apple-id-icon.pdf deleted file mode 100644 index 40cb42fd90457f8dda988e8dac4309633f267090..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3923 zcmZvfTaOb*5QX38SIi3%JkZcfbEJ&GWNo^V#dKhqvz!tHWmePwb!dYWA)r&u@PI zeEgX<+Y2?D!ae&RH``B#&vOJkXW_Iz+-z3Y)7h)d-)a*Bfrc2mt?V`CP-YR=y>G1KDfyDCJ(XH&615gr zvh<3zxSV<|oG7j}->W^v+JnBu-`-0$YeFDmwGJ-&6sxFjy`--1gUg}URu#)ZYPYze z&(#|iQ%t@ctskIQx~GJ$D>Pn@-H0OQ#F=1Zy9jTg4JmAG?yk1reH!7g3QqxZEU}Lm zysrj@*d!%2m5?$eZm!{Cqjs9dp4dkKQz>B-q19Si0J2l<`~|8mOKYhN(*-Q9hY+*X zueX@UW^pNs1WY-Vf(C`W;L_3IQI+g+>pqZ5ElG?xoL#7qiYnAK{swew8lXs3G9_12 z=|Y|Yu2YW!l&e_6f-5!msE~zar+y1s;ybS3Uve=^YF4-2D0&2vn3OUc(Rvkf5_yQh z8wUYX7wQvTEip%1SIK|~5LLohO>3dB1xSn27Sl}fpi3MbG1wAfjBSQW8if^mNdP?5 z+~ra1;(f*gsH?TG9Ea!w1*KD)4~ciG86lBaPhPw9rD7?kTog1?L((8(($A^s_1o*M^oAyyC}V-lzZ6yzt0nXYA}ThA0(ky zUui{6?^J+O(dAWOvAWktD;$HA!;aAD7ivWasTFi^jj)rHh@Y9n7H@@I#ssM@zGp4S zf>er~k!-VDFbHs5B|Y}&p%?K%VC*s}HzDIqBVs+T7X;M=$5_jgOQ^DTIYLq@?=`T_ z11&?)L+&&+%m^Ye)$plNpXee??$(vs7L1idAN8VhflUUaRg5)+dmx4Nl>uSvMjg>0 zU%HC94i`IMJ&_k`JG@)uI6KNsCUo60aG)H0id3t^nKWpQ3b|z)bJ(=9LFt9Qtcw+G zDL^XrwB;?Nrj7p4iFjI938z96g{*u+%fzBmWv327(avJVi%=EO5DU$S5EoXz^WAY&h z#HHM&x3$M~9!l~^l%b>)8|xAiJXU5V@0gnpZyAgAgS*Go^qD-goNOvG6rdG!zgCcf zFbg@!W?@!!N|I3qg)$iq3Pq%q6BT0GX=hgE9k&B>J<(}YjFQktN*L}7I{mv=lMIim z%mQgOX-zca{yLF}j@>oI3!z*FG5_?`f^NmYL@|&-9yub_kac(<7M`7xLOCo4EHhqB zzX7ilq-CO=yNKi6b<0?~3l++r2c3`mG10~(!Mj8kv0FTHve2bm^+*$Ql+-~OqcdkJ zn1(ngcZKBX4U3UJS{yo)L%pccoV{lXV01>|lttOm{yL(nV|Yis#^rGbY0x>L=6ZXg zIdvGSDHAN)i~(!`YU}9ZCcrgJ*Fkg@%uW*pCbdBw9;%#j%SsVZnuR+}ps@?<=rHkg zJ-c;olxlhlnd+*-{SSC`57cQx&(bl;#v{=#YC~t~b%P_w$#GKRUtmNUs8nczsR*Qm zvRq5X6_E>Hsr<1@w8+iK5Ng>37ZY(Q^F4(T|z&g%oMN(}xcO{mzHe zciZjmu%AEt#)lNYvtR%Db2Fd4TYcIrz)zct%hikB*ZITkygCgoJVqxQlTz!B7BVP~ zJDqR#yDvBE&3cXrYM_ooAI1AnCvOtKfn18HgXrf diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json deleted file mode 100644 index 118ae3c7ce..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "email-icon.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/email-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/email-icon.imageset/email-icon.pdf deleted file mode 100644 index fa392436929480cbee4d0191adad490061a9da6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1828 zcmZvd+iu%142JLZ6uc?W4yYqqXP_vs9x-ggx^#ENE<8tVGbHvFyF<69ALZCo(pCr% z<+JpY{3SC7Hy2mOSU4dNB(y(%3V@Rnm|Z^i*SCGu$L%j1zUm5O;kBdzr>EPGh|MBx z6Xo9iPqBIEA3+hE(?Zepn^>*I>>~bI)^UD)2IsfCzv@o>7PCu)hfZLTB2WxDO^$i? z`WZi}*MjG-CtckLRbXk7FqV_@nxd%?5O*Q(Mp;-Xeb`CG7v2pTb&kfmtIV?Oq|d6n zoJlPxqfwXPN}wLX`;eVLJ7t~&Fk)fSW5@}U9>cLPQqD25DLI+U>bKlmiAPZu%6nfr zAWaiOFdo(fEo!fPf;L#AB~XD@S!WX(9F)NMX0+=>rj$CZRaTXQ;<%ld@&+vxv5k`A zDT<8f6{lmM7QT?qdbD7R(g&k7tP)db4JKx^qO@HRtXEd=ky2p@d0+ijy~zeFw3SMi z5DKTflgR}-qS6E-iD@rOr1U7(aJ55$-fBmwPlV5jESMM@LWjv!q2O|TD1@RTc0=aUPy2v-R!R42$|3jpIcmMzZ diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json deleted file mode 100644 index 08e3f66b26..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "itr-service-icon.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/itr-service-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/itr-service-icon.imageset/itr-service-icon.pdf deleted file mode 100644 index 8cdf1fed11c45441d74ad6702fe8acf45780f6a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4560 zcmcIo%WfM-5WLS<%*B9x;Bwv%zz`5ivJoUf6e>3(2WGXF8AxP8(t(n%clGSfW6c1e z4TvzTRjGTrySjR3hA(ce-Y#V48IxS_-S2-e&R@Ug%j>7(yW8XXIE=sf^rK&M(FG&$ z;TIRMJozlI!`YVZY)f^vr9Im+o^5H)w$x`^%5y9)E{Nq}dq-_yYSy10hwc6GBi8^| zWOX?1hxH>{UJZY5`eF6%4Zpl?f9ntIFSfjv{QkgXM;$~DMSfV6r_;Yl)ded=47`72 z#qRXs&~I7n9M*-Y>fk~l9+`xxG+N3E0@CA>s*}zrX?TSo#^lR;a-Hl%1|Mdi*476{ z&QpkzR>`CwJ+hO*hG1&lA(w_g<5CS$(kNI-?SU1nR8UqgwU-WrR^#7A0ZI<@JWIoWG7EOd;GHe$2 z0#D;qNs|YyVkk$*WnXRUOe1=ZQ|7#8gf%OQ>ZWn4E9xwlCU(K#UGpp`GO867 zw5hztY1t^PYbVG&Z@^{fS_lW{8Ji4A%~>xk$Lty>qlmQ4=uUeT zlp@qy6NKg@-sqzVbIb`VZ0il7%3|rVH-su7fYH|FNDXGY7`)Kff2udgiAEYlyBD^G zC<2}_ct%KNB4w0wZ?R~DLlv;A-nB}^hPM?w4Rf+ZMK0T-@{D9Dm1WpC?2%C%6Q5ynvl+t2pqEkXEZo8mdK$;Dbvb8`aKhpkG z77h&FDP!T63q{DWP+Q7`)mSEFk?jg0^QrOA+mNKuAhn-5Vlh9Mnxt@LGOrHvGs8fv z$NV(jiP?bpNeah&kawa;Sw#}1p-X`hlM)=I*#zOZVi4UEgh7a+am9K`$v%46fnIG9 zxIlD9jloKcTM{?<0t3MYqmz=s7?ATQIVvz^GsPJNW+1gRm{Mpli7sI@!e7ucADHm< z{Ry4%PC}|@oCicFK{Xf@Bv&D6UT}6XN*kyXq7NQ#5IU*>5_D+mG~RtE7m!pD$pdoM zIPbwm#AfY+2lrhxn3jNpm4?zWRtXX5s3ly~)N%OfD98^RiFN7SiXFn?$$&&`12Rz8 za59L>IWP}qPcxniI(|K2=!_7E@6KVYQ{G_2(L|ifp@OJDneIRZw8L>K#B0S$ns$ZR zi#{O#(0PqFBq>953WCMs0+o1)SGfa9D)WNMre$%L@gORJ*3pyzG{IO$QihOa#9t|X z5Xc4mp?kuN(n2lJFfKz7y+`}#9R~~|Q`EiF?5p1B(l_1c^7W5ydgJw6Tjk5G5cAXA z+P~XwcgF*N|J!&sU;g^fp95dstnY^j_-VM?tl#XO_^|-L z;lSy8g4ML#xqwI1#+O Z-TXTs`*i1D?~kb*qjg{}UR?kD_A{{#h&})S diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json deleted file mode 100644 index 45cd94e1bb..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "pir-service-icon.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/pir-service-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/pir-service-icon.imageset/pir-service-icon.pdf deleted file mode 100644 index d52adbe6c3c4b8b3d038c8ef8e77add3075923e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4271 zcmc(j%Who95r+5YDS9Ko4!~yLFMuH+mSiJHf+$pWMi!INSY|-ZB%E=e`04XipEGq1 z&5brj2tgYD(_Q!at6Hz#zy0pU+d56&ICJ{PKPO|p`NrJ7`+WKS!)13l%)h1a&wgi| zZOQRtUcct#i=W$f;fqhkFFxtNtlK!B@-~<1Tr`SAE7!4XKPi@Nia`XZB(+HODYBoU?J0;*#llse zN>XH_PcgMhC8@KjPm%rf)9SUiVVG~kYwv4G-YC|rAK#k5sTy-PifpV{G6wOY(gvMGn= zlVCOU>a)2iHu&nuCAUFqE2#;ZT#A(pgT*FA3sxK-gJ8w?l7nD1R~OTKJ3Aj8x(m7; zO66qqtpsmwv|zzTtzeI0dwOpqbPGNJrs!ke-gY*mR^2dhQQ|CGr8>6OP{Tg(XJfX| zKxida!4~%fa)D3-X&g}7ypPWUhT2>uG8$KED(38gHVKF}ghFj^5=GE!?u13#N=r@= zDLHZ%8f4wgjh6W%MA1r)q)d`m`M#0)P~i-67j5#*jb_U&#yHF?sg}}7NR`~=iag17 zE~%JeTZk=iRjYk524@wLhU=k_xD5zj^Z-|Fq($pXDU}ioR<#DqETgj2I^xq(f#X&m zyiLUyYH9U#RPT!jJUnm4_?tFkr;>6E(s-W8whq^I>r&1X4OnT>JL2ysmBPd#VZKpg zAdLnTB&JdBgW)5o%V5Q&+T~QvJ&(3--8DXRXfR183BB{KOB;bzR|8!pB}Xhd)dF7* zwk3*)1gRmBM$W*ZVV)=*GtJvS2wjk#;nv)nSRsm#1kyc!g3m|#|gBC7!wSsC|D^p2+TzA9gXaa zU%oGagiwXtE+q;#6`NB@j55KeJZJ|{PS!4zuN#II&V2u_?TMU_LjDh*~vh5%8zX86t> z6hf$NT=gx=tY!A)lW`B3;K`uA2HR42Y0IH&FN?E`WN<}K{k@u7&el`!Lusl&AmAOW_Q^= zoIX9j`uXYb=b+r)olX}c4vzreez^bTfc4{%zI(b1cPQRI Ly?XWTFW>zS3|3Il diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json deleted file mode 100644 index 72547fd84b..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "subscription-active-icon.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/subscription-active-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/subscription-active-icon.imageset/subscription-active-icon.pdf deleted file mode 100644 index 823d59197d6e9e41b60651d9ba177c011c3d8b03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3346 zcmb7GO>g5i5WVwP@DgCRKqP*X0u}+f&Za2ZMN_A@q6e+4I4*1{mE|JsV*h&|DUp&k zZjK1m9mV5 zkMbzI2KjAdxo!{Tt~pB+x7?hXs^7KEFMmDOR6+0RiX8i{E)V20g?S;Q76q-Jst5cs zi(lL@>(sEeIXzC!ZhQK9fFIk(Gs0r?@$`va;#3BorH9jgvRZC7XV^yt&a`u4R2Uum zPX%U#ReDqyeNPHs=q9C?S@`vU%7pp^xh?y??wXJ+w>DJ8AzJ7bmD9lV=k;lM>&A&Fnsqd(x1fUmIZ+-P9Q`v}FYi5gxl$YE(GQ z(4%~WZRV782)EAAg@rNnKwQR@l1czm)=JGO5G`DxfuMgnCn@xd01ZX&F-&nwLwE-a z(}M?WkB|#vrSKQSS)o)xWyaC)&?4anNS&bq?MwNF>JcTm0hh>}#@*V0X{-D$i$p0< z`x)n&DF(zy#-!I1+!k6w4~qi^QWLN}@yQ4>!MtrN1crx)6ef=Vq@2?%IB2AwL^3m0 z3Bv-L1PQWI;<$`)Z^}3&0+qvQbjVy&6jpgXP~xaQ5{y|53Z5Zs!g0Dsq`9qc;%X3jT%AS$EQhPlZ>S%K|FpomQ2O02_OYv)smauXMm!UVyaq%om0>C9@PfpX%W%q5NF zR8mNY&%e>VgeBqmQX-h;)tUE-9|^;Ig=@jm_(0*zUbNpfP21ze5MP;bAZ_bm8s#7B z&8~dkE*>z+x9Px>6q(lbu|0KFeWd=06ranhw!x@{ML)$Ncw2VBD0)YLX0y&i!x{YR z>W5u;-1rl$X|%IyPtfI~yOaM%ATuVnV7e{0a4Z_c$Q~#0s_e^syIrb2b@e@Av>-!P z`M=a?jUfnl{5YdQqD_Rf?{of}LB9JeAkNWhmXNR-el2z(fxo5~zI!frN#Cm?)OY1> zUw6cZ?fYxIY~CE(L;aS%*=^nqq0858+fyHwa1Ht0?(Z6BM97YP+4bSjk~u>zEoo>e zNQ}>PcU7Hps(K!sy?p&teVrzC;=GG;c2VA6x`HXW_Kl-_EO>>Ez}7&((T9fAhkfzWw~K^=|rmI(en; zayO~B2*i-Pho{GA3FehuwNJ6`xwDH9E~cm0DVJH-N2aZ^6J;ddupn zPqEgJoE9%lN-jXX*Ix2~YUdQUo|@0O1*r>IJ2`i+8hVoDv=mbS%F=u6BWfm6XRC_W z7FsJ#ldm;tW{db*2t$6!Cru@hf%;fNjA~ej(o3`5>U%Dc{6WP^VP){8hFB>COkxh? z6~Uq^Z9Euj9E(p$VziV^lp?)>p=*oLT@Nh6@xY3$yGGC6FbG!m z5w;-EuvlXe+bg1xa;h((gjN?MsUbpukPrJPem!heGdWt!3=^DXR!g1wMjnCk468+Q zwLC0e62G1jUDgJ}OI3x6drbwC9!f`KUo|(BTNr}SHTKUz=+6U@u{i4-WM|``J2m6Q z>VShq7FZrTMM~%cY!ruW#fRQC0Yx8M?9CdoW)&U2!fKr-{NT0Y{DX0%!z8&?t>``c zCAkmDLW*G+7HT`WttSqSDrtCINSxPZXQD1p##{-gB=W^uR%)Wx*@`t|%FB7Y#dD7K zRfHVwF+U;9HyVCDeLz!`A)uY8Qngp+z&+?2{YbxFTbGugtt-5@MRZuBJrus_EzJQ3 zL6FdQJ&=NM^3vcDGZpg?NzfZ*{A zC4<14IxwEPnCX*`sn|#W%OMw<^3dK0Kw^vpQmiy6yk;3{f|XKjv_x7pK;l@Ju%i*n zo=PV(Pc4pR%jpOTCs=;OX{C{w_^wUbuuNsDp#ZxXYpJQZVUQwZ(J^+-1^pr=Flr<9 zeQreG^##h0 znjqQOnz4X&j){ja2*$O5wZ_Oq<7{dhWLFETFax$9TN8s7cFi=js3k`O+eBHM#iV9D z6q3|~0qGTMuFeI~t-Rq=<0DpFWy^<|BZKQp!cB(v-%Xq8*U7H-!@YfX$=iLtd?YBw zINtr~2-~;A_nXajzjNv`wwYvufK-060+pEpFbPmnwCY;_Cf7|8?T6XdQ2gYG|ce!RLM zRc95PALE>EKeAW9%6|v)*-6ao&rY%ecgKMDg`ckWtLyFM!>ylg=l7GiIxV#h3QEo~ zJK)2Q5i_3L<_J~twBHfpW~82nkoo)zC}8O?pvV}ygZ8(ptLyo8GWC9V2_27j+nf1` rdwg~AWFh6`e7oH{D|irm^7iV#ne4}x-RgEf%Aq;a^yty6Uta$Y0Y4zk diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json deleted file mode 100644 index 8933667242..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "sync-icon.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/sync-icon.pdf b/LocalPackages/Subscription/Sources/Subscription/Resources/Subscription.xcassets/Icons/sync-icon.imageset/sync-icon.pdf deleted file mode 100644 index 17fe5ef4551ab2eec45d5af6d009b88bd011e867..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2952 zcmcJR%WfM-5JmU%6}>TFFVyUJbpwWgSbiW#f+%u!MivY;mK8{3BGQ4Aug|F&&J1N` zkxjUenwQn}yj9iDE?>Vr^H#df8)tUE{?-}u@}*h5`Eq#o{;)pu^pDOH@tJx;cPxV{Y`9hKBR0m_uVY^^swJ-yJAIJY{hVhlEj(&uG-+G zxL^chzP4(b6&Ivtm1@Cp7j4Knhh&n?*$0;t zYQDJAOtdjo$@tl!FG|)3XYfuz7g`Nr0ll+>n98|QTa0Fbf|Y=lLEkJFP+YUf;PR*~ zSl0%-i`5yzl{~pJgSe8>?YuNIgT73)SWKKiiz_oIHjMtE?*z=pIA#+97f1hQS7vDN z@0gVt^l8e9*h$+7bhOIV8B8{|P*O0sLOmqp5~y17NgAmNwn{3nq1fUCRI`^$^VPu$ zN}IfB)gYCxo^!P>desMcvNY<*p}LS8Dsqmo=*3n_Es={!Bdm}{Y{@Z|LOBD>%4$&> zL=X~<;+)GsCa1bLu(?4nTum^oKuH=RDE~6zC8QR!wn&1l^cij7GAi{Rr!t7=EZ#E{ z@Uu92L=UcHde!FE5+qlZ#bpg}v=tvUho`(6SF`8uffOUebc-oi7t6f&{r+H={|KI zyKVQ=FfhXZIxrd>b|lW6QPK=Dx7JqP&H~_eXT*k3b&)~8A-uwV2qK9 zV%<22sXzlEB`|>g#1*f_*BJzwa&8OVXhLhP&koL`EC?rQJ^fOEG8Y7y=5Y+w}DzaQWJgjf;`iD-d_w8%a@qE9#?_Zeb qx7RPmq^z!XyMxhyhXk+Q-~Qd>{qUk)KOBZ|Sii1&_Uz4%Z~pAwo?p(|+v#uZr|o^$-cMf++nxMuVz5!=KA7xsQL@Em7eiFW z_OS#Vi*kJmAt$d)oCC=7%K~jXc3AHX_7>EHKAGrzX!f!KE7^GrPJWYOGhp%f2K31~ z?^afTtaq+Ff?V>YWZAk3%cN)}($Gg+tO-~%*6C2a21_F1z!sN{9=$q~*aH}SG|9+T z?W1)WsDx+=)_k9Ai7BblTVsk1veox7dTX43(PvF`KqeI(fE*s+P9Uvq)9e&~ve&&g#9_oYXtY{Z>fQt> z9&<<~I?1{itiZJ)g&3V=ot+E0W{qcfk48XhqOH0UgM{q~vh+#k08x^k3!2)Az36fc zA*4(j$&WT=-bW(5$wtbJX`?L`YgmpN384~yEkgkk_D*w~E*r#NJ6rx#+Jx;SDq?K2h2CoYxF@13s&Q<6gwUmLQ z+9AvM1ZL3c%1Ir3sax(dy$gACWqlxz9GF!n^E+XM6daub!X#!VTsu!rvQLM^n2)&! zwk4H%sLYU6dQAzL(nR-&Etco31MD?q+YwhVg)smd`MDD;(;beWGa^c*89nOhvw{lR zODEN+a8hg}hZtgQ?NAhDiin10l`x{ICS)3EkJLN#hsw~E+A&e=OIT+nC=&auE0U_l zW|c?AA~7w@ye8R#UUXi~3nA{cb6tcuP=Sw)@{K#;_638Ja#xCX6}>2|$?gBkl>B@yfNRdALwP4rya79?17MY8rHX%-c6ebL6lGE(a35E(u? zCZRqeu8hzsPW102_B%3SCC3AmWsK^>IZ8&>Nl6=q*!2-`{Fdrk@yWILATFRJad60F8pknKwU(Zev`=%8T7CGY+jT$J ztBC#oSCKT+@$Xj=#BZYR%+7C+xS$+Dv?f$RM(Zk~hGDbOv#gY}q6QLI+goSOUgX4`LIY|+E?|pX?9D5u6eF9b zTvtpiu{6%A28g1|qZWoKcSG66)kSV&47TN7Afh+zSPI*qT3AjndSJEA%{vxr4O-mR zy>l=vNQ%&cS<-ogw$=~{mUZyAA>uc+^DuL)I223KnKbGiS-UUL%v0c2%~`m8KTf`{gi z@2)BF%gu3fx4(J3_4C85k?LGSv2g!-_A0EhN=>F!cN|N4HPJsEp% zPlBuZNK_TyXJ=-sNK#wDHMUGsM0qYV* zXyhdR2?2w^RUB|R5Lm(`p`P#_#X^2k1o@Oj`4I~k0CPxjtynX32I z+iFw&{_R`WsuP`_0$<;{p|PV4gT;1Zq!IEfQ1yN=-T;ef&2aXOb$+m1H=51{FN2|l z^_+tBX?f^?P(Wvp8cD3_>!ipB$nuTFY}WxBC@Sv$K$Z2lWHUlXbJ~nB#BBsK=9<3m zENlo(KR|**lEsiyHi<#@vgKie+OfXJ%#nt{qt^HlvXRhrN0uQf2H8FD-kgU}+)gjj)SdO5> zs!#LbKWtq+BaEv2`p*4+>3+X-<$~1$>-(k4E93vTbhRSZv+RaydBjl^pW5Ey$~DW? zrlMJ3_p$cOH-}E1PN{(63bD93cyK|0NK)H$xgxCCWVvEGIGs`fye@>Ws?p_Yur61m zJQBaju_i2f%m^hjiWxD~6rkG0Iov!$_PS~CcWOFx6&Vs;T+2>FydYG0vk0QD z!)f^h&!M z``MSKW=DLU+s-GS56>V5DaEk?V!$nTEiO9PE!Cdm~W&mPZz zUrzJ+vxoJgi>ml*+N0_{gQj=;>I?=w-o`rkn=;4d%sFmg$8Kybx5xDX8`vD{vFYvD zJC?x;I^6btXe}ELRAnQ|%uXi_YPO-pe+7mlP&s)e3aczg-_P^QtS=;GIVi969nsmx zo?1~c4(TasSUpM#iX2c-2rXbT9byz@&lD-L+md&6AXA0dg*+>LU6yTL7hTM4qlW@8 zpz1<Qx$3kw0X&x+xcgB~Bc9woD) zh+c;@2nMxlM$^a#oB&8SYg$yX4#oR0xPaOfSrCSR5i&ykV+xr)P6XRq&!fRPgZ_%; zH<))A${xq-`Q4`X)&-W&;u03!wsma76|?Gx+eZLJb(BokER`_#!Z2Fnc~V{7o=R4%Hf@-20;2O*o$po(>bliId6_Gr!$I+Knunu%8TcS{mCncBJ z4>zA$knL2#8OoBe6_rpn8e1o$Dm!Z_W|e5hup4jiA;X8y1s!_Gp>_c$jSa0sb_aYd z@($g)8yhzcJ^V8^V91;<&WSAY)En?~UI}-=c!zCLo<7L<&~^j9`-vK~jL3NFO@~k? zdj{+hWOurlI^;aE-x?Pi$s0^3BkV&vK*%C%v|xnR#|>dHV=c05`pUsS1ajz@kNc(Hmn z3-H_g=5lr0FY=U>Kkl;%TYgb^wA~!$4K%WJTEG|l;owl?)QwP4U=d0j_^ZV)mv3Nd zO$O)nV)3KxC%kJ;?BAzf29{C{A7~3nB)C_FnfRmCVYS}AJAU=^Zhl+gV@j2-BL2Vb zy?4F_ERVtE#ivG*6(1M)@rO?VIg}vB& String { - switch channel { - case .appleID: - return UserText.activateModalAppleIDDescription - case .email: - return UserText.activateModalEmailDescription - case .sync: - return UserText.activateModalSyncDescription - } - } - - public func buttonTitle(for channel: AccessChannel) -> String? { - switch channel { - case .appleID: - return UserText.restorePurchasesButton - case .email: - return UserText.enterEmailButton - case .sync: - return UserText.goToSyncSettingsButton - } - } - - public func handleAction(for channel: AccessChannel) { - switch channel { - case .appleID: - actionHandlers.restorePurchases() - case .email: - actionHandlers.openURLHandler(.activateSubscriptionViaEmail) - case .sync: - actionHandlers.goToSyncPreferences() - } - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift deleted file mode 100644 index 8ece29fe02..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/ShareSubscriptionAccessModel.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// ShareSubscriptionAccessModel.swift -// -// 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 - -public final class ShareSubscriptionAccessModel: SubscriptionAccessModel { - public var title = UserText.shareModalTitle - public var description = UserText.shareModalDescription - - private var actionHandlers: SubscriptionAccessActionHandlers - - private var email: String? - private var hasEmail: Bool { !(email?.isEmpty ?? true) } - - public init(actionHandlers: SubscriptionAccessActionHandlers, email: String?) { - self.actionHandlers = actionHandlers - self.email = email - } - - public func descriptionHeader(for channel: AccessChannel) -> String? { - hasEmail && channel == .email ? email : nil - } - - public func description(for channel: AccessChannel) -> String { - switch channel { - case .appleID: - return UserText.shareModalAppleIDDescription - case .email: - return hasEmail ? UserText.shareModalNoEmailDescription : UserText.shareModalHasEmailDescription - case .sync: - return UserText.shareModalSyncDescription - } - } - - public func buttonTitle(for channel: AccessChannel) -> String? { - switch channel { - case .appleID: - return nil - case .email: - return hasEmail ? UserText.manageEmailButton : UserText.enterEmailButton - case .sync: - return UserText.goToSyncSettingsButton - } - } - - public func handleAction(for channel: AccessChannel) { - switch channel { - case .appleID: - actionHandlers.restorePurchases() - case .email: - let url: URL = hasEmail ? .manageSubscriptionEmail : .addEmailToSubscription - - Task { - await AppStoreAccountManagementFlow.refreshAuthTokenIfNeeded() - - DispatchQueue.main.async { - self.actionHandlers.openURLHandler(url) - } - } - case .sync: - actionHandlers.goToSyncPreferences() - } - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift deleted file mode 100644 index aee70c96a9..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/Model/SubscriptionAccessModel.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// SubscriptionAccessModel.swift -// -// 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 - -public protocol SubscriptionAccessModel { - var items: [AccessChannel] { get } - - var title: String { get } - var description: String { get } - - func descriptionHeader(for channel: AccessChannel) -> String? - func description(for channel: AccessChannel) -> String - func buttonTitle(for channel: AccessChannel) -> String? - func handleAction(for channel: AccessChannel) -} - -extension SubscriptionAccessModel { - public var items: [AccessChannel] { AccessChannel.allCases } - - public func descriptionHeader(for channel: AccessChannel) -> String? { nil } -} - -public final class SubscriptionAccessActionHandlers { - var restorePurchases: () -> Void - var openURLHandler: (URL) -> Void - var goToSyncPreferences: () -> Void - - public init(restorePurchases: @escaping () -> Void, openURLHandler: @escaping (URL) -> Void, goToSyncPreferences: @escaping () -> Void) { - self.restorePurchases = restorePurchases - self.openURLHandler = openURLHandler - self.goToSyncPreferences = goToSyncPreferences - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift deleted file mode 100644 index 523fb91fcc..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessRow.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// SubscriptionAccessRow.swift -// -// 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 SwiftUIExtensions - -public struct SubscriptionAccessRow: View { - let iconName: String - let name: String - let descriptionHeader: String? - let description: String - let isExpanded: Bool - let buttonTitle: String? - let buttonAction: (() -> Void)? - - public init(iconName: String, name: String, descriptionHeader: String? = nil, description: String, isExpanded: Bool, buttonTitle: String? = nil, buttonAction: (() -> Void)? = nil) { - self.iconName = iconName - self.name = name - self.descriptionHeader = descriptionHeader - self.description = description - self.isExpanded = isExpanded - self.buttonTitle = buttonTitle - self.buttonAction = buttonAction - } - - public var body: some View { - VStack(alignment: .leading, spacing: 0) { - HStack(alignment: .center, spacing: 8) { - Image(iconName, bundle: .module) - - Text(name) - .font(.system(size: 14, weight: .regular, design: .default)) - - Spacer() - .contentShape(Rectangle()) - - Image(systemName: "chevron.down") - .rotationEffect(Angle(degrees: isExpanded ? -180 : 0)) - - } - .padding([.top, .bottom], 8) - .drawingGroup() - - if isExpanded { - VStack(alignment: .leading, spacing: 4) { - - if let header = descriptionHeader, !header.isEmpty { - Text(header) - .bold() - .foregroundColor(Color("TextPrimary", bundle: .module)) - } - - Text(description) - .font(.system(size: 13, weight: .regular, design: .default)) - .foregroundColor(Color("TextSecondary", bundle: .module)) - .fixMultilineScrollableText() - - if let title = buttonTitle, let action = buttonAction { - Spacer() - .frame(height: 8) - Button(title) { action() } - .buttonStyle(DefaultActionButtonStyle(enabled: true)) - } - - Spacer() - .frame(height: 4) - } - .transition(.asymmetric(insertion: .opacity.animation(.easeIn(duration: Constants.Animation.contentShowingDuration).delay(Constants.Animation.contentShowingDelay)), - removal: .opacity.animation(.easeOut(duration: Constants.Animation.contentHidingDuration)))) - } - } - .animation(.easeOut(duration: Constants.Animation.duration), value: isExpanded) - } -} - -private enum Constants { - - enum Animation { - static let duration: CGFloat = 0.3 - static let contentHidingDuration = duration * 0.6 - static let contentShowingDuration = duration - static let contentShowingDelay = duration * 0.3 - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift deleted file mode 100644 index e25354b7e1..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessView.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// SubscriptionAccessView.swift -// -// 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 SwiftUIExtensions - -public struct SubscriptionAccessView: View { - - @Environment(\.presentationMode) private var presentationMode: Binding - private let model: SubscriptionAccessModel - - private let dismissAction: (() -> Void)? - - @State private var selection: AccessChannel? = .appleID - @State var fullHeight: CGFloat = 0.0 - - public init(model: SubscriptionAccessModel, dismiss: (() -> Void)? = nil) { - self.model = model - self.dismissAction = dismiss - } - - public var body: some View { - VStack(spacing: 8) { - VStack(spacing: 8) { - Text(model.title) - .font(.title2) - .bold() - .foregroundColor(Color("TextPrimary", bundle: .module)) - Text(model.description) - .font(.body) - .multilineTextAlignment(.center) - .fixMultilineScrollableText() - .foregroundColor(Color("TextPrimary", bundle: .module)) - } - .padding(4) - - VStack(spacing: 0) { - ForEach(model.items) { item in - SubscriptionAccessRow(iconName: item.iconName, - name: item.title, - descriptionHeader: model.descriptionHeader(for: item), - description: model.description(for: item), - isExpanded: self.selection == item, - buttonTitle: model.buttonTitle(for: item), - buttonAction: { - dismiss { - model.handleAction(for: item) - } - }) - .contentShape(Rectangle()) - .onTapGesture { - self.selection = item - } - .padding(.vertical, 10) - - if model.items.last != item { - Divider() - } - } - .padding(.horizontal, 20) - - } - .roundedBorder() - .animation(.easeOut(duration: 0.3)) - - Spacer() - .frame(minHeight: 4, idealHeight: 60) - - Divider() - - Spacer() - .frame(height: 8) - - HStack { - Spacer() - - Button("Cancel") { - dismiss() - } - } - } - .padding(20) - .frame(width: 480) - } - - private func dismiss(completion: (() -> Void)? = nil) { - dismissAction?() - presentationMode.wrappedValue.dismiss() - - if let completion = completion { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { - completion() - } - } - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift b/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift deleted file mode 100644 index 4b96a8764e..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/SubscriptionAccessView/SubscriptionAccessViewController.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// SubscriptionAccessViewController.swift -// -// 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 AppKit -import Account -import SwiftUI - -public final class SubscriptionAccessViewController: NSViewController { - - private let accountManager: AccountManager - private var actionHandlers: SubscriptionAccessActionHandlers - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public init(accountManager: AccountManager = AccountManager(), actionHandlers: SubscriptionAccessActionHandlers) { - self.accountManager = accountManager - self.actionHandlers = actionHandlers - super.init(nibName: nil, bundle: nil) - } - - public override func loadView() { - let subscriptionAccessView = SubscriptionAccessView(model: makeSubscriptionAccessModel(), - dismiss: { [weak self] in - guard let self = self else { return } - self.presentingViewController?.dismiss(self) - }) - - let hostingView = NSHostingView(rootView: subscriptionAccessView) - let size = hostingView.fittingSize - - view = NSView(frame: NSRect(x: 0, y: 0, width: size.width, height: size.height)) - hostingView.frame = view.bounds - hostingView.autoresizingMask = [.height, .width] - hostingView.translatesAutoresizingMaskIntoConstraints = true - - view.addSubview(hostingView) - } - - private func makeSubscriptionAccessModel() -> SubscriptionAccessModel { - if accountManager.isUserAuthenticated { - ShareSubscriptionAccessModel(actionHandlers: actionHandlers, email: accountManager.email) - } else { - ActivateSubscriptionAccessModel(actionHandlers: actionHandlers) - } - } -} diff --git a/LocalPackages/Subscription/Sources/Subscription/UserText.swift b/LocalPackages/Subscription/Sources/Subscription/UserText.swift deleted file mode 100644 index dc53af7b28..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/UserText.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// UserText.swift -// -// 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 - -enum UserText { - // MARK: - Subscription preferences - - static let preferencesTitle = NSLocalizedString("subscription.preferences.title", value: "Privacy Pro", comment: "Title for the preferences pane for the subscription") - - static let vpnServiceTitle = NSLocalizedString("subscription.preferences.services.vpn.title", value: "VPN", comment: "Title for the VPN service listed in the subscription preferences pane") - static let vpnServiceDescription = NSLocalizedString("subscription.preferences.services.vpn.description", value: "Full-device protection with the VPN built for speed and security.", comment: "Description for the VPN service listed in the subscription preferences pane") - - static let personalInformationRemovalServiceTitle = NSLocalizedString("subscription.preferences.services.personal.information.removal.title", value: "Personal Information Removal", comment: "Title for the Personal Information Removal service listed in the subscription preferences pane") - static let personalInformationRemovalServiceDescription = NSLocalizedString("subscription.preferences.services.personal.information.removal.description", value: "Find and remove your personal information from sites that store and sell it.", comment: "Description for the Personal Information Removal service listed in the subscription preferences pane") - - static let identityTheftRestorationServiceTitle = NSLocalizedString("subscription.preferences.services.identity.theft.restoration.title", value: "Identity Theft Restoration", comment: "Title for the Identity Theft Restoration service listed in the subscription preferences pane") - static let identityTheftRestorationServiceDescription = NSLocalizedString("subscription.preferences.services.identity.theft.restoration.description", value: "Restore stolen accounts and financial losses in the event of identity theft.", comment: "Description for the Identity Theft Restoration service listed in the subscription preferences pane") - - // MARK: Preferences footer - static let preferencesSubscriptionFooterTitle = NSLocalizedString("subscription.preferences.subscription.footer.title", value: "Questions about Privacy Pro?", comment: "Title for the subscription preferences pane footer") - static let preferencesSubscriptionFooterCaption = NSLocalizedString("subscription.preferences.subscription.footer.caption", value: "Visit our Privacy Pro help pages for answers to frequently asked questions.", comment: "Caption for the subscription preferences pane footer") - static let viewFaqsButton = NSLocalizedString("subscription.preferences.view.faqs.button", value: "View FAQs", comment: "Button to open page for FAQs") - - // MARK: Preferences when subscription is active - static let preferencesSubscriptionActiveHeader = NSLocalizedString("subscription.preferences.subscription.active.header", value: "Privacy Pro is active on this device", comment: "Header for the subscription preferences pane when the subscription is active") - static let preferencesSubscriptionActiveCaption = NSLocalizedString("subscription.preferences.subscription.active.caption", value: "Your monthly Privacy Pro subscription renews on April 20, 2027.", comment: "Caption for the subscription preferences pane when the subscription is active") - - static let addToAnotherDeviceButton = NSLocalizedString("subscription.preferences.add.to.another.device.button", value: "Add to Another Device…", comment: "Button to add subscription to another device") - static let manageSubscriptionButton = NSLocalizedString("subscription.preferences.manage.subscription.button", value: "Manage Subscription", comment: "Button to manage subscription") - static let changePlanOrBillingButton = NSLocalizedString("subscription.preferences.change.plan.or.billing.button", value: "Change Plan or Billing...", comment: "Button to add subscription to another device") - static let removeFromThisDeviceButton = NSLocalizedString("subscription.preferences.remove.from.this.device.button", value: "Remove From This Device...", comment: "Button to remove subscription from this device") - - // MARK: Preferences when subscription is inactive - static let preferencesSubscriptionInactiveHeader = NSLocalizedString("subscription.preferences.subscription.inactive.header", value: "One subscription, three advanced protections", comment: "Header for the subscription preferences pane when the subscription is inactive") - static let preferencesSubscriptionInactiveCaption = NSLocalizedString("subscription.preferences.subscription.inactive.caption", value: "Get enhanced protection across all your devices and reduce your online footprint for as little as $9.99/mo.", comment: "Caption for the subscription preferences pane when the subscription is inactive") - - static let learnMoreButton = NSLocalizedString("subscription.preferences.learn.more.button", value: "Learn More", comment: "Button to open a page where user can learn more and purchase the subscription") - static let haveSubscriptionButton = NSLocalizedString("subscription.preferences.i.have.a.subscription.button", value: "I Have a Subscription", comment: "Button enabling user to activate a subscription user bought earlier or on another device") - - // MARK: - Remove from this device dialog - static let removeSubscriptionDialogTitle = NSLocalizedString("subscription.dialog.remove.title", value: "Remove From This Device?", comment: "Remove subscription from device dialog title") - static let removeSubscriptionDialogDescription = NSLocalizedString("subscription.dialog.remove.description", value: "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices.", comment: "Remove subscription from device dialog subtitle description") - static let removeSubscriptionDialogCancel = NSLocalizedString("subscription.dialog.remove.cancel.button", value: "Cancel", comment: "Button to cancel removing subscription from device") - static let removeSubscriptionDialogConfirm = NSLocalizedString("subscription.dialog.remove.confirm", value: "Remove Subscription", comment: "Button to confirm removing subscription from device") - - // MARK: - Services for accessing the subscription - static let appleID = NSLocalizedString("subscription.access.channel.appleid.name", value: "Apple ID", comment: "Service name displayed when accessing subscription using AppleID account") - static let email = NSLocalizedString("subscription.access.channel.email.name", value: "Email", comment: "Service name displayed when accessing subscription using email address") - static let sync = NSLocalizedString("subscription.access.channel.sync.name", value: "Sync", comment: "Service name displayed when accessing sync feature") - - // MARK: - Activate subscription modal - static let activateModalTitle = NSLocalizedString("subscription.activate.modal.title", value: "Activate your subscription on this device", comment: "Activate subscription modal view title") - static let activateModalDescription = NSLocalizedString("subscription.activate.modal.description", value: "Access your Privacy Pro subscription on this device via Sync, Apple ID or an email address.", comment: "Activate subscription modal view subtitle description") - - static let activateModalAppleIDDescription = NSLocalizedString("subscription.activate.modal.appleid.description", value: "Your subscription is automatically available on any device signed in to the same Apple ID.", comment: "Activate subscription modal description for Apple ID channel") - static let activateModalEmailDescription = NSLocalizedString("subscription.activate.modal.email.description", value: "Use your email to access your subscription on this device.", comment: "Activate subscription modal description for email address channel") - static let activateModalSyncDescription = NSLocalizedString("subscription.activate.modal.sync.description", value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", comment: "Activate subscription modal description for sync service channel") - - // MARK: - Share subscription modal - static let shareModalTitle = NSLocalizedString("subscription.share.modal.title", value: "Use your subscription on all your devices", comment: "Share subscription modal view title") - static let shareModalDescription = NSLocalizedString("subscription.share.modal.description", value: "Access your Privacy Pro subscription on any of your devices via Sync, Apple ID or by adding an email address.", comment: "Share subscription modal view subtitle description") - - static let shareModalAppleIDDescription = NSLocalizedString("subscription.share.modal.appleid.description", value: "Your subscription is automatically available on any device signed in to the same Apple ID.", comment: "Share subscription modal description for Apple ID channel") - static let shareModalHasEmailDescription = NSLocalizedString("subscription.share.modal.has.email.description", value: "You can use this email to activate your subscription on your other devices.", comment: "Share subscription modal description for email address channel") - static let shareModalNoEmailDescription = NSLocalizedString("subscription.share.modal.no.email.description", value: "Add an email address to access your subscription on your other devices. We’ll only use this address to verify your subscription.", comment: "Share subscription modal description for email address channel") - static let shareModalSyncDescription = NSLocalizedString("subscription.share.modal.sync.description", value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", comment: "Share subscription modal description for sync service channel") - - // MARK: - Activate/share modal buttons - static let restorePurchasesButton = NSLocalizedString("subscription.modal.restore.purchases.button", value: "Restore Purchases", comment: "Button for restoring past subscription purchases") - static let manageEmailButton = NSLocalizedString("subscription.modal.manage.email.button", value: "Manage", comment: "Button for opening manage email address page") - static let enterEmailButton = NSLocalizedString("subscription.modal.enter.email.button", value: "Enter Email", comment: "Button for opening page to enter email address") - static let goToSyncSettingsButton = NSLocalizedString("subscription.modal.sync.settings.button", value: "Go to Sync Settings", comment: "Button to open sync settings") -} -*/ diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index cba0bb90cd..eba9ee01d4 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -22,7 +22,7 @@ import PackageDescription let package = Package( name: "SubscriptionUI", platforms: [ - .iOS(.v14) + .iOS(.v15) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -31,6 +31,8 @@ let package = Package( targets: ["SubscriptionUI"]) ], dependencies: [ + .package(path: "../Account"), + .package(path: "../Purchase"), .package(path: "../DuckUI"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], @@ -38,6 +40,8 @@ let package = Package( .target( name: "SubscriptionUI", dependencies: [ + .product(name: "Account", package: "Account"), + .product(name: "Purchase", package: "Purchase"), .product(name: "DuckUI", package: "DuckUI"), "DesignResourcesKit" ]) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift new file mode 100644 index 0000000000..8eea0f112e --- /dev/null +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift @@ -0,0 +1,257 @@ +// +// UserText.swift +// +// 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 + +enum UserText { + // MARK: - Subscription preferences + + static let preferencesTitle = NSLocalizedString( + "subscription.preferences.title", + value: "Privacy Pro", + comment: "Title for the preferences pane for the subscription" + ) + + static let vpnServiceTitle = NSLocalizedString( + "subscription.preferences.services.vpn.title", + value: "VPN", + comment: "Title for the VPN service listed in the subscription preferences pane" + ) + static let vpnServiceDescription = NSLocalizedString( + "subscription.preferences.services.vpn.description", + value: "Full-device protection with the VPN built for speed and security.", + comment: "Description for the VPN service listed in the subscription preferences pane" + ) + + static let personalInformationRemovalServiceTitle = NSLocalizedString( + "subscription.preferences.services.personal.information.removal.title", + value: "Personal Information Removal", + comment: "Title for the Personal Information Removal service listed in the subscription preferences pane" + ) + static let personalInformationRemovalServiceDescription = NSLocalizedString( + "subscription.preferences.services.personal.information.removal.description", + value: "Find and remove your personal information from sites that store and sell it.", + comment: "Description for the Personal Information Removal service listed in the subscription preferences pane" + ) + + static let identityTheftRestorationServiceTitle = NSLocalizedString( + "subscription.preferences.services.identity.theft.restoration.title", + value: "Identity Theft Restoration", + comment: "Title for the Identity Theft Restoration service listed in the subscription preferences pane" + ) + static let identityTheftRestorationServiceDescription = NSLocalizedString( + "subscription.preferences.services.identity.theft.restoration.description", + value: "Restore stolen accounts and financial losses in the event of identity theft.", + comment: "Description for the Identity Theft Restoration service listed in the subscription preferences pane" + ) + + // MARK: Preferences footer + static let preferencesSubscriptionFooterTitle = NSLocalizedString( + "subscription.preferences.subscription.footer.title", + value: "Questions about Privacy Pro?", + comment: "Title for the subscription preferences pane footer" + ) + static let preferencesSubscriptionFooterCaption = NSLocalizedString( + "subscription.preferences.subscription.footer.caption", + value: "Visit our Privacy Pro help pages for answers to frequently asked questions.", + comment: "Caption for the subscription preferences pane footer" + ) + static let viewFaqsButton = NSLocalizedString( + "subscription.preferences.view.faqs.button", + value: "View FAQs", + comment: "Button to open page for FAQs" + ) + + // MARK: Preferences when subscription is active + static let preferencesSubscriptionActiveHeader = NSLocalizedString( + "subscription.preferences.subscription.active.header", + value: "Privacy Pro is active on this device", + comment: "Header for the subscription preferences pane when the subscription is active" + ) + static let preferencesSubscriptionActiveCaption = NSLocalizedString( + "subscription.preferences.subscription.active.caption", + value: "Your monthly Privacy Pro subscription renews on April 20, 2027.", + comment: "Caption for the subscription preferences pane when the subscription is active" + ) + + static let addToAnotherDeviceButton = NSLocalizedString( + "subscription.preferences.add.to.another.device.button", + value: "Add to Another Device…", + comment: "Button to add subscription to another device" + ) + static let manageSubscriptionButton = NSLocalizedString( + "subscription.preferences.manage.subscription.button", + value: "Manage Subscription", + comment: "Button to manage subscription" + ) + static let changePlanOrBillingButton = NSLocalizedString( + "subscription.preferences.change.plan.or.billing.button", + value: "Change Plan or Billing...", + comment: "Button to add subscription to another device" + ) + static let removeFromThisDeviceButton = NSLocalizedString( + "subscription.preferences.remove.from.this.device.button", + value: "Remove From This Device...", + comment: "Button to remove subscription from this device" + ) + + // MARK: Preferences when subscription is inactive + static let preferencesSubscriptionInactiveHeader = NSLocalizedString( + "subscription.preferences.subscription.inactive.header", + value: "One subscription, three advanced protections", + comment: "Header for the subscription preferences pane when the subscription is inactive" + ) + static let preferencesSubscriptionInactiveCaption = NSLocalizedString( + "subscription.preferences.subscription.inactive.caption", + value: "Get enhanced protection across all your devices and reduce your online footprint for as little as $9.99/mo.", + comment: "Caption for the subscription preferences pane when the subscription is inactive" + ) + + static let learnMoreButton = NSLocalizedString( + "subscription.preferences.learn.more.button", + value: "Learn More", + comment: "Button to open a page where user can learn more and purchase the subscription" + ) + static let haveSubscriptionButton = NSLocalizedString( + "subscription.preferences.i.have.a.subscription.button", + value: "I Have a Subscription", + comment: "Button enabling user to activate a subscription user bought earlier or on another device" + ) + + // MARK: - Remove from this device dialog + static let removeSubscriptionDialogTitle = NSLocalizedString( + "subscription.dialog.remove.title", + value: "Remove From This Device?", + comment: "Remove subscription from device dialog title" + ) + static let removeSubscriptionDialogDescription = NSLocalizedString( + "subscription.dialog.remove.description", + value: "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices.", + comment: "Remove subscription from device dialog subtitle description" + ) + static let removeSubscriptionDialogCancel = NSLocalizedString( + "subscription.dialog.remove.cancel.button", + value: "Cancel", + comment: "Button to cancel removing subscription from device" + ) + static let removeSubscriptionDialogConfirm = NSLocalizedString( + "subscription.dialog.remove.confirm", + value: "Remove Subscription", + comment: "Button to confirm removing subscription from device" + ) + + // MARK: - Services for accessing the subscription + static let appleID = NSLocalizedString( + "subscription.access.channel.appleid.name", + value: "Apple ID", + comment: "Service name displayed when accessing subscription using AppleID account" + ) + static let email = NSLocalizedString( + "subscription.access.channel.email.name", + value: "Email", + comment: "Service name displayed when accessing subscription using email address" + ) + static let sync = NSLocalizedString( + "subscription.access.channel.sync.name", + value: "Sync", + comment: "Service name displayed when accessing sync feature" + ) + + // MARK: - Activate subscription modal + static let activateModalTitle = NSLocalizedString( + "subscription.activate.modal.title", + value: "Activate your subscription on this device", + comment: "Activate subscription modal view title" + ) + static let activateModalDescription = NSLocalizedString( + "subscription.activate.modal.description", + value: "Access your Privacy Pro subscription on this device via Sync, Apple ID or an email address.", + comment: "Activate subscription modal view subtitle description" + ) + + static let activateModalAppleIDDescription = NSLocalizedString( + "subscription.activate.modal.appleid.description", + value: "Your subscription is automatically available on any device signed in to the same Apple ID.", + comment: "Activate subscription modal description for Apple ID channel" + ) + static let activateModalEmailDescription = NSLocalizedString( + "subscription.activate.modal.email.description", + value: "Use your email to access your subscription on this device.", + comment: "Activate subscription modal description for email address channel" + ) + static let activateModalSyncDescription = NSLocalizedString( + "subscription.activate.modal.sync.description", + value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", + comment: "Activate subscription modal description for sync service channel" + ) + + // MARK: - Share subscription modal + static let shareModalTitle = NSLocalizedString( + "subscription.share.modal.title", + value: "Use your subscription on all your devices", + comment: "Share subscription modal view title" + ) + static let shareModalDescription = NSLocalizedString( + "subscription.share.modal.description", + value: "Access your Privacy Pro subscription on any of your devices via Sync, Apple ID or by adding an email address.", + comment: "Share subscription modal view subtitle description" + ) + + static let shareModalAppleIDDescription = NSLocalizedString( + "subscription.share.modal.appleid.description", + value: "Your subscription is automatically available on any device signed in to the same Apple ID.", + comment: "Share subscription modal description for Apple ID channel" + ) + static let shareModalHasEmailDescription = NSLocalizedString( + "subscription.share.modal.has.email.description", + value: "You can use this email to activate your subscription on your other devices.", + comment: "Share subscription modal description for email address channel" + ) + static let shareModalNoEmailDescription = NSLocalizedString( + "subscription.share.modal.no.email.description", + value: "Add an email address to access your subscription on your other devices. We’ll only use this address to verify your subscription.", + comment: "Share subscription modal description for email address channel" + ) + static let shareModalSyncDescription = NSLocalizedString( + "subscription.share.modal.sync.description", + value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", + comment: "Share subscription modal description for sync service channel" + ) + + // MARK: - Activate/share modal buttons + static let restorePurchasesButton = NSLocalizedString( + "subscription.modal.restore.purchases.button", + value: "Restore Purchases", + comment: "Button for restoring past subscription purchases" + ) + static let manageEmailButton = NSLocalizedString( + "subscription.modal.manage.email.button", + value: "Manage", + comment: "Button for opening manage email address page" + ) + static let enterEmailButton = NSLocalizedString( + "subscription.modal.enter.email.button", + value: "Enter Email", + comment: "Button for opening page to enter email address" + ) + static let goToSyncSettingsButton = NSLocalizedString( + "subscription.modal.sync.settings.button", + value: "Go to Sync Settings", + comment: "Button to open sync settings" + ) +} diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift index 456906a107..f976aafc40 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift @@ -15,6 +15,74 @@ // 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 SwiftUI +import WebKit + +// MARK: - WebView +struct InfoWebView: UIViewRepresentable { + let url: URL + + func makeUIView(context: Context) -> WKWebView { + let webView = WKWebView() + webView.load(URLRequest(url: url)) + return webView + } + + func updateUIView(_ webView: WKWebView, context: Context) { + // Update the view if required. + } +} + +// MARK: - ContentView +struct ContainerView: View { + var body: some View { + NavigationView { + VStack { + // WebView Container + InfoWebView(url: URL(string: "https://www.example.com")!) + .edgesIgnoringSafeArea(.all) + + // Footer + HStack { + Button(action: { + // Action for monthly subscription + }) { + Text("$9.99 / month") + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding() + .background(Color.blue) + .cornerRadius(10) + } + + Button(action: { + // Action for yearly subscription + }) { + Text("$99.99 / year") + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding() + .background(Color.gray) + .cornerRadius(10) + } + } + .padding() + } + .navigationBarTitle("Privacy Pro", displayMode: .inline) + .navigationBarItems(leading: Button(action: { + // Action for closing the view + }) { + Image(systemName: "xmark") + }) + } + } +} + +// MARK: - Preview +struct ContainerView_Previews: PreviewProvider { + static var previews: some View { + ContainerView() + } +} From d09dc98d96a0d78f883c9913cc4936ed8dcc6ca9 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 27 Nov 2023 13:29:05 +0100 Subject: [PATCH 48/99] Updated constraints From 6253101e4aa250f5e8c8c779f8d366cb3f2f73f7 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 28 Nov 2023 00:15:53 +0100 Subject: [PATCH 49/99] Moved Dependencies into a single package --- Core/FeatureFlag.swift | 9 +- DuckDuckGo.xcodeproj/project.pbxproj | 55 +++- .../xcshareddata/swiftpm/Package.resolved | 178 ++++++++++++ .../SubscriptionPagesUserScript.swift | 266 ++++++++++++++++++ DuckDuckGo/SettingsViewController.swift | 51 +++- LocalPackages/Account/.gitignore | 9 - LocalPackages/Account/Package.swift | 27 -- LocalPackages/Purchase/.gitignore | 9 - LocalPackages/Purchase/Package.swift | 24 -- LocalPackages/Subscription/.gitignore | 9 - LocalPackages/Subscription/Package.swift | 10 +- .../Account/AccountManager.swift | 0 .../AccountKeychainStorage.swift | 0 .../AccountStorage/AccountStorage.swift | 0 .../Subscription}/Account/Logging.swift | 0 .../Account/Services/APIService.swift | 0 .../Account/Services/AuthService.swift | 0 .../Services/SubscriptionService.swift | 0 .../Purchase/PurchaseManager.swift | 0 .../AppStoreAccountManagementFlow.swift | 3 +- .../PurchaseFlows/AppStorePurchaseFlow.swift | 3 +- .../PurchaseFlows/AppStoreRestoreFlow.swift | 3 +- .../Subscription/URL+Subscription.swift | 44 --- .../AccountTests/AccountsTests.swift | 0 .../PurchaseTests/PurchaseTests.swift | 0 .../SubscriptionTests.swift | 0 LocalPackages/SubscriptionUI/.gitignore | 9 - LocalPackages/SubscriptionUI/Package.swift | 49 ---- LocalPackages/SubscriptionUI/README.md | 1 - .../Sources/SubscriptionUI/UserText.swift | 257 ----------------- .../Views/SubscriptionActionbar.swift | 20 -- .../Views/SubscriptionContainerView.swift | 88 ------ .../Views/SubscriptionWebView.swift | 55 ---- 33 files changed, 550 insertions(+), 629 deletions(-) create mode 100644 DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift delete mode 100644 LocalPackages/Account/.gitignore delete mode 100644 LocalPackages/Account/Package.swift delete mode 100644 LocalPackages/Purchase/.gitignore delete mode 100644 LocalPackages/Purchase/Package.swift delete mode 100644 LocalPackages/Subscription/.gitignore rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/AccountManager.swift (100%) rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/AccountStorage/AccountKeychainStorage.swift (100%) rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/AccountStorage/AccountStorage.swift (100%) rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/Logging.swift (100%) rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/Services/APIService.swift (100%) rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/Services/AuthService.swift (100%) rename LocalPackages/{Account/Sources => Subscription/Sources/Subscription}/Account/Services/SubscriptionService.swift (100%) rename LocalPackages/{Purchase/Sources => Subscription/Sources/Subscription}/Purchase/PurchaseManager.swift (100%) rename LocalPackages/Subscription/Sources/{ => Subscription}/PurchaseFlows/AppStoreAccountManagementFlow.swift (96%) rename LocalPackages/Subscription/Sources/{ => Subscription}/PurchaseFlows/AppStorePurchaseFlow.swift (98%) rename LocalPackages/Subscription/Sources/{ => Subscription}/PurchaseFlows/AppStoreRestoreFlow.swift (97%) delete mode 100644 LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift rename LocalPackages/{Account/Tests => Subscription/Tests/SubscriptionTests}/AccountTests/AccountsTests.swift (100%) rename LocalPackages/{Purchase/Tests => Subscription/Tests/SubscriptionTests}/PurchaseTests/PurchaseTests.swift (100%) rename LocalPackages/Subscription/Tests/SubscriptionTests/{ => SubscriptionTests}/SubscriptionTests.swift (100%) delete mode 100644 LocalPackages/SubscriptionUI/.gitignore delete mode 100644 LocalPackages/SubscriptionUI/Package.swift delete mode 100644 LocalPackages/SubscriptionUI/README.md delete mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift delete mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift delete mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift delete mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index dfe79f7d48..b3c019f78e 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -34,12 +34,19 @@ public enum FeatureFlag: String { case networkProtection case networkProtectionWaitlistAccess case networkProtectionWaitlistActive + case privacyPro } extension FeatureFlag: FeatureFlagSourceProviding { public var source: FeatureFlagSource { switch self { - case .debugMenu, .sync, .appTrackingProtection: + case .debugMenu, + .sync, + .appTrackingProtection, + .networkProtection, + .networkProtectionWaitlistAccess, + .networkProtectionWaitlistActive, + .privacyPro: return .internalOnly case .networkProtection: return .remoteReleasable(.feature(.networkProtection)) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2e4be90389..8313f58391 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -765,6 +765,9 @@ CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DDE29A6736A00832877 /* APIHeadersTests.swift */; }; CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; + D62529672B154637002A372F /* PrivacyPro in Resources */ = {isa = PBXBuildFile; fileRef = D62529662B154637002A372F /* PrivacyPro */; }; + D625296E2B1547C3002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */; }; + D62529732B15563A002A372F /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D62529722B15563A002A372F /* Subscription */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; @@ -2363,11 +2366,10 @@ CBF14FC227970072001D94D0 /* HomeMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageView.swift; sourceTree = ""; }; CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; + D62529662B154637002A372F /* PrivacyPro */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PrivacyPro; sourceTree = ""; }; + D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; + D625296F2B154A2D002A372F /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; - D649903F2B14A18300AFD57B /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Account; sourceTree = ""; }; - D64990402B14A18800AFD57B /* Purchase */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Purchase; sourceTree = ""; }; - D64990412B14A18C00AFD57B /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = ""; }; - D64990422B14A20700AFD57B /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -2592,6 +2594,7 @@ F42D541D29DCA40B004C4FF1 /* DesignResourcesKit in Frameworks */, 85875B6129912A9900115F05 /* SyncUI in Frameworks */, F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, + D62529732B15563A002A372F /* Subscription in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2852,6 +2855,7 @@ 02025B0A29884CF300E694E7 /* AppTrackingProtection */ = { isa = PBXGroup; children = ( + D62529662B154637002A372F /* PrivacyPro */, 02341FA22A435E42008A1531 /* AppTPOnboarding */, 0290471C29E7085D0008FE3C /* AppTPManageTrackersView */, 0290472629E8619B0008FE3C /* AppTPTrackerDetailView */, @@ -3349,10 +3353,7 @@ 31E69A60280F4BAD00478327 /* LocalPackages */ = { isa = PBXGroup; children = ( - D64990422B14A20700AFD57B /* Subscription */, - D649903F2B14A18300AFD57B /* Account */, - D64990402B14A18800AFD57B /* Purchase */, - D64990412B14A18C00AFD57B /* SubscriptionUI */, + D625296F2B154A2D002A372F /* Subscription */, 85875B5F29912A2D00115F05 /* SyncUI */, 37FCAACB2993149A000E420A /* Waitlist */, 31794BFF2821DFB600F18633 /* DuckUI */, @@ -3691,7 +3692,8 @@ 85AE668C20971FCA0014CF04 /* Notifications */, F1C4A70C1E5771F800A6CA1B /* OmniBar */, F1AE54DB1F0425BB00D9A700 /* Privacy */, - F1DF09502B039E6E008CC908 /* PrivacyDashboard */, + 1E87615728A1515400C7C5CE /* PrivacyDashboard */, + D625296A2B15465E002A372F /* PrivacyPro */, 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */, C1B7B51D28941F160098FD6A /* RemoteMessaging */, F1AB2B401E3F75A000868554 /* Settings */, @@ -4437,6 +4439,30 @@ name = Resources; sourceTree = ""; }; + D625296A2B15465E002A372F /* PrivacyPro */ = { + isa = PBXGroup; + children = ( + D625296C2B15468B002A372F /* UserScripts */, + D625296B2B15467E002A372F /* Views */, + ); + path = PrivacyPro; + sourceTree = ""; + }; + D625296B2B15467E002A372F /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + D625296C2B15468B002A372F /* UserScripts */ = { + isa = PBXGroup; + children = ( + D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */, + ); + path = UserScripts; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5461,6 +5487,7 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, + D62529722B15563A002A372F /* Subscription */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -5844,6 +5871,7 @@ 85F98F98296F4CB100742F4A /* SyncAssets.xcassets in Resources */, 984147AB24F025F700362052 /* Autocomplete.storyboard in Resources */, AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, + D62529672B154637002A372F /* PrivacyPro in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, F1E4A4451EE89460006F2EAE /* Bookmarks.storyboard in Resources */, @@ -6362,6 +6390,7 @@ 1E8AD1D127C000AB00ABA377 /* OngoingDownloadRow.swift in Sources */, 85058366219AE9EA00ED4EDB /* HomePageConfiguration.swift in Sources */, EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */, + D625296E2B1547C3002A372F /* SubscriptionPagesUserScript.swift in Sources */, C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */, 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, @@ -8059,7 +8088,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTIONS"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8591,7 +8620,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTIONS"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -9334,6 +9363,10 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Configuration; }; + D62529722B15563A002A372F /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + productName = Subscription; + }; EE8E56892A56BCE400F11DCA /* NetworkProtection */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..1f616e1038 --- /dev/null +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,178 @@ +{ + "object": { + "pins": [ + { + "package": "BloomFilter", + "repositoryURL": "https://github.com/duckduckgo/bloom_cpp.git", + "state": { + "branch": null, + "revision": "8076199456290b61b4544bf2f4caf296759906a0", + "version": "3.0.0" + } + }, + { + "package": "BrowserServicesKit", + "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", + "state": { + "branch": null, + "revision": "e4f4ae624174c1398d345cfc387db38f8f69986d", + "version": "94.0.0" + } + }, + { + "package": "CocoaAsyncSocket", + "repositoryURL": "https://github.com/robbiehanson/CocoaAsyncSocket", + "state": { + "branch": null, + "revision": "dbdc00669c1ced63b27c3c5f052ee4d28f10150c", + "version": "7.6.5" + } + }, + { + "package": "ContentScopeScripts", + "repositoryURL": "https://github.com/duckduckgo/content-scope-scripts", + "state": { + "branch": null, + "revision": "b7ad9843e70cede0c2ca9c4260d970f62cb28156", + "version": "4.52.0" + } + }, + { + "package": "DesignResourcesKit", + "repositoryURL": "https://github.com/duckduckgo/DesignResourcesKit", + "state": { + "branch": null, + "revision": "d7ea2561ec7624c224f52e1c9b349075ddf1c782", + "version": "2.0.0" + } + }, + { + "package": "Autofill", + "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state": { + "branch": null, + "revision": "dbecae0df07650a21b5632a92fa2e498c96af7b5", + "version": "10.0.1" + } + }, + { + "package": "GRDB", + "repositoryURL": "https://github.com/duckduckgo/GRDB.swift.git", + "state": { + "branch": null, + "revision": "77d9a83191a74e319a5cfa27b0e3145d15607573", + "version": "2.2.0" + } + }, + { + "package": "FindInPageIOSJSSupport", + "repositoryURL": "https://github.com/duckduckgo/ios-js-support", + "state": { + "branch": null, + "revision": "6a6789ac8104a587316c58af75539753853b50d9", + "version": "2.0.0" + } + }, + { + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher.git", + "state": { + "branch": null, + "revision": "af4be924ad984cf4d16f4ae4df424e79a443d435", + "version": "7.6.2" + } + }, + { + "package": "Lottie", + "repositoryURL": "https://github.com/duckduckgo/lottie-ios.git", + "state": { + "branch": null, + "revision": "abf5510e261c85ffddd29de0bca9b72592ea2bdd", + "version": "3.3.0" + } + }, + { + "package": "OHHTTPStubs", + "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", + "state": { + "branch": null, + "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version": "9.1.0" + } + }, + { + "package": "PrivacyDashboardResources", + "repositoryURL": "https://github.com/duckduckgo/privacy-dashboard", + "state": { + "branch": null, + "revision": "38336a574e13090764ba09a6b877d15ee514e371", + "version": "3.1.1" + } + }, + { + "package": "Punycode", + "repositoryURL": "https://github.com/gumob/PunycodeSwift.git", + "state": { + "branch": null, + "revision": "4356ec54e073741449640d3d50a1fd24fd1e1b8b", + "version": "2.1.0" + } + }, + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "c8ed701b513cf5177118a175d85fbbbcd707ab41", + "version": "1.3.0" + } + }, + { + "package": "Swifter", + "repositoryURL": "https://github.com/httpswift/swifter.git", + "state": { + "branch": null, + "revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version": "1.5.0" + } + }, + { + "package": "SwiftSoup", + "repositoryURL": "https://github.com/scinfu/SwiftSoup", + "state": { + "branch": null, + "revision": "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886", + "version": "2.4.2" + } + }, + { + "package": "DDGSyncCrypto", + "repositoryURL": "https://github.com/duckduckgo/sync_crypto", + "state": { + "branch": null, + "revision": "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version": "0.2.0" + } + }, + { + "package": "TrackerRadarKit", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", + "state": { + "branch": null, + "revision": "a6b7ba151d9dc6684484f3785293875ec01cc1ff", + "version": "1.2.2" + } + }, + { + "package": "WireGuardKit", + "repositoryURL": "https://github.com/duckduckgo/wireguard-apple", + "state": { + "branch": null, + "revision": "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", + "version": "1.1.1" + } + } + ] + }, + "version": 1 +} diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift new file mode 100644 index 0000000000..5d186734d2 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift @@ -0,0 +1,266 @@ +// +// SubscriptionPagesUserScript.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. +// + +#if SUBSCRIPTION + +import BrowserServicesKit +import Common +import Combine +import Foundation +import WebKit +import UserScript +import Subscription + +public extension Notification.Name { + static let subscriptionPageCloseAndOpenPreferences = Notification.Name("com.duckduckgo.subscriptionPage.CloseAndOpenPreferences") +} + +/// +/// The user script that will be the broker for all subscription features +/// +public final class SubscriptionPagesUserScript: NSObject, UserScript, UserScriptMessaging { + public var source: String = "" + + public static let context = "subscriptionPages" + + // special pages messaging cannot be isolated as we'll want regular page-scripts to be able to communicate + public let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: true ) + + public let messageNames: [String] = [ + SubscriptionPagesUserScript.context + ] + + public let injectionTime: WKUserScriptInjectionTime = .atDocumentStart + public let forMainFrameOnly = true + public let requiresRunInPageContentWorld = true +} + +extension SubscriptionPagesUserScript: WKScriptMessageHandlerWithReply { + @MainActor + public func userContentController(_ userContentController: WKUserContentController, + didReceive message: WKScriptMessage) async -> (Any?, String?) { + let action = broker.messageHandlerFor(message) + do { + let json = try await broker.execute(action: action, original: message) + return (json, nil) + } catch { + // forward uncaught errors to the client + return (nil, error.localizedDescription) + } + } +} + +// MARK: - Fallback for macOS 10.15 +extension SubscriptionPagesUserScript: WKScriptMessageHandler { + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + // unsupported + } +} + +/// +/// Use Subscription sub-feature +/// +final class SubscriptionPagesUseSubscriptionFeature: Subfeature { + var broker: UserScriptMessageBroker? + + var featureName = "useSubscription" + + var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ + .exact(hostname: "duckduckgo.com"), + .exact(hostname: "abrown.duckduckgo.com") + ]) + + func with(broker: UserScriptMessageBroker) { + self.broker = broker + } + + func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + switch methodName { + case "getSubscription": return getSubscription + case "setSubscription": return setSubscription + case "backToSettings": return backToSettings + case "getSubscriptionOptions": return getSubscriptionOptions + case "subscriptionSelected": return subscriptionSelected + case "activateSubscription": return activateSubscription + case "featureSelected": return featureSelected + default: + return nil + } + } + + struct Subscription: Encodable { + let token: String + } + + /// Values that the Frontend can use to determine the current state. + struct SubscriptionValues: Codable { + enum CodingKeys: String, CodingKey { + case token + } + let token: String + } + + func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + var authToken = AccountManager().authToken ?? "" + return Subscription(token: authToken) + } + + func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + return nil + } + + await AccountManager().exchangeAndStoreTokens(with: subscriptionValues.token) + return nil + } + + func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + await AccountManager().refreshAccountData() + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) + } + + return nil + } + + func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct SubscriptionOptions: Encodable { + let platform: String + let options: [SubscriptionOption] + let features: [SubscriptionFeature] + } + + struct SubscriptionOption: Encodable { + let id: String + let cost: SubscriptionCost + + struct SubscriptionCost: Encodable { + let displayPrice: String + let recurrence: String + } + } + + enum SubscriptionFeatureName: String, CaseIterable { + case privateBrowsing = "private-browsing" + case privateSearch = "private-search" + case emailProtection = "email-protection" + case appTrackingProtection = "app-tracking-protection" + case vpn = "vpn" + case personalInformationRemoval = "personal-information-removal" + case identityTheftRestoration = "identity-theft-restoration" + } + + struct SubscriptionFeature: Encodable { + let name: String + } + + let subscriptionOptions: [SubscriptionOption] + + if #available(macOS 12.0, iOS 15, *) { + let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1month") }) + let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1year") }) + + guard let monthly, let yearly else { return nil } + + subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: "monthly")), + SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: "yearly"))] + } else { + return nil + } + + let message = SubscriptionOptions(platform: "macos", + options: subscriptionOptions, + features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) + + return message + } + + func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct SubscriptionSelection: Decodable { + let id: String + } + + let message = original + + if #available(macOS 12.0, iOS 15, *) { + guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + return nil + } + + print("Selected: \(subscriptionSelection.id)") + + let emailAccessToken = try? EmailManager().getToken() + + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + case .success: + break + case .failure(let error): + print("Purchase failed: \(error)") + return nil + } + + await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) + + DispatchQueue.main.async { + self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) + } + } + + return nil + } + + func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + print(">>> Selected to activate a subscription -- show the activation settings screen") + return nil + } + + func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct FeatureSelection: Codable { + let feature: String + } + + guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") + return nil + } + + print(">>> Selected a feature -- show the corresponding UI", featureSelection) + return nil + } + + enum SubscribeActionName: String { + case onPurchaseUpdate + } + + struct PurchaseUpdate: Codable { + let type: String + } + + func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { + let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: true ) + + print(">>> Pushing into WebView:", method.rawValue, String(describing: params)) + broker.push(method: method.rawValue, params: params, for: self, into: webView) + } +} + +#endif diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index ea2a89ed79..c6e9334ff3 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -81,8 +81,10 @@ class SettingsViewController: UITableViewController { private let syncSectionIndex = 1 private let autofillSectionIndex = 2 private let appearanceSectionIndex = 3 - private let moreFromDDGSectionIndex = 6 - private let debugSectionIndex = 8 + private let privacyProSectionIndex = 5 + private let moreFromDDGSectionIndex = 7 + private let debugSectionIndex = 9 + private let bookmarksDatabase: CoreDataDatabase @@ -136,6 +138,19 @@ class SettingsViewController: UITableViewController { return false #endif }() + + private lazy var shouldShowPrivacyPro: Bool = { +#if PRIVACYPRO + if #available(iOS 15, *) { + // return featureFlagger.isFeatureOn(.privacyPro) + return true + } else { + return false + } +#else + return false +#endif + }() override func viewDidLoad() { super.viewDidLoad() @@ -155,6 +170,7 @@ class SettingsViewController: UITableViewController { configureDebugCell() configureVoiceSearchCell() configureNetPCell() + configurePrivacyPro() applyTheme(ThemeManager.shared.currentTheme) internalUserDecider.isInternalUserPublisher.dropFirst().sink(receiveValue: { [weak self] _ in @@ -357,6 +373,9 @@ class SettingsViewController: UITableViewController { } } + private func configurePrivacyPro() { + } + private func configureNetPCell() { netPCell.isHidden = !shouldShowNetPCell #if NETWORK_PROTECTION @@ -461,6 +480,13 @@ class SettingsViewController: UITableViewController { ) } #endif + +#if PRIVACYPRO + @available(iOS 15, *) + private func showPrivacyPro() { + + } +#endif private func showWindowsBrowserWaitlistViewController() { navigationController?.pushViewController(WindowsWaitlistViewController(nibName: nil, bundle: nil), animated: true) @@ -505,6 +531,16 @@ class SettingsViewController: UITableViewController { break #endif } + + case privacyProSignupCell: + if #available(iOS 15, *) { +#if PRIVACYPRO + showPrivacyPro() +#else + break +#endif + } + default: break } @@ -550,6 +586,8 @@ class SettingsViewController: UITableViewController { return CGFloat.leastNonzeroMagnitude } else if debugSectionIndex == section && !shouldShowDebugCell { return CGFloat.leastNonzeroMagnitude + } else if privacyProSectionIndex == section && !shouldShowPrivacyPro { + return CGFloat.leastNonzeroMagnitude } else { return super.tableView(tableView, heightForHeaderInSection: section) } @@ -577,10 +615,19 @@ class SettingsViewController: UITableViewController { } else if section == appearanceSectionIndex && UIDevice.current.userInterfaceIdiom == .pad { // Both the text size and bottom bar settings are at the end of the section so need to reduce the section size appropriately return rows - 2 + } else if section == privacyProSectionIndex && !shouldShowPrivacyPro { + return 0 } else { return rows } } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + if section == privacyProSectionIndex && !shouldShowPrivacyPro { + return nil + } + return super.tableView(tableView, titleForHeaderInSection: section) + } @IBAction func onVoiceSearchToggled(_ sender: UISwitch) { var enableVoiceSearch = sender.isOn diff --git a/LocalPackages/Account/.gitignore b/LocalPackages/Account/.gitignore deleted file mode 100644 index 3b29812086..0000000000 --- a/LocalPackages/Account/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/config/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift deleted file mode 100644 index 5e4b768925..0000000000 --- a/LocalPackages/Account/Package.swift +++ /dev/null @@ -1,27 +0,0 @@ -// swift-tools-version: 5.8 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "Account", - platforms: [ .macOS(.v11), .iOS(.v15) ], - products: [ - .library( - name: "Account", - targets: ["Account"]), - ], - dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "85.0.2"), - ], - targets: [ - .target( - name: "Account", - dependencies: [ - .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), - ]), - .testTarget( - name: "AccountTests", - dependencies: ["Account"]), - ] -) diff --git a/LocalPackages/Purchase/.gitignore b/LocalPackages/Purchase/.gitignore deleted file mode 100644 index 3b29812086..0000000000 --- a/LocalPackages/Purchase/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/config/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift deleted file mode 100644 index 0cd9799bf2..0000000000 --- a/LocalPackages/Purchase/Package.swift +++ /dev/null @@ -1,24 +0,0 @@ -// swift-tools-version: 5.8 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "Purchase", - platforms: [ .macOS(.v11), .iOS(.v15) ], - products: [ - .library( - name: "Purchase", - targets: ["Purchase"]), - ], - dependencies: [ - ], - targets: [ - .target( - name: "Purchase", - dependencies: []), - .testTarget( - name: "PurchaseTests", - dependencies: ["Purchase"]), - ] -) diff --git a/LocalPackages/Subscription/.gitignore b/LocalPackages/Subscription/.gitignore deleted file mode 100644 index 3b29812086..0000000000 --- a/LocalPackages/Subscription/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/config/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 827ef93fa8..2c8b7f01e1 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -11,17 +11,11 @@ let package = Package( name: "Subscription", targets: ["Subscription"]), ], - dependencies: [ - .package(path: "../Account"), - .package(path: "../Purchase"), - ], + dependencies: [], targets: [ .target( name: "Subscription", - dependencies: [ - .product(name: "Account", package: "Account"), - .product(name: "Purchase", package: "Purchase"), - ]), + dependencies: []), .testTarget( name: "SubscriptionTests", dependencies: ["Subscription"]), diff --git a/LocalPackages/Account/Sources/Account/AccountManager.swift b/LocalPackages/Subscription/Sources/Subscription/Account/AccountManager.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/AccountManager.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/AccountManager.swift diff --git a/LocalPackages/Account/Sources/Account/AccountStorage/AccountKeychainStorage.swift b/LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountKeychainStorage.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/AccountStorage/AccountKeychainStorage.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountKeychainStorage.swift diff --git a/LocalPackages/Account/Sources/Account/AccountStorage/AccountStorage.swift b/LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountStorage.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/AccountStorage/AccountStorage.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountStorage.swift diff --git a/LocalPackages/Account/Sources/Account/Logging.swift b/LocalPackages/Subscription/Sources/Subscription/Account/Logging.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/Logging.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/Logging.swift diff --git a/LocalPackages/Account/Sources/Account/Services/APIService.swift b/LocalPackages/Subscription/Sources/Subscription/Account/Services/APIService.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/Services/APIService.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/Services/APIService.swift diff --git a/LocalPackages/Account/Sources/Account/Services/AuthService.swift b/LocalPackages/Subscription/Sources/Subscription/Account/Services/AuthService.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/Services/AuthService.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/Services/AuthService.swift diff --git a/LocalPackages/Account/Sources/Account/Services/SubscriptionService.swift b/LocalPackages/Subscription/Sources/Subscription/Account/Services/SubscriptionService.swift similarity index 100% rename from LocalPackages/Account/Sources/Account/Services/SubscriptionService.swift rename to LocalPackages/Subscription/Sources/Subscription/Account/Services/SubscriptionService.swift diff --git a/LocalPackages/Purchase/Sources/Purchase/PurchaseManager.swift b/LocalPackages/Subscription/Sources/Subscription/Purchase/PurchaseManager.swift similarity index 100% rename from LocalPackages/Purchase/Sources/Purchase/PurchaseManager.swift rename to LocalPackages/Subscription/Sources/Subscription/Purchase/PurchaseManager.swift diff --git a/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreAccountManagementFlow.swift b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift similarity index 96% rename from LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreAccountManagementFlow.swift rename to LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift index 39b0351603..5d42ea7130 100644 --- a/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreAccountManagementFlow.swift +++ b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift @@ -18,7 +18,6 @@ import Foundation import StoreKit -import Account public final class AppStoreAccountManagementFlow { @@ -35,7 +34,7 @@ public final class AppStoreAccountManagementFlow { if case let .failure(error) = await AuthService.validateToken(accessToken: authToken) { print(error) - if #available(macOS 12.0, iOS 15.0, *) { + if #available(macOS 12.0, *) { // In case of invalid token attempt store based authentication to obtain a new one guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.noPastTransaction) } diff --git a/LocalPackages/Subscription/Sources/PurchaseFlows/AppStorePurchaseFlow.swift b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift similarity index 98% rename from LocalPackages/Subscription/Sources/PurchaseFlows/AppStorePurchaseFlow.swift rename to LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift index 1e1e90e67a..4de04c4b9a 100644 --- a/LocalPackages/Subscription/Sources/PurchaseFlows/AppStorePurchaseFlow.swift +++ b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift @@ -18,9 +18,8 @@ import Foundation import StoreKit -import Account -@available(macOS 12.0, iOS 15.0, *) +@available(macOS 12.0, *) public final class AppStorePurchaseFlow { public enum Error: Swift.Error { diff --git a/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreRestoreFlow.swift b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift similarity index 97% rename from LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreRestoreFlow.swift rename to LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift index 42caa8a4c8..32ca71ea4c 100644 --- a/LocalPackages/Subscription/Sources/PurchaseFlows/AppStoreRestoreFlow.swift +++ b/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift @@ -18,9 +18,8 @@ import Foundation import StoreKit -import Account -@available(macOS 12.0, iOS 15.0, *) +@available(macOS 12.0, *) public final class AppStoreRestoreFlow { public enum Error: Swift.Error { diff --git a/LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift b/LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift deleted file mode 100644 index dbb018b53e..0000000000 --- a/LocalPackages/Subscription/Sources/Subscription/URL+Subscription.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// URL+Subscription.swift -// -// 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 - -public extension URL { - - static var purchaseSubscription: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/welcome")! - } - - static var subscriptionFAQ: URL { - URL(string: "https://duckduckgo.com/about")! - } - - - // MARK: - Subscription Email - static var activateSubscriptionViaEmail: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/activate")! - } - - static var addEmailToSubscription: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/add-email")! - } - - static var manageSubscriptionEmail: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/manage")! - } -} diff --git a/LocalPackages/Account/Tests/AccountTests/AccountsTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/AccountTests/AccountsTests.swift similarity index 100% rename from LocalPackages/Account/Tests/AccountTests/AccountsTests.swift rename to LocalPackages/Subscription/Tests/SubscriptionTests/AccountTests/AccountsTests.swift diff --git a/LocalPackages/Purchase/Tests/PurchaseTests/PurchaseTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/PurchaseTests/PurchaseTests.swift similarity index 100% rename from LocalPackages/Purchase/Tests/PurchaseTests/PurchaseTests.swift rename to LocalPackages/Subscription/Tests/SubscriptionTests/PurchaseTests/PurchaseTests.swift diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests/SubscriptionTests.swift similarity index 100% rename from LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift rename to LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests/SubscriptionTests.swift diff --git a/LocalPackages/SubscriptionUI/.gitignore b/LocalPackages/SubscriptionUI/.gitignore deleted file mode 100644 index 3b29812086..0000000000 --- a/LocalPackages/SubscriptionUI/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/config/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift deleted file mode 100644 index eba9ee01d4..0000000000 --- a/LocalPackages/SubscriptionUI/Package.swift +++ /dev/null @@ -1,49 +0,0 @@ -// swift-tools-version:5.7 -// Package.swift -// DuckDuckGo -// -// Copyright © 2022 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 PackageDescription - -let package = Package( - name: "SubscriptionUI", - platforms: [ - .iOS(.v15) - ], - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "SubscriptionUI", - targets: ["SubscriptionUI"]) - ], - dependencies: [ - .package(path: "../Account"), - .package(path: "../Purchase"), - .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") - ], - targets: [ - .target( - name: "SubscriptionUI", - dependencies: [ - .product(name: "Account", package: "Account"), - .product(name: "Purchase", package: "Purchase"), - .product(name: "DuckUI", package: "DuckUI"), - "DesignResourcesKit" - ]) - ] -) diff --git a/LocalPackages/SubscriptionUI/README.md b/LocalPackages/SubscriptionUI/README.md deleted file mode 100644 index b247174116..0000000000 --- a/LocalPackages/SubscriptionUI/README.md +++ /dev/null @@ -1 +0,0 @@ -# Subscription UI diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift deleted file mode 100644 index 8eea0f112e..0000000000 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift +++ /dev/null @@ -1,257 +0,0 @@ -// -// UserText.swift -// -// 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 - -enum UserText { - // MARK: - Subscription preferences - - static let preferencesTitle = NSLocalizedString( - "subscription.preferences.title", - value: "Privacy Pro", - comment: "Title for the preferences pane for the subscription" - ) - - static let vpnServiceTitle = NSLocalizedString( - "subscription.preferences.services.vpn.title", - value: "VPN", - comment: "Title for the VPN service listed in the subscription preferences pane" - ) - static let vpnServiceDescription = NSLocalizedString( - "subscription.preferences.services.vpn.description", - value: "Full-device protection with the VPN built for speed and security.", - comment: "Description for the VPN service listed in the subscription preferences pane" - ) - - static let personalInformationRemovalServiceTitle = NSLocalizedString( - "subscription.preferences.services.personal.information.removal.title", - value: "Personal Information Removal", - comment: "Title for the Personal Information Removal service listed in the subscription preferences pane" - ) - static let personalInformationRemovalServiceDescription = NSLocalizedString( - "subscription.preferences.services.personal.information.removal.description", - value: "Find and remove your personal information from sites that store and sell it.", - comment: "Description for the Personal Information Removal service listed in the subscription preferences pane" - ) - - static let identityTheftRestorationServiceTitle = NSLocalizedString( - "subscription.preferences.services.identity.theft.restoration.title", - value: "Identity Theft Restoration", - comment: "Title for the Identity Theft Restoration service listed in the subscription preferences pane" - ) - static let identityTheftRestorationServiceDescription = NSLocalizedString( - "subscription.preferences.services.identity.theft.restoration.description", - value: "Restore stolen accounts and financial losses in the event of identity theft.", - comment: "Description for the Identity Theft Restoration service listed in the subscription preferences pane" - ) - - // MARK: Preferences footer - static let preferencesSubscriptionFooterTitle = NSLocalizedString( - "subscription.preferences.subscription.footer.title", - value: "Questions about Privacy Pro?", - comment: "Title for the subscription preferences pane footer" - ) - static let preferencesSubscriptionFooterCaption = NSLocalizedString( - "subscription.preferences.subscription.footer.caption", - value: "Visit our Privacy Pro help pages for answers to frequently asked questions.", - comment: "Caption for the subscription preferences pane footer" - ) - static let viewFaqsButton = NSLocalizedString( - "subscription.preferences.view.faqs.button", - value: "View FAQs", - comment: "Button to open page for FAQs" - ) - - // MARK: Preferences when subscription is active - static let preferencesSubscriptionActiveHeader = NSLocalizedString( - "subscription.preferences.subscription.active.header", - value: "Privacy Pro is active on this device", - comment: "Header for the subscription preferences pane when the subscription is active" - ) - static let preferencesSubscriptionActiveCaption = NSLocalizedString( - "subscription.preferences.subscription.active.caption", - value: "Your monthly Privacy Pro subscription renews on April 20, 2027.", - comment: "Caption for the subscription preferences pane when the subscription is active" - ) - - static let addToAnotherDeviceButton = NSLocalizedString( - "subscription.preferences.add.to.another.device.button", - value: "Add to Another Device…", - comment: "Button to add subscription to another device" - ) - static let manageSubscriptionButton = NSLocalizedString( - "subscription.preferences.manage.subscription.button", - value: "Manage Subscription", - comment: "Button to manage subscription" - ) - static let changePlanOrBillingButton = NSLocalizedString( - "subscription.preferences.change.plan.or.billing.button", - value: "Change Plan or Billing...", - comment: "Button to add subscription to another device" - ) - static let removeFromThisDeviceButton = NSLocalizedString( - "subscription.preferences.remove.from.this.device.button", - value: "Remove From This Device...", - comment: "Button to remove subscription from this device" - ) - - // MARK: Preferences when subscription is inactive - static let preferencesSubscriptionInactiveHeader = NSLocalizedString( - "subscription.preferences.subscription.inactive.header", - value: "One subscription, three advanced protections", - comment: "Header for the subscription preferences pane when the subscription is inactive" - ) - static let preferencesSubscriptionInactiveCaption = NSLocalizedString( - "subscription.preferences.subscription.inactive.caption", - value: "Get enhanced protection across all your devices and reduce your online footprint for as little as $9.99/mo.", - comment: "Caption for the subscription preferences pane when the subscription is inactive" - ) - - static let learnMoreButton = NSLocalizedString( - "subscription.preferences.learn.more.button", - value: "Learn More", - comment: "Button to open a page where user can learn more and purchase the subscription" - ) - static let haveSubscriptionButton = NSLocalizedString( - "subscription.preferences.i.have.a.subscription.button", - value: "I Have a Subscription", - comment: "Button enabling user to activate a subscription user bought earlier or on another device" - ) - - // MARK: - Remove from this device dialog - static let removeSubscriptionDialogTitle = NSLocalizedString( - "subscription.dialog.remove.title", - value: "Remove From This Device?", - comment: "Remove subscription from device dialog title" - ) - static let removeSubscriptionDialogDescription = NSLocalizedString( - "subscription.dialog.remove.description", - value: "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices.", - comment: "Remove subscription from device dialog subtitle description" - ) - static let removeSubscriptionDialogCancel = NSLocalizedString( - "subscription.dialog.remove.cancel.button", - value: "Cancel", - comment: "Button to cancel removing subscription from device" - ) - static let removeSubscriptionDialogConfirm = NSLocalizedString( - "subscription.dialog.remove.confirm", - value: "Remove Subscription", - comment: "Button to confirm removing subscription from device" - ) - - // MARK: - Services for accessing the subscription - static let appleID = NSLocalizedString( - "subscription.access.channel.appleid.name", - value: "Apple ID", - comment: "Service name displayed when accessing subscription using AppleID account" - ) - static let email = NSLocalizedString( - "subscription.access.channel.email.name", - value: "Email", - comment: "Service name displayed when accessing subscription using email address" - ) - static let sync = NSLocalizedString( - "subscription.access.channel.sync.name", - value: "Sync", - comment: "Service name displayed when accessing sync feature" - ) - - // MARK: - Activate subscription modal - static let activateModalTitle = NSLocalizedString( - "subscription.activate.modal.title", - value: "Activate your subscription on this device", - comment: "Activate subscription modal view title" - ) - static let activateModalDescription = NSLocalizedString( - "subscription.activate.modal.description", - value: "Access your Privacy Pro subscription on this device via Sync, Apple ID or an email address.", - comment: "Activate subscription modal view subtitle description" - ) - - static let activateModalAppleIDDescription = NSLocalizedString( - "subscription.activate.modal.appleid.description", - value: "Your subscription is automatically available on any device signed in to the same Apple ID.", - comment: "Activate subscription modal description for Apple ID channel" - ) - static let activateModalEmailDescription = NSLocalizedString( - "subscription.activate.modal.email.description", - value: "Use your email to access your subscription on this device.", - comment: "Activate subscription modal description for email address channel" - ) - static let activateModalSyncDescription = NSLocalizedString( - "subscription.activate.modal.sync.description", - value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", - comment: "Activate subscription modal description for sync service channel" - ) - - // MARK: - Share subscription modal - static let shareModalTitle = NSLocalizedString( - "subscription.share.modal.title", - value: "Use your subscription on all your devices", - comment: "Share subscription modal view title" - ) - static let shareModalDescription = NSLocalizedString( - "subscription.share.modal.description", - value: "Access your Privacy Pro subscription on any of your devices via Sync, Apple ID or by adding an email address.", - comment: "Share subscription modal view subtitle description" - ) - - static let shareModalAppleIDDescription = NSLocalizedString( - "subscription.share.modal.appleid.description", - value: "Your subscription is automatically available on any device signed in to the same Apple ID.", - comment: "Share subscription modal description for Apple ID channel" - ) - static let shareModalHasEmailDescription = NSLocalizedString( - "subscription.share.modal.has.email.description", - value: "You can use this email to activate your subscription on your other devices.", - comment: "Share subscription modal description for email address channel" - ) - static let shareModalNoEmailDescription = NSLocalizedString( - "subscription.share.modal.no.email.description", - value: "Add an email address to access your subscription on your other devices. We’ll only use this address to verify your subscription.", - comment: "Share subscription modal description for email address channel" - ) - static let shareModalSyncDescription = NSLocalizedString( - "subscription.share.modal.sync.description", - value: "Privacy Pro is automatically available on your Synced devices. Manage your synced devices in Sync settings.", - comment: "Share subscription modal description for sync service channel" - ) - - // MARK: - Activate/share modal buttons - static let restorePurchasesButton = NSLocalizedString( - "subscription.modal.restore.purchases.button", - value: "Restore Purchases", - comment: "Button for restoring past subscription purchases" - ) - static let manageEmailButton = NSLocalizedString( - "subscription.modal.manage.email.button", - value: "Manage", - comment: "Button for opening manage email address page" - ) - static let enterEmailButton = NSLocalizedString( - "subscription.modal.enter.email.button", - value: "Enter Email", - comment: "Button for opening page to enter email address" - ) - static let goToSyncSettingsButton = NSLocalizedString( - "subscription.modal.sync.settings.button", - value: "Go to Sync Settings", - comment: "Button to open sync settings" - ) -} diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift deleted file mode 100644 index daac719e9d..0000000000 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionActionbar.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// SubscriptionActionbar.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 SwiftUI diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift deleted file mode 100644 index f976aafc40..0000000000 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionContainerView.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// SubscriptionContainerView.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 WebKit - -// MARK: - WebView -struct InfoWebView: UIViewRepresentable { - let url: URL - - func makeUIView(context: Context) -> WKWebView { - let webView = WKWebView() - webView.load(URLRequest(url: url)) - return webView - } - - func updateUIView(_ webView: WKWebView, context: Context) { - // Update the view if required. - } -} - -// MARK: - ContentView -struct ContainerView: View { - var body: some View { - NavigationView { - VStack { - // WebView Container - InfoWebView(url: URL(string: "https://www.example.com")!) - .edgesIgnoringSafeArea(.all) - - // Footer - HStack { - Button(action: { - // Action for monthly subscription - }) { - Text("$9.99 / month") - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding() - .background(Color.blue) - .cornerRadius(10) - } - - Button(action: { - // Action for yearly subscription - }) { - Text("$99.99 / year") - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding() - .background(Color.gray) - .cornerRadius(10) - } - } - .padding() - } - .navigationBarTitle("Privacy Pro", displayMode: .inline) - .navigationBarItems(leading: Button(action: { - // Action for closing the view - }) { - Image(systemName: "xmark") - }) - } - } -} - -// MARK: - Preview -struct ContainerView_Previews: PreviewProvider { - static var previews: some View { - ContainerView() - } -} diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift deleted file mode 100644 index fae2f26566..0000000000 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Views/SubscriptionWebView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SubscriptionWebView.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 SwiftUI -import WebKit - -struct WebView: UIViewRepresentable { - let urlString: String - - func makeUIView(context: Context) -> WKWebView { - guard let url = URL(string: urlString) else { - return WKWebView() - } - let request = URLRequest(url: url) - let webView = WKWebView() - webView.load(request) - return webView - } - - func updateUIView(_ uiView: WKWebView, context: Context) { - - } -} - -struct ContentView: View { - var body: some View { - WebView(urlString: "https://www.google.com") - .edgesIgnoringSafeArea(.all) // Use this to make the web view full screen - } -} - -@main -struct MyApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} From db15d93d04ac25895ba04ac5a105b51a20825acb Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 28 Nov 2023 15:55:12 +0100 Subject: [PATCH 50/99] Display Base Webview --- DuckDuckGo.xcodeproj/project.pbxproj | 113 ++++++++++++++++-- .../PrivacyPro}/Account/AccountManager.swift | 0 .../AccountKeychainStorage.swift | 0 .../AccountStorage/AccountStorage.swift | 0 .../PrivacyPro}/Account/Logging.swift | 0 .../Account/Services/APIService.swift | 0 .../Account/Services/AuthService.swift | 0 .../Services/SubscriptionService.swift | 0 .../Purchase/PurchaseManager.swift | 0 .../AppStoreAccountManagementFlow.swift | 0 .../PurchaseFlows/AppStorePurchaseFlow.swift | 0 .../PurchaseFlows/AppStoreRestoreFlow.swift | 0 DuckDuckGo/PrivacyPro/URL+Subscription.swift | 44 +++++++ .../SubscriptionPagesUserScript.swift | 12 +- .../Views/SubscriptionFlowView.swift | 91 ++++++++++++++ DuckDuckGo/SettingsViewController.swift | 12 +- 16 files changed, 248 insertions(+), 24 deletions(-) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/AccountManager.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/AccountStorage/AccountKeychainStorage.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/AccountStorage/AccountStorage.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/Logging.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/Services/APIService.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/Services/AuthService.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Account/Services/SubscriptionService.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/Purchase/PurchaseManager.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/PurchaseFlows/AppStoreAccountManagementFlow.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/PurchaseFlows/AppStorePurchaseFlow.swift (100%) rename {LocalPackages/Subscription/Sources/Subscription => DuckDuckGo/PrivacyPro}/PurchaseFlows/AppStoreRestoreFlow.swift (100%) create mode 100644 DuckDuckGo/PrivacyPro/URL+Subscription.swift create mode 100644 DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8313f58391..5818650adf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -767,7 +767,20 @@ CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; D62529672B154637002A372F /* PrivacyPro in Resources */ = {isa = PBXBuildFile; fileRef = D62529662B154637002A372F /* PrivacyPro */; }; D625296E2B1547C3002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */; }; - D62529732B15563A002A372F /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D62529722B15563A002A372F /* Subscription */; }; + D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529742B155BCB002A372F /* SubscriptionFlowView.swift */; }; + D625298A2B15E44A002A372F /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625297D2B15E449002A372F /* Logging.swift */; }; + D625298B2B15E44A002A372F /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625297E2B15E449002A372F /* AccountManager.swift */; }; + D625298C2B15E44A002A372F /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529802B15E449002A372F /* AccountKeychainStorage.swift */; }; + D625298D2B15E44A002A372F /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529812B15E449002A372F /* AccountStorage.swift */; }; + D625298E2B15E44A002A372F /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529832B15E449002A372F /* SubscriptionService.swift */; }; + D625298F2B15E44A002A372F /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529842B15E449002A372F /* APIService.swift */; }; + D62529902B15E44A002A372F /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529852B15E449002A372F /* AuthService.swift */; }; + D62529912B15E44A002A372F /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529872B15E449002A372F /* PurchaseManager.swift */; }; + D62529922B15E44A002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */; }; + D62529942B15E454002A372F /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529932B15E454002A372F /* URL+Subscription.swift */; }; + D62529992B15E4B9002A372F /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */; }; + D625299A2B15E4B9002A372F /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */; }; + D625299B2B15E4B9002A372F /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; @@ -2368,7 +2381,20 @@ CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; D62529662B154637002A372F /* PrivacyPro */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PrivacyPro; sourceTree = ""; }; D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; - D625296F2B154A2D002A372F /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = ""; }; + D62529742B155BCB002A372F /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; + D625297D2B15E449002A372F /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D625297E2B15E449002A372F /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D62529802B15E449002A372F /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; + D62529812B15E449002A372F /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; + D62529832B15E449002A372F /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; + D62529842B15E449002A372F /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; + D62529852B15E449002A372F /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + D62529872B15E449002A372F /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; + D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; + D62529932B15E454002A372F /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "URL+Subscription.swift"; path = "PrivacyPro/URL+Subscription.swift"; sourceTree = ""; }; + D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; + D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; + D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; @@ -2594,7 +2620,6 @@ F42D541D29DCA40B004C4FF1 /* DesignResourcesKit in Frameworks */, 85875B6129912A9900115F05 /* SyncUI in Frameworks */, F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, - D62529732B15563A002A372F /* Subscription in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3353,7 +3378,6 @@ 31E69A60280F4BAD00478327 /* LocalPackages */ = { isa = PBXGroup; children = ( - D625296F2B154A2D002A372F /* Subscription */, 85875B5F29912A2D00115F05 /* SyncUI */, 37FCAACB2993149A000E420A /* Waitlist */, 31794BFF2821DFB600F18633 /* DuckUI */, @@ -3694,6 +3718,7 @@ F1AE54DB1F0425BB00D9A700 /* Privacy */, 1E87615728A1515400C7C5CE /* PrivacyDashboard */, D625296A2B15465E002A372F /* PrivacyPro */, + D62529932B15E454002A372F /* URL+Subscription.swift */, 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */, C1B7B51D28941F160098FD6A /* RemoteMessaging */, F1AB2B401E3F75A000868554 /* Settings */, @@ -4442,7 +4467,11 @@ D625296A2B15465E002A372F /* PrivacyPro */ = { isa = PBXGroup; children = ( + D62529952B15E4B9002A372F /* PurchaseFlows */, D625296C2B15468B002A372F /* UserScripts */, + D625297C2B15E449002A372F /* Account */, + D62529862B15E449002A372F /* Purchase */, + D62529882B15E449002A372F /* UserScripts */, D625296B2B15467E002A372F /* Views */, ); path = PrivacyPro; @@ -4451,6 +4480,7 @@ D625296B2B15467E002A372F /* Views */ = { isa = PBXGroup; children = ( + D62529742B155BCB002A372F /* SubscriptionFlowView.swift */, ); path = Views; sourceTree = ""; @@ -4463,6 +4493,62 @@ path = UserScripts; sourceTree = ""; }; + D625297C2B15E449002A372F /* Account */ = { + isa = PBXGroup; + children = ( + D625297D2B15E449002A372F /* Logging.swift */, + D625297E2B15E449002A372F /* AccountManager.swift */, + D625297F2B15E449002A372F /* AccountStorage */, + D62529822B15E449002A372F /* Services */, + ); + path = Account; + sourceTree = ""; + }; + D625297F2B15E449002A372F /* AccountStorage */ = { + isa = PBXGroup; + children = ( + D62529802B15E449002A372F /* AccountKeychainStorage.swift */, + D62529812B15E449002A372F /* AccountStorage.swift */, + ); + path = AccountStorage; + sourceTree = ""; + }; + D62529822B15E449002A372F /* Services */ = { + isa = PBXGroup; + children = ( + D62529832B15E449002A372F /* SubscriptionService.swift */, + D62529842B15E449002A372F /* APIService.swift */, + D62529852B15E449002A372F /* AuthService.swift */, + ); + path = Services; + sourceTree = ""; + }; + D62529862B15E449002A372F /* Purchase */ = { + isa = PBXGroup; + children = ( + D62529872B15E449002A372F /* PurchaseManager.swift */, + ); + path = Purchase; + sourceTree = ""; + }; + D62529882B15E449002A372F /* UserScripts */ = { + isa = PBXGroup; + children = ( + D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */, + ); + path = UserScripts; + sourceTree = ""; + }; + D62529952B15E4B9002A372F /* PurchaseFlows */ = { + isa = PBXGroup; + children = ( + D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */, + D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */, + D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */, + ); + path = PurchaseFlows; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5487,7 +5573,6 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, - D62529722B15563A002A372F /* Subscription */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -6302,6 +6387,7 @@ 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, 854A01332A558B3A00FCC628 /* UIView+Constraints.swift in Sources */, + D625298D2B15E44A002A372F /* AccountStorage.swift in Sources */, C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */, 83134D7D20E2D725006CE65D /* FeedbackSender.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, @@ -6314,6 +6400,7 @@ EE458D0D2AB1DA4600FC651A /* EventMapping+NetworkProtectionError.swift in Sources */, 85047C752A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift in Sources */, F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */, + D62529942B15E454002A372F /* URL+Subscription.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, @@ -6336,6 +6423,7 @@ 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, + D62529992B15E4B9002A372F /* AppStorePurchaseFlow.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, @@ -6349,6 +6437,7 @@ 8586A10D24CBA7070049720E /* FindInPageActivity.swift in Sources */, 1E1626072968413B0004127F /* ViewExtension.swift in Sources */, 31A42566285A0A6300049386 /* FaviconViewModel.swift in Sources */, + D625298B2B15E44A002A372F /* AccountManager.swift in Sources */, 8C4838B5221C8F7F008A6739 /* GestureToolbarButton.swift in Sources */, EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */, 310ECFDD282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift in Sources */, @@ -6433,6 +6522,7 @@ F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */, 85449EF523FDA02800512AAF /* KeyboardSettingsViewController.swift in Sources */, 85C11E4C2090888C00BFFEB4 /* HomeRowReminder.swift in Sources */, + D625298E2B15E44A002A372F /* SubscriptionService.swift in Sources */, 31B2F11F287846320040427A /* NoMicPermissionAlert.swift in Sources */, 310C4B45281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift in Sources */, 1EFDCBC127D2393C00916BC5 /* DownloadsDeleteHelper.swift in Sources */, @@ -6452,12 +6542,14 @@ EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, + D625298A2B15E44A002A372F /* Logging.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */, 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */, 8C4724502217A14B004C9B2D /* TabViewControllerLongPressBookmarkExtension.swift in Sources */, 1EDE39D22705D4A200C99C72 /* FileSizeDebugViewController.swift in Sources */, + D625299B2B15E4B9002A372F /* AppStoreAccountManagementFlow.swift in Sources */, 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, @@ -6500,6 +6592,7 @@ F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */, + D62529922B15E44A002A372F /* SubscriptionPagesUserScript.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, @@ -6512,6 +6605,7 @@ 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, 312E5746283BB04A00C18FA0 /* AutofillEmptySearchView.swift in Sources */, + D62529902B15E44A002A372F /* AuthService.swift in Sources */, F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */, 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */, 0290472829E861BE0008FE3C /* AppTPTrackerDetailViewModel.swift in Sources */, @@ -6522,6 +6616,7 @@ 85449EFD23FDA71F00512AAF /* KeyboardSettings.swift in Sources */, 980891A222369ADB00313A70 /* FeedbackUserText.swift in Sources */, 4BCD14692B05BDD5000B1E4C /* AppDelegate+Waitlists.swift in Sources */, + D62529912B15E44A002A372F /* PurchaseManager.swift in Sources */, 988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */, 850ABD012AC3961100A733DF /* MainViewController+Segues.swift in Sources */, 9817C9C321EF594700884F65 /* AutoClear.swift in Sources */, @@ -6594,10 +6689,12 @@ 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, + D625299A2B15E4B9002A372F /* AppStoreRestoreFlow.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, 314C92B827C3DD660042EC96 /* QuickLookPreviewView.swift in Sources */, F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */, 020108AE29A7F91600644F9D /* AppTPTrackerCell.swift in Sources */, + D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */, 983D71B12A286E810072E26D /* SyncDebugViewController.swift in Sources */, F103073B1E7C91330059FEC7 /* BookmarksDataSource.swift in Sources */, EE0153E62A6FE106002A8B26 /* NetworkProtectionRootViewModel.swift in Sources */, @@ -6648,6 +6745,7 @@ 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, C1B0F6422AB08BE9001EAF05 /* MockPrivacyConfiguration.swift in Sources */, 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, + D625298F2B15E44A002A372F /* APIService.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, F17669D71E43401C003D3222 /* MainViewController.swift in Sources */, 984D60B2222A1284003B9E3B /* FeedbackFormViewController.swift in Sources */, @@ -6661,6 +6759,7 @@ 3132FA2827A0788400DD7A12 /* PassKitPreviewHelper.swift in Sources */, 8505836C219F424500ED4EDB /* TextFieldWithInsets.swift in Sources */, CBD4F13F279EBFAF00B20FD7 /* HomeMessageViewModel.swift in Sources */, + D625298C2B15E44A002A372F /* AccountKeychainStorage.swift in Sources */, 1E162613296C62820004127F /* CookieConsentDaxDialogViewModel.swift in Sources */, 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */, 2DC3FC65C6D9DA634426672D /* AutofillNoAuthAvailableView.swift in Sources */, @@ -9363,10 +9462,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Configuration; }; - D62529722B15563A002A372F /* Subscription */ = { - isa = XCSwiftPackageProductDependency; - productName = Subscription; - }; EE8E56892A56BCE400F11DCA /* NetworkProtection */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/AccountManager.swift b/DuckDuckGo/PrivacyPro/Account/AccountManager.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/AccountManager.swift rename to DuckDuckGo/PrivacyPro/Account/AccountManager.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountKeychainStorage.swift b/DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountKeychainStorage.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountKeychainStorage.swift rename to DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountKeychainStorage.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountStorage.swift b/DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountStorage.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/AccountStorage/AccountStorage.swift rename to DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountStorage.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/Logging.swift b/DuckDuckGo/PrivacyPro/Account/Logging.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/Logging.swift rename to DuckDuckGo/PrivacyPro/Account/Logging.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/Services/APIService.swift b/DuckDuckGo/PrivacyPro/Account/Services/APIService.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/Services/APIService.swift rename to DuckDuckGo/PrivacyPro/Account/Services/APIService.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/Services/AuthService.swift b/DuckDuckGo/PrivacyPro/Account/Services/AuthService.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/Services/AuthService.swift rename to DuckDuckGo/PrivacyPro/Account/Services/AuthService.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Account/Services/SubscriptionService.swift b/DuckDuckGo/PrivacyPro/Account/Services/SubscriptionService.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Account/Services/SubscriptionService.swift rename to DuckDuckGo/PrivacyPro/Account/Services/SubscriptionService.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/Purchase/PurchaseManager.swift b/DuckDuckGo/PrivacyPro/Purchase/PurchaseManager.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/Purchase/PurchaseManager.swift rename to DuckDuckGo/PrivacyPro/Purchase/PurchaseManager.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreAccountManagementFlow.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreAccountManagementFlow.swift rename to DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreAccountManagementFlow.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStorePurchaseFlow.swift rename to DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift diff --git a/LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift similarity index 100% rename from LocalPackages/Subscription/Sources/Subscription/PurchaseFlows/AppStoreRestoreFlow.swift rename to DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift diff --git a/DuckDuckGo/PrivacyPro/URL+Subscription.swift b/DuckDuckGo/PrivacyPro/URL+Subscription.swift new file mode 100644 index 0000000000..dbb018b53e --- /dev/null +++ b/DuckDuckGo/PrivacyPro/URL+Subscription.swift @@ -0,0 +1,44 @@ +// +// URL+Subscription.swift +// +// 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 + +public extension URL { + + static var purchaseSubscription: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/welcome")! + } + + static var subscriptionFAQ: URL { + URL(string: "https://duckduckgo.com/about")! + } + + + // MARK: - Subscription Email + static var activateSubscriptionViaEmail: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/activate")! + } + + static var addEmailToSubscription: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/add-email")! + } + + static var manageSubscriptionEmail: URL { + URL(string: "https://abrown.duckduckgo.com/subscriptions/manage")! + } +} diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift index 5d186734d2..2144cb6680 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift @@ -25,11 +25,6 @@ import Combine import Foundation import WebKit import UserScript -import Subscription - -public extension Notification.Name { - static let subscriptionPageCloseAndOpenPreferences = Notification.Name("com.duckduckgo.subscriptionPage.CloseAndOpenPreferences") -} /// /// The user script that will be the broker for all subscription features @@ -117,7 +112,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - var authToken = AccountManager().authToken ?? "" + var authToken = AccountManager().authToken ?? "Test Token" return Subscription(token: authToken) } @@ -133,11 +128,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { await AccountManager().refreshAccountData() - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .subscriptionPageCloseAndOpenPreferences, object: self) - } - return nil } diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift new file mode 100644 index 0000000000..36aea2d226 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -0,0 +1,91 @@ +// +// SubscriptionFlowView.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 SwiftUI +import WebKit +import BrowserServicesKit + +struct SubscriptionWebView: UIViewRepresentable { + let userScript: WKUserScript + // let context: String + let url: URL + + func makeUIView(context: Context) -> WKWebView { + let webView = WKWebView() + + let userContentController = WKUserContentController() + userContentController.addUserScript(userScript) + + userContentController.add(context.coordinator, name: SubscriptionPagesUserScript.context) + + let configuration = webView.configuration + configuration.userContentController = userContentController + + webView.load(URLRequest(url: url)) + + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) { + // Implement if needed to handle updates + } + + func makeCoordinator() -> Coordinator { + Coordinator() + } + + class Coordinator: NSObject, WKScriptMessageHandler { + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + // Handle the script message + } + } +} + +struct AsyncSubscriptionWebView: View { + @State private var userScript: WKUserScript? + + let url: URL + let script: SubscriptionPagesUserScript + + var body: some View { + GeometryReader { geometry in + if let script = userScript { + SubscriptionWebView(userScript: script, url: url) + .frame(width: geometry.size.width, height: geometry.size.height) + } + } + .onAppear(perform: loadUserScript) + } + + private func loadUserScript() { + Task { + userScript = await script.makeWKUserScript().wkUserScript + } + } +} + +struct SubscriptionFlowView: View { + let userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript() + + var body: some View { + // Assuming URL.purchaseSubscription is a valid URL for subscription + AsyncSubscriptionWebView(url: URL(string: "https://example.com/subscription")!, script: userScript) + } +} diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index c6e9334ff3..4f99d48b82 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -140,7 +140,7 @@ class SettingsViewController: UITableViewController { }() private lazy var shouldShowPrivacyPro: Bool = { -#if PRIVACYPRO +#if SUBSCRIPTION if #available(iOS 15, *) { // return featureFlagger.isFeatureOn(.privacyPro) return true @@ -374,8 +374,10 @@ class SettingsViewController: UITableViewController { } private func configurePrivacyPro() { + // Fetch the status of the subscription and decide what needs to be shown } + private func configureNetPCell() { netPCell.isHidden = !shouldShowNetPCell #if NETWORK_PROTECTION @@ -481,10 +483,12 @@ class SettingsViewController: UITableViewController { } #endif -#if PRIVACYPRO +#if SUBSCRIPTION @available(iOS 15, *) private func showPrivacyPro() { - + let privacyView = SubscriptionFlowView() + let hostingController = UIHostingController(rootView: privacyView) + navigationController?.pushViewController(hostingController, animated: true) } #endif @@ -534,7 +538,7 @@ class SettingsViewController: UITableViewController { case privacyProSignupCell: if #available(iOS 15, *) { -#if PRIVACYPRO +#if SUBSCRIPTION showPrivacyPro() #else break From 1a33d17bb47455f862024906656c4ec8f0047bd9 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 29 Nov 2023 00:56:47 +0100 Subject: [PATCH 51/99] Properly configure userscripts --- DuckDuckGo.xcodeproj/project.pbxproj | 24 ++++++- .../UserScripts/SimpleUserScript.swift | 34 +++++++++ .../SubscriptionPagesUserScript.swift | 5 +- .../UserScripts/TestUserScript.swift | 36 ++++++++++ .../ViewModel/SubscriptionFlowViewModel.swift | 32 +++++++++ .../PrivacyPro/Views/HeadlessWebView.swift | 64 +++++++++++++++++ .../Views/SubscriptionFlowView.swift | 70 ++----------------- .../WKUserContentController+Handler.swift | 45 ++++++++++++ DuckDuckGo/SettingsViewController.swift | 4 +- 9 files changed, 244 insertions(+), 70 deletions(-) create mode 100644 DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift create mode 100644 DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift create mode 100644 DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift create mode 100644 DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift create mode 100644 DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5818650adf..992c391ba4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -782,6 +782,9 @@ D625299A2B15E4B9002A372F /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */; }; D625299B2B15E4B9002A372F /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; + D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; + D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; + D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2391,11 +2394,14 @@ D62529852B15E449002A372F /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; D62529872B15E449002A372F /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; - D62529932B15E454002A372F /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "URL+Subscription.swift"; path = "PrivacyPro/URL+Subscription.swift"; sourceTree = ""; }; + D62529932B15E454002A372F /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; + D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; + D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; + D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -3718,7 +3724,6 @@ F1AE54DB1F0425BB00D9A700 /* Privacy */, 1E87615728A1515400C7C5CE /* PrivacyDashboard */, D625296A2B15465E002A372F /* PrivacyPro */, - D62529932B15E454002A372F /* URL+Subscription.swift */, 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */, C1B7B51D28941F160098FD6A /* RemoteMessaging */, F1AB2B401E3F75A000868554 /* Settings */, @@ -4467,12 +4472,15 @@ D625296A2B15465E002A372F /* PrivacyPro */ = { isa = PBXGroup; children = ( + D62529932B15E454002A372F /* URL+Subscription.swift */, D62529952B15E4B9002A372F /* PurchaseFlows */, + D6BBC4632B1665270027507E /* ViewModel */, D625296C2B15468B002A372F /* UserScripts */, D625297C2B15E449002A372F /* Account */, D62529862B15E449002A372F /* Purchase */, D62529882B15E449002A372F /* UserScripts */, D625296B2B15467E002A372F /* Views */, + D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */, ); path = PrivacyPro; sourceTree = ""; @@ -4481,6 +4489,7 @@ isa = PBXGroup; children = ( D62529742B155BCB002A372F /* SubscriptionFlowView.swift */, + D6BBC4612B1663190027507E /* HeadlessWebView.swift */, ); path = Views; sourceTree = ""; @@ -4549,6 +4558,14 @@ path = PurchaseFlows; sourceTree = ""; }; + D6BBC4632B1665270027507E /* ViewModel */ = { + isa = PBXGroup; + children = ( + D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -6483,6 +6500,7 @@ C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */, 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, + D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, @@ -6612,6 +6630,7 @@ 311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */, C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */, 316931D727BD10BB0095F5ED /* SaveToDownloadsAlert.swift in Sources */, + D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */, 31C70B5B2804C61000FB6AD1 /* SaveAutofillLoginManager.swift in Sources */, 85449EFD23FDA71F00512AAF /* KeyboardSettings.swift in Sources */, 980891A222369ADB00313A70 /* FeedbackUserText.swift in Sources */, @@ -6644,6 +6663,7 @@ F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */, 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */, + D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */, 1EEF387D285B1A1100383393 /* TrackerImageCache.swift in Sources */, 3151F0EC27357FEE00226F58 /* VoiceSearchFeedbackViewModel.swift in Sources */, 85010502292FB1000033978F /* FireproofFaviconUpdater.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift new file mode 100644 index 0000000000..bc5220aad8 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift @@ -0,0 +1,34 @@ +// +// SimpleUserScript.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 WebKit + +public final class SimpleUserScript: NSObject, WKScriptMessageHandler { + public var source: String { + return "" // Empty since the test script is in the HTML content + } + + public let context = "subscriptionPages" + + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + if message.name == context { + print("Message received from web content: \(message.body)") + } + } +} diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift index 2144cb6680..6dbaab10c6 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift @@ -35,7 +35,7 @@ public final class SubscriptionPagesUserScript: NSObject, UserScript, UserScript public static let context = "subscriptionPages" // special pages messaging cannot be isolated as we'll want regular page-scripts to be able to communicate - public let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: true ) + public let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: false ) public let messageNames: [String] = [ SubscriptionPagesUserScript.context @@ -61,7 +61,6 @@ extension SubscriptionPagesUserScript: WKScriptMessageHandlerWithReply { } } -// MARK: - Fallback for macOS 10.15 extension SubscriptionPagesUserScript: WKScriptMessageHandler { public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // unsupported @@ -112,7 +111,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - var authToken = AccountManager().authToken ?? "Test Token" + var authToken = AccountManager().authToken ?? "" return Subscription(token: authToken) } diff --git a/DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift new file mode 100644 index 0000000000..4d5433ed13 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift @@ -0,0 +1,36 @@ +// +// TestUserScript.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 WebKit + +public final class TestUserScript: NSObject, WKScriptMessageHandler { + public var source: String { + // Here, you can inject any JavaScript if needed + return "" + } + + public let context = "simpleContext" + + // Implement WKScriptMessageHandler method + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + if message.name == context { + print("Message received from web content: \(message.body)") + } + } +} diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift new file mode 100644 index 0000000000..dfbc8a1187 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -0,0 +1,32 @@ +// +// SubscriptionFlowViewModel.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 UserScript + +struct SubscriptionFlowViewModel { + let userScript: SubscriptionPagesUserScript + let subFeature: Subfeature + + init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), + subFeature: Subfeature = SubscriptionPagesUseSubscriptionFeature()) { + self.userScript = userScript + self.subFeature = subFeature + } +} diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift new file mode 100644 index 0000000000..87604aeaf0 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -0,0 +1,64 @@ +// +// HeadlessWebView.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 WebKit +import UserScript +import SwiftUI + +struct HeadlessWebview: UIViewRepresentable { + let userScript: UserScriptMessaging + let subFeature: Subfeature + let url: URL + + func makeUIView(context: Context) -> WKWebView { + let userContentController = WKUserContentController() + userContentController.addUserScript(userScript.makeWKUserScriptSync()) + userContentController.addHandler(userScript) + userScript.registerSubfeature(delegate: subFeature) + + let configuration = WKWebViewConfiguration() + configuration.userContentController = userContentController + + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.load(URLRequest(url: url)) + + if #available(iOS 16.4, *) { + webView.isInspectable = true + } + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) {} +} + +struct AsyncHeadlessWebView: View { + let url: URL + let userScript: UserScriptMessaging + let subFeature: Subfeature + + var body: some View { + GeometryReader { geometry in + HeadlessWebview(userScript: userScript, + subFeature: subFeature, + url: url) + .frame(width: geometry.size.width, height: geometry.size.height) + } + } +} diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index 36aea2d226..532fa5bccc 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -17,75 +17,19 @@ // limitations under the License. // -import Foundation import SwiftUI -import WebKit -import BrowserServicesKit -struct SubscriptionWebView: UIViewRepresentable { - let userScript: WKUserScript - // let context: String - let url: URL - - func makeUIView(context: Context) -> WKWebView { - let webView = WKWebView() - - let userContentController = WKUserContentController() - userContentController.addUserScript(userScript) - - userContentController.add(context.coordinator, name: SubscriptionPagesUserScript.context) - - let configuration = webView.configuration - configuration.userContentController = userContentController - - webView.load(URLRequest(url: url)) - - return webView - } - - func updateUIView(_ uiView: WKWebView, context: Context) { - // Implement if needed to handle updates - } - - func makeCoordinator() -> Coordinator { - Coordinator() - } - - class Coordinator: NSObject, WKScriptMessageHandler { - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - // Handle the script message - } - } -} - -struct AsyncSubscriptionWebView: View { - @State private var userScript: WKUserScript? - - let url: URL - let script: SubscriptionPagesUserScript +struct SubscriptionFlowView: View { - var body: some View { - GeometryReader { geometry in - if let script = userScript { - SubscriptionWebView(userScript: script, url: url) - .frame(width: geometry.size.width, height: geometry.size.height) - } - } - .onAppear(perform: loadUserScript) - } + let model: SubscriptionFlowViewModel - private func loadUserScript() { - Task { - userScript = await script.makeWKUserScript().wkUserScript - } + init(model: SubscriptionFlowViewModel = SubscriptionFlowViewModel()) { + self.model = model } -} - -struct SubscriptionFlowView: View { - let userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript() var body: some View { - // Assuming URL.purchaseSubscription is a valid URL for subscription - AsyncSubscriptionWebView(url: URL(string: "https://example.com/subscription")!, script: userScript) + AsyncHeadlessWebView(url: URL.purchaseSubscription, + userScript: model.userScript, + subFeature: model.subFeature) } } diff --git a/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift b/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift new file mode 100644 index 0000000000..b156804a4e --- /dev/null +++ b/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift @@ -0,0 +1,45 @@ +// +// WKUserContentController+Handler.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 WebKit +import UserScript + +@MainActor +extension WKUserContentController { + + func addHandler(_ userScript: UserScript) { + for messageName in userScript.messageNames { + let contentWorld: WKContentWorld = userScript.getContentWorld() + if let handlerWithReply = userScript as? WKScriptMessageHandlerWithReply { + addScriptMessageHandler(handlerWithReply, contentWorld: contentWorld, name: messageName) + } else { + add(userScript, contentWorld: contentWorld, name: messageName) + } + } + } + + func removeHandler(_ userScript: UserScript) { + userScript.messageNames.forEach { + let contentWorld: WKContentWorld = userScript.getContentWorld() + removeScriptMessageHandler(forName: $0, contentWorld: contentWorld) + } + } + +} diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 4f99d48b82..ae831652ae 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -486,8 +486,8 @@ class SettingsViewController: UITableViewController { #if SUBSCRIPTION @available(iOS 15, *) private func showPrivacyPro() { - let privacyView = SubscriptionFlowView() - let hostingController = UIHostingController(rootView: privacyView) + let privacyProview = SubscriptionFlowView() + let hostingController = UIHostingController(rootView: privacyProview) navigationController?.pushViewController(hostingController, animated: true) } #endif From 61830e810b2ed980c91f19ff16dcf672e1f5d092 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 29 Nov 2023 01:06:19 +0100 Subject: [PATCH 52/99] Confirm mesages are being properly received --- DuckDuckGo.xcodeproj/project.pbxproj | 26 +-- ...scriptionPagesUseSubscriptionFeature.swift | 210 ++++++++++++++++++ .../SubscriptionPagesUserScript.swift | 185 --------------- .../WKUserContentController+Handler.swift | 2 + 4 files changed, 225 insertions(+), 198 deletions(-) create mode 100644 DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 992c391ba4..d9b4dcbf30 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -766,7 +766,6 @@ CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; D62529672B154637002A372F /* PrivacyPro in Resources */ = {isa = PBXBuildFile; fileRef = D62529662B154637002A372F /* PrivacyPro */; }; - D625296E2B1547C3002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */; }; D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529742B155BCB002A372F /* SubscriptionFlowView.swift */; }; D625298A2B15E44A002A372F /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625297D2B15E449002A372F /* Logging.swift */; }; D625298B2B15E44A002A372F /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625297E2B15E449002A372F /* AccountManager.swift */; }; @@ -785,6 +784,7 @@ D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */; }; + D6BBC4692B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2383,7 +2383,6 @@ CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; D62529662B154637002A372F /* PrivacyPro */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PrivacyPro; sourceTree = ""; }; - D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; D62529742B155BCB002A372F /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; D625297D2B15E449002A372F /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; D625297E2B15E449002A372F /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; @@ -2402,6 +2401,7 @@ D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; + D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4472,15 +4472,15 @@ D625296A2B15465E002A372F /* PrivacyPro */ = { isa = PBXGroup; children = ( + D6BBC46A2B16B68C0027507E /* Extensions */, D62529932B15E454002A372F /* URL+Subscription.swift */, + D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */, D62529952B15E4B9002A372F /* PurchaseFlows */, D6BBC4632B1665270027507E /* ViewModel */, - D625296C2B15468B002A372F /* UserScripts */, D625297C2B15E449002A372F /* Account */, D62529862B15E449002A372F /* Purchase */, D62529882B15E449002A372F /* UserScripts */, D625296B2B15467E002A372F /* Views */, - D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */, ); path = PrivacyPro; sourceTree = ""; @@ -4494,14 +4494,6 @@ path = Views; sourceTree = ""; }; - D625296C2B15468B002A372F /* UserScripts */ = { - isa = PBXGroup; - children = ( - D625296D2B1547C3002A372F /* SubscriptionPagesUserScript.swift */, - ); - path = UserScripts; - sourceTree = ""; - }; D625297C2B15E449002A372F /* Account */ = { isa = PBXGroup; children = ( @@ -4544,6 +4536,7 @@ isa = PBXGroup; children = ( D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */, + D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */, ); path = UserScripts; sourceTree = ""; @@ -4566,6 +4559,13 @@ path = ViewModel; sourceTree = ""; }; + D6BBC46A2B16B68C0027507E /* Extensions */ = { + isa = PBXGroup; + children = ( + ); + path = Extensions; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -6434,6 +6434,7 @@ CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, 85F200002215C17B006BB258 /* FindInPage.swift in Sources */, F1386BA41E6846C40062FC3C /* TabDelegate.swift in Sources */, + D6BBC4692B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, C1B924B72ACD6E6800EE7B06 /* AutofillNeverSavedTableViewCell.swift in Sources */, 020108A929A7C1CD00644F9D /* AppTrackerImageCache.swift in Sources */, 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */, @@ -6496,7 +6497,6 @@ 1E8AD1D127C000AB00ABA377 /* OngoingDownloadRow.swift in Sources */, 85058366219AE9EA00ED4EDB /* HomePageConfiguration.swift in Sources */, EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */, - D625296E2B1547C3002A372F /* SubscriptionPagesUserScript.swift in Sources */, C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */, 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift new file mode 100644 index 0000000000..6e5ac378dc --- /dev/null +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -0,0 +1,210 @@ +// +// SubscriptionPagesUseSubscriptionFeature.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. +// + +#if SUBSCRIPTION + +import BrowserServicesKit +import Common +import Foundation +import WebKit +import UserScript + +final class SubscriptionPagesUseSubscriptionFeature: Subfeature { + var broker: UserScriptMessageBroker? + + var featureName = "useSubscription" + + var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ + .exact(hostname: "duckduckgo.com"), + .exact(hostname: "abrown.duckduckgo.com") + ]) + + func with(broker: UserScriptMessageBroker) { + self.broker = broker + } + + func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + switch methodName { + case "getSubscription": return getSubscription + case "setSubscription": return setSubscription + case "backToSettings": return backToSettings + case "getSubscriptionOptions": return getSubscriptionOptions + case "subscriptionSelected": return subscriptionSelected + case "activateSubscription": return activateSubscription + case "featureSelected": return featureSelected + default: + return nil + } + } + + struct Subscription: Encodable { + let token: String + } + + /// Values that the Frontend can use to determine the current state. + struct SubscriptionValues: Codable { + enum CodingKeys: String, CodingKey { + case token + } + let token: String + } + + func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + var authToken = AccountManager().authToken ?? "" + return Subscription(token: authToken) + } + + func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + return nil + } + + await AccountManager().exchangeAndStoreTokens(with: subscriptionValues.token) + return nil + } + + func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + await AccountManager().refreshAccountData() + return nil + } + + func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct SubscriptionOptions: Encodable { + let platform: String + let options: [SubscriptionOption] + let features: [SubscriptionFeature] + } + + struct SubscriptionOption: Encodable { + let id: String + let cost: SubscriptionCost + + struct SubscriptionCost: Encodable { + let displayPrice: String + let recurrence: String + } + } + + enum SubscriptionFeatureName: String, CaseIterable { + case privateBrowsing = "private-browsing" + case privateSearch = "private-search" + case emailProtection = "email-protection" + case appTrackingProtection = "app-tracking-protection" + case vpn = "vpn" + case personalInformationRemoval = "personal-information-removal" + case identityTheftRestoration = "identity-theft-restoration" + } + + struct SubscriptionFeature: Encodable { + let name: String + } + + let subscriptionOptions: [SubscriptionOption] + + if #available(macOS 12.0, iOS 15, *) { + let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1month") }) + let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1year") }) + + guard let monthly, let yearly else { return nil } + + subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: "monthly")), + SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: "yearly"))] + } else { + return nil + } + + let message = SubscriptionOptions(platform: "macos", + options: subscriptionOptions, + features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) + + return message + } + + func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct SubscriptionSelection: Decodable { + let id: String + } + + let message = original + + if #available(macOS 12.0, iOS 15, *) { + guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + return nil + } + + print("Selected: \(subscriptionSelection.id)") + + let emailAccessToken = try? EmailManager().getToken() + + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + case .success: + break + case .failure(let error): + print("Purchase failed: \(error)") + return nil + } + + await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) + + DispatchQueue.main.async { + self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) + } + } + + return nil + } + + func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + print(">>> Selected to activate a subscription -- show the activation settings screen") + return nil + } + + func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + struct FeatureSelection: Codable { + let feature: String + } + + guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") + return nil + } + + print(">>> Selected a feature -- show the corresponding UI", featureSelection) + return nil + } + + enum SubscribeActionName: String { + case onPurchaseUpdate + } + + struct PurchaseUpdate: Codable { + let type: String + } + + func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { + let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: true ) + + print(">>> Pushing into WebView:", method.rawValue, String(describing: params)) + broker.push(method: method.rawValue, params: params, for: self, into: webView) + } +} + +#endif diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift index 6dbaab10c6..81633fa54c 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUserScript.swift @@ -67,189 +67,4 @@ extension SubscriptionPagesUserScript: WKScriptMessageHandler { } } -/// -/// Use Subscription sub-feature -/// -final class SubscriptionPagesUseSubscriptionFeature: Subfeature { - var broker: UserScriptMessageBroker? - - var featureName = "useSubscription" - - var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ - .exact(hostname: "duckduckgo.com"), - .exact(hostname: "abrown.duckduckgo.com") - ]) - - func with(broker: UserScriptMessageBroker) { - self.broker = broker - } - - func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - switch methodName { - case "getSubscription": return getSubscription - case "setSubscription": return setSubscription - case "backToSettings": return backToSettings - case "getSubscriptionOptions": return getSubscriptionOptions - case "subscriptionSelected": return subscriptionSelected - case "activateSubscription": return activateSubscription - case "featureSelected": return featureSelected - default: - return nil - } - } - - struct Subscription: Encodable { - let token: String - } - - /// Values that the Frontend can use to determine the current state. - struct SubscriptionValues: Codable { - enum CodingKeys: String, CodingKey { - case token - } - let token: String - } - - func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - var authToken = AccountManager().authToken ?? "" - return Subscription(token: authToken) - } - - func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") - return nil - } - - await AccountManager().exchangeAndStoreTokens(with: subscriptionValues.token) - return nil - } - - func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - await AccountManager().refreshAccountData() - return nil - } - - func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct SubscriptionOptions: Encodable { - let platform: String - let options: [SubscriptionOption] - let features: [SubscriptionFeature] - } - - struct SubscriptionOption: Encodable { - let id: String - let cost: SubscriptionCost - - struct SubscriptionCost: Encodable { - let displayPrice: String - let recurrence: String - } - } - - enum SubscriptionFeatureName: String, CaseIterable { - case privateBrowsing = "private-browsing" - case privateSearch = "private-search" - case emailProtection = "email-protection" - case appTrackingProtection = "app-tracking-protection" - case vpn = "vpn" - case personalInformationRemoval = "personal-information-removal" - case identityTheftRestoration = "identity-theft-restoration" - } - - struct SubscriptionFeature: Encodable { - let name: String - } - - let subscriptionOptions: [SubscriptionOption] - - if #available(macOS 12.0, iOS 15, *) { - let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1month") }) - let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1year") }) - - guard let monthly, let yearly else { return nil } - - subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: "monthly")), - SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: "yearly"))] - } else { - return nil - } - - let message = SubscriptionOptions(platform: "macos", - options: subscriptionOptions, - features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) - - return message - } - - func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct SubscriptionSelection: Decodable { - let id: String - } - - let message = original - - if #available(macOS 12.0, iOS 15, *) { - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") - return nil - } - - print("Selected: \(subscriptionSelection.id)") - - let emailAccessToken = try? EmailManager().getToken() - - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { - case .success: - break - case .failure(let error): - print("Purchase failed: \(error)") - return nil - } - - await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) - - DispatchQueue.main.async { - self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) - } - } - - return nil - } - - func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - print(">>> Selected to activate a subscription -- show the activation settings screen") - return nil - } - - func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct FeatureSelection: Codable { - let feature: String - } - - guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") - return nil - } - - print(">>> Selected a feature -- show the corresponding UI", featureSelection) - return nil - } - - enum SubscribeActionName: String { - case onPurchaseUpdate - } - - struct PurchaseUpdate: Codable { - let type: String - } - - func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { - let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: true ) - - print(">>> Pushing into WebView:", method.rawValue, String(describing: params)) - broker.push(method: method.rawValue, params: params, for: self, into: webView) - } -} - #endif diff --git a/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift b/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift index b156804a4e..9bfae41627 100644 --- a/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift +++ b/DuckDuckGo/PrivacyPro/WKUserContentController+Handler.swift @@ -17,6 +17,8 @@ // limitations under the License. // +// TODO: Move to BSK + import Foundation import WebKit import UserScript From 1762cf87c7c93f425d7cb72b4a1eb41ba870e42b Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 29 Nov 2023 19:24:47 +0100 Subject: [PATCH 53/99] Display Webview --- DuckDuckGo.xcodeproj/project.pbxproj | 22 ++- .../xcschemes/DuckDuckGo-Alpha.xcscheme | 3 + DuckDuckGo/AppDelegate.swift | 9 ++ .../{ => Extensions}/URL+Subscription.swift | 0 .../WKUserContentController+Handler.swift | 0 .../PrivacyPro/Models/Subscription.swift | 66 +++++++++ .../PrivacyPro/Purchase/PurchaseManager.swift | 11 +- .../UserScripts/SubscriptionOptions.swift | 18 +++ ...scriptionPagesUseSubscriptionFeature.swift | 119 +++++++--------- .../PrivacyPro/Views/HeadlessWebView.swift | 20 ++- PrivacyPro.storekit | 129 ++++++++++++++++++ 11 files changed, 320 insertions(+), 77 deletions(-) rename DuckDuckGo/PrivacyPro/{ => Extensions}/URL+Subscription.swift (100%) rename DuckDuckGo/PrivacyPro/{ => Extensions}/WKUserContentController+Handler.swift (100%) create mode 100644 DuckDuckGo/PrivacyPro/Models/Subscription.swift create mode 100644 DuckDuckGo/PrivacyPro/UserScripts/SubscriptionOptions.swift create mode 100644 PrivacyPro.storekit diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d9b4dcbf30..b9e8f78f5d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -785,6 +785,8 @@ D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */; }; D6BBC4692B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; + D6FC2BAF2B178FF800744491 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6FC2BAE2B178FF800744491 /* StoreKit.framework */; }; + D6FC2BB52B17AD6300744491 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FC2BB42B17AD6300744491 /* Subscription.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2402,6 +2404,9 @@ D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; + D6FC2BAE2B178FF800744491 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + D6FC2BB42B17AD6300744491 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; + D6FC2BB62B17B6DD00744491 /* PrivacyPro.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -2627,6 +2632,7 @@ 85875B6129912A9900115F05 /* SyncUI in Frameworks */, F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, + D6FC2BAF2B178FF800744491 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3649,6 +3655,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + D6FC2BB62B17B6DD00744491 /* PrivacyPro.storekit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -4472,9 +4479,8 @@ D625296A2B15465E002A372F /* PrivacyPro */ = { isa = PBXGroup; children = ( + D6FC2BB32B17AD5800744491 /* Models */, D6BBC46A2B16B68C0027507E /* Extensions */, - D62529932B15E454002A372F /* URL+Subscription.swift */, - D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */, D62529952B15E4B9002A372F /* PurchaseFlows */, D6BBC4632B1665270027507E /* ViewModel */, D625297C2B15E449002A372F /* Account */, @@ -4562,10 +4568,20 @@ D6BBC46A2B16B68C0027507E /* Extensions */ = { isa = PBXGroup; children = ( + D62529932B15E454002A372F /* URL+Subscription.swift */, + D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */, ); path = Extensions; sourceTree = ""; }; + D6FC2BB32B17AD5800744491 /* Models */ = { + isa = PBXGroup; + children = ( + D6FC2BB42B17AD6300744491 /* Subscription.swift */, + ); + path = Models; + sourceTree = ""; + }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5183,6 +5199,7 @@ F1AA545F1E48D90700223211 /* Frameworks */ = { isa = PBXGroup; children = ( + D6FC2BAE2B178FF800744491 /* StoreKit.framework */, F1AA54601E48D90700223211 /* NotificationCenter.framework */, 8512EA4E24ED30D20073EE19 /* WidgetKit.framework */, 8512EA5024ED30D20073EE19 /* SwiftUI.framework */, @@ -6369,6 +6386,7 @@ 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, + D6FC2BB52B17AD6300744491 /* Subscription.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme index ab41c0e9a0..2d1a0f8907 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme @@ -49,6 +49,9 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + Subfeature.Handler? { switch methodName { - case "getSubscription": return getSubscription - case "setSubscription": return setSubscription - case "backToSettings": return backToSettings - case "getSubscriptionOptions": return getSubscriptionOptions - case "subscriptionSelected": return subscriptionSelected - case "activateSubscription": return activateSubscription - case "featureSelected": return featureSelected + case Handlers.getSubscription: return getSubscription + case Handlers.setSubscription: return setSubscription + case Handlers.backToSettings: return backToSettings + case Handlers.getSubscriptionOptions: return getSubscriptionOptions + case Handlers.subscriptionSelected: return subscriptionSelected + case Handlers.activateSubscription: return activateSubscription + case Handlers.featureSelected: return featureSelected default: return nil } } - struct Subscription: Encodable { - let token: String - } - - /// Values that the Frontend can use to determine the current state. - struct SubscriptionValues: Codable { - enum CodingKeys: String, CodingKey { - case token - } - let token: String - } - func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - var authToken = AccountManager().authToken ?? "" + var authToken = AccountManager().authToken ?? Constants.empty return Subscription(token: authToken) } func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") - return nil - } - - await AccountManager().exchangeAndStoreTokens(with: subscriptionValues.token) + // WIP return nil } func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - await AccountManager().refreshAccountData() + // WIP return nil } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct SubscriptionOptions: Encodable { - let platform: String - let options: [SubscriptionOption] - let features: [SubscriptionFeature] - } - - struct SubscriptionOption: Encodable { - let id: String - let cost: SubscriptionCost - - struct SubscriptionCost: Encodable { - let displayPrice: String - let recurrence: String - } - } - - enum SubscriptionFeatureName: String, CaseIterable { - case privateBrowsing = "private-browsing" - case privateSearch = "private-search" - case emailProtection = "email-protection" - case appTrackingProtection = "app-tracking-protection" - case vpn = "vpn" - case personalInformationRemoval = "personal-information-removal" - case identityTheftRestoration = "identity-theft-restoration" - } - - struct SubscriptionFeature: Encodable { - let name: String - } let subscriptionOptions: [SubscriptionOption] - if #available(macOS 12.0, iOS 15, *) { - let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1month") }) - let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains("1year") }) + if #available(iOS 15, *) { + let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.monthly) }) + let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.yearly) }) guard let monthly, let yearly else { return nil } - subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: "monthly")), - SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: "yearly"))] + subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: RecurrenceOptions.month)), + SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: RecurrenceOptions.year))] } else { return nil } - let message = SubscriptionOptions(platform: "macos", + let message = SubscriptionOptions(platform: Constants.os, options: subscriptionOptions, features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) @@ -138,9 +124,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - struct SubscriptionSelection: Decodable { - let id: String - } let message = original diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index 87604aeaf0..16959c6f9e 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -22,7 +22,16 @@ import WebKit import UserScript import SwiftUI +class WebViewManager: ObservableObject { + @Published var shouldReload: Bool = false + + func reload() { + shouldReload = true + } +} + struct HeadlessWebview: UIViewRepresentable { + @ObservedObject var manager: WebViewManager let userScript: UserScriptMessaging let subFeature: Subfeature let url: URL @@ -45,17 +54,24 @@ struct HeadlessWebview: UIViewRepresentable { return webView } - func updateUIView(_ uiView: WKWebView, context: Context) {} + func updateUIView(_ uiView: WKWebView, context: Context) { + if manager.shouldReload { + uiView.reload() + manager.shouldReload = false + } + } } struct AsyncHeadlessWebView: View { let url: URL let userScript: UserScriptMessaging let subFeature: Subfeature + let webViewManager = WebViewManager() var body: some View { GeometryReader { geometry in - HeadlessWebview(userScript: userScript, + HeadlessWebview(manager: webViewManager, + userScript: userScript, subFeature: subFeature, url: url) .frame(width: geometry.size.width, height: geometry.size.height) diff --git a/PrivacyPro.storekit b/PrivacyPro.storekit new file mode 100644 index 0000000000..8c488fb4b4 --- /dev/null +++ b/PrivacyPro.storekit @@ -0,0 +1,129 @@ +{ + "identifier" : "5935A809", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + + ], + "settings" : { + "_applicationInternalID" : "6458101566", + "_developerTeamID" : "HKE973VLUW", + "_failTransactionsEnabled" : false, + "_lastSynchronizedDate" : 722974434.57783198, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + { + "id" : "21416043", + "localizations" : [ + + ], + "name" : "Premium Privacy Protection Subscription", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "9.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "6473453075", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Monthly Subscription", + "displayName" : "Monthly Subscription", + "locale" : "en_US" + } + ], + "productID" : "ios.subscription.1month", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Monthly Subscription", + "subscriptionGroupID" : "21416043", + "type" : "RecurringSubscription" + }, + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "99.99", + "familyShareable" : false, + "groupNumber" : 2, + "internalID" : "6473453289", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Yearly Subscription", + "displayName" : "Yearly Subscription", + "locale" : "en_US" + } + ], + "productID" : "ios.subscription.1year", + "recurringSubscriptionPeriod" : "P1Y", + "referenceName" : "Yearly Subscription", + "subscriptionGroupID" : "21416043", + "type" : "RecurringSubscription" + } + ] + } + ], + "version" : { + "major" : 3, + "minor" : 0 + } +} From cfa4241390452f6483a46a3c294e73ed9341c6bf Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 29 Nov 2023 21:49:24 +0100 Subject: [PATCH 54/99] Implement Settings cell in storyboard --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +++-- .../PrivacyPro/SubscriptionUserText.swift | 49 +++++++++++++++++++ .../ViewModel/SubscriptionFlowViewModel.swift | 2 + .../Views/SubscriptionFlowView.swift | 11 +++-- DuckDuckGo/SettingsViewController.swift | 13 +++-- DuckDuckGo/en.lproj/Localizable.strings | 3 ++ 6 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 DuckDuckGo/PrivacyPro/SubscriptionUserText.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b9e8f78f5d..53eee61116 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -781,6 +781,7 @@ D625299A2B15E4B9002A372F /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */; }; D625299B2B15E4B9002A372F /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; + D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */; }; D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */; }; @@ -2400,6 +2401,7 @@ D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; + D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; @@ -4480,13 +4482,14 @@ isa = PBXGroup; children = ( D6FC2BB32B17AD5800744491 /* Models */, - D6BBC46A2B16B68C0027507E /* Extensions */, - D62529952B15E4B9002A372F /* PurchaseFlows */, D6BBC4632B1665270027507E /* ViewModel */, + D625296B2B15467E002A372F /* Views */, D625297C2B15E449002A372F /* Account */, D62529862B15E449002A372F /* Purchase */, + D62529952B15E4B9002A372F /* PurchaseFlows */, D62529882B15E449002A372F /* UserScripts */, - D625296B2B15467E002A372F /* Views */, + D6BBC46A2B16B68C0027507E /* Extensions */, + D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */, ); path = PrivacyPro; sourceTree = ""; @@ -4494,8 +4497,8 @@ D625296B2B15467E002A372F /* Views */ = { isa = PBXGroup; children = ( - D62529742B155BCB002A372F /* SubscriptionFlowView.swift */, D6BBC4612B1663190027507E /* HeadlessWebView.swift */, + D62529742B155BCB002A372F /* SubscriptionFlowView.swift */, ); path = Views; sourceTree = ""; @@ -6691,6 +6694,7 @@ 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */, F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */, 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */, + D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */, 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift b/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift new file mode 100644 index 0000000000..f804e3c181 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift @@ -0,0 +1,49 @@ +// +// UserText.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 + +public struct SubscriptionUserText { + + public static let navigationTitle = NSLocalizedString("nagivation.title", value: "Privacy Pro", comment: "Navigation Bar Title for Feature") + + // Settings Strings + public static let settingsSectionTitle = NSLocalizedString("settings.sectionTitle", + value: "Privacy Pro", + comment: "Settings section title") + public static let subscribeTitle = NSLocalizedString("settings.subscribeTitle", + value: "Subscribe to Privacy Pro", + comment: "Settings title for unsubscribed users") + public static let subscribeSubtitle = NSLocalizedString("settings.subscribeSubTitle", + value: "More seamlessless privacy with three new protections, incluiding:", + comment: "Settings title for unsubscribed users") + public static let featureOneName = NSLocalizedString("settings.feature1", + value: "VPN (Virtual Private Network)", + comment: "Feature one title for unsubscribed users") + public static let featureTwoName = NSLocalizedString("settings.feature2", + value: "Personal Information Removal", + comment: "Feature two title for unsubscribed users") + public static let featureThreeName = NSLocalizedString("settings.feature3", + value: "Identity Theft Restoration", + comment: "Feature three title for unsubscribed users") + public static let learnMore = NSLocalizedString("settings.learnMore", + value: "Learn More", + comment: "Button title for Learn more") + +} diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index dfbc8a1187..95161008ec 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -23,6 +23,8 @@ import UserScript struct SubscriptionFlowViewModel { let userScript: SubscriptionPagesUserScript let subFeature: Subfeature + let purchaseURL = URL.purchaseSubscription + let viewTitle = SubscriptionUserText.navigationTitle init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: Subfeature = SubscriptionPagesUseSubscriptionFeature()) { diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index 532fa5bccc..37fe2975c6 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -21,15 +21,16 @@ import SwiftUI struct SubscriptionFlowView: View { - let model: SubscriptionFlowViewModel + let viewModel: SubscriptionFlowViewModel init(model: SubscriptionFlowViewModel = SubscriptionFlowViewModel()) { - self.model = model + self.viewModel = model } var body: some View { - AsyncHeadlessWebView(url: URL.purchaseSubscription, - userScript: model.userScript, - subFeature: model.subFeature) + AsyncHeadlessWebView(url: viewModel.purchaseURL, + userScript: viewModel.userScript, + subFeature: viewModel.subFeature) + .navigationTitle(viewModel.viewTitle) } } diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index ae831652ae..e259fa2ae2 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -74,6 +74,7 @@ class SettingsViewController: UITableViewController { @IBOutlet weak var voiceSearchCell: UITableViewCell! @IBOutlet weak var voiceSearchToggle: UISwitch! @IBOutlet weak var privacyProSignupCell: UITableViewCell! + @IBOutlet weak var privacyProLearnMoreCell: UITableViewCell! @IBOutlet var labels: [UILabel]! @IBOutlet var accessoryLabels: [UILabel]! @@ -142,8 +143,7 @@ class SettingsViewController: UITableViewController { private lazy var shouldShowPrivacyPro: Bool = { #if SUBSCRIPTION if #available(iOS 15, *) { - // return featureFlagger.isFeatureOn(.privacyPro) - return true + return featureFlagger.isFeatureOn(.privacyPro) } else { return false } @@ -374,7 +374,9 @@ class SettingsViewController: UITableViewController { } private func configurePrivacyPro() { - // Fetch the status of the subscription and decide what needs to be shown + privacyProSignupCell.accessoryType = .none + privacyProSignupCell.isUserInteractionEnabled = false + } @@ -534,15 +536,17 @@ class SettingsViewController: UITableViewController { #else break #endif + } - case privacyProSignupCell: + case privacyProLearnMoreCell: if #available(iOS 15, *) { #if SUBSCRIPTION showPrivacyPro() #else break #endif + } default: break @@ -632,6 +636,7 @@ class SettingsViewController: UITableViewController { } return super.tableView(tableView, titleForHeaderInSection: section) } + @IBAction func onVoiceSearchToggled(_ sender: UISwitch) { var enableVoiceSearch = sender.isOn diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 1bd0fb0559..c6a2c88e3f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1378,6 +1378,9 @@ https://duckduckgo.com/mac"; /* No comment provided by engineer. */ "menu.button.hint" = "Browsing Menu"; +/* Navigation Bar Title for Feature */ +"nagivation.title" = "Privacy Pro"; + /* Title for back button in navigation bar */ "navbar.back-button.title" = "Back"; From 8572ce8e135003a8726b1d7f664c5263097c8c42 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 29 Nov 2023 21:53:17 +0100 Subject: [PATCH 55/99] Fix constraints From 0184e8e18d7c34c26a29f6a10a9f5b3bb3cb7ce9 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 30 Nov 2023 00:20:15 +0100 Subject: [PATCH 56/99] Only load the WebApp when we have products Available --- DuckDuckGo/AppDelegate.swift | 9 ------- .../PrivacyPro/SubscriptionUserText.swift | 24 +------------------ .../ViewModel/SubscriptionFlowViewModel.swift | 17 ++++++++++++- .../PrivacyPro/Views/HeadlessWebView.swift | 21 +++------------- .../Views/SubscriptionFlowView.swift | 20 +++++++++------- DuckDuckGo/SettingsViewController.swift | 2 +- 6 files changed, 33 insertions(+), 60 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 65d75b789f..a7b87702f1 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -356,15 +356,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { widgetRefreshModel.beginObservingVPNStatus() NetworkProtectionAccessController().refreshNetworkProtectionAccess() #endif - - -#if SUBSCRIPTION - if #available(iOS 15.0, *) { - Task { - await PurchaseManager.shared.updateAvailableProducts() - } - } -#endif return true } diff --git a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift b/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift index f804e3c181..14e5ba02d3 100644 --- a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift +++ b/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift @@ -23,27 +23,5 @@ public struct SubscriptionUserText { public static let navigationTitle = NSLocalizedString("nagivation.title", value: "Privacy Pro", comment: "Navigation Bar Title for Feature") - // Settings Strings - public static let settingsSectionTitle = NSLocalizedString("settings.sectionTitle", - value: "Privacy Pro", - comment: "Settings section title") - public static let subscribeTitle = NSLocalizedString("settings.subscribeTitle", - value: "Subscribe to Privacy Pro", - comment: "Settings title for unsubscribed users") - public static let subscribeSubtitle = NSLocalizedString("settings.subscribeSubTitle", - value: "More seamlessless privacy with three new protections, incluiding:", - comment: "Settings title for unsubscribed users") - public static let featureOneName = NSLocalizedString("settings.feature1", - value: "VPN (Virtual Private Network)", - comment: "Feature one title for unsubscribed users") - public static let featureTwoName = NSLocalizedString("settings.feature2", - value: "Personal Information Removal", - comment: "Feature two title for unsubscribed users") - public static let featureThreeName = NSLocalizedString("settings.feature3", - value: "Identity Theft Restoration", - comment: "Feature three title for unsubscribed users") - public static let learnMore = NSLocalizedString("settings.learnMore", - value: "Learn More", - comment: "Button title for Learn more") - + } diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index 95161008ec..4c3f08977d 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -20,15 +20,30 @@ import Foundation import UserScript -struct SubscriptionFlowViewModel { +class SubscriptionFlowViewModel: ObservableObject { + let userScript: SubscriptionPagesUserScript let subFeature: Subfeature let purchaseURL = URL.purchaseSubscription let viewTitle = SubscriptionUserText.navigationTitle + + @Published var isLoadingProducts = true init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: Subfeature = SubscriptionPagesUseSubscriptionFeature()) { self.userScript = userScript self.subFeature = subFeature + Task { await updateStoreProducts() } + } + + // Fetch available products from Storekit + private func updateStoreProducts() async { + await PurchaseManager.shared.updateAvailableProducts() + await setProductsLoading(false) + } + + @MainActor + private func setProductsLoading(_ isLoading: Bool) { + self.isLoadingProducts = isLoading } } diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index 16959c6f9e..6c2604b2dc 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -21,17 +21,9 @@ import Foundation import WebKit import UserScript import SwiftUI - -class WebViewManager: ObservableObject { - @Published var shouldReload: Bool = false - - func reload() { - shouldReload = true - } -} +import DesignResourcesKit struct HeadlessWebview: UIViewRepresentable { - @ObservedObject var manager: WebViewManager let userScript: UserScriptMessaging let subFeature: Subfeature let url: URL @@ -54,24 +46,17 @@ struct HeadlessWebview: UIViewRepresentable { return webView } - func updateUIView(_ uiView: WKWebView, context: Context) { - if manager.shouldReload { - uiView.reload() - manager.shouldReload = false - } - } + func updateUIView(_ uiView: WKWebView, context: Context) {} } struct AsyncHeadlessWebView: View { let url: URL let userScript: UserScriptMessaging let subFeature: Subfeature - let webViewManager = WebViewManager() var body: some View { GeometryReader { geometry in - HeadlessWebview(manager: webViewManager, - userScript: userScript, + HeadlessWebview(userScript: userScript, subFeature: subFeature, url: url) .frame(width: geometry.size.width, height: geometry.size.height) diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index 37fe2975c6..a3092c130e 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -18,19 +18,23 @@ // import SwiftUI +import Foundation struct SubscriptionFlowView: View { - let viewModel: SubscriptionFlowViewModel - - init(model: SubscriptionFlowViewModel = SubscriptionFlowViewModel()) { - self.viewModel = model - } + @ObservedObject var viewModel: SubscriptionFlowViewModel var body: some View { - AsyncHeadlessWebView(url: viewModel.purchaseURL, - userScript: viewModel.userScript, - subFeature: viewModel.subFeature) + ZStack { + if !viewModel.isLoadingProducts { + AsyncHeadlessWebView(url: viewModel.purchaseURL, + userScript: viewModel.userScript, + subFeature: viewModel.subFeature).background() + } else { + SwiftUI.ProgressView() + } + } + .navigationTitle(viewModel.viewTitle) } } diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index e259fa2ae2..00cdd877d9 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -488,7 +488,7 @@ class SettingsViewController: UITableViewController { #if SUBSCRIPTION @available(iOS 15, *) private func showPrivacyPro() { - let privacyProview = SubscriptionFlowView() + let privacyProview = SubscriptionFlowView(viewModel: SubscriptionFlowViewModel()) let hostingController = UIHostingController(rootView: privacyProview) navigationController?.pushViewController(hostingController, animated: true) } From bd645429adac88aff3fe88d72ed21acd4fdcd10e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 30 Nov 2023 01:02:04 +0100 Subject: [PATCH 57/99] User PurchaseManager publishers --- ...scriptionPagesUseSubscriptionFeature.swift | 26 +++++++++---------- .../ViewModel/SubscriptionFlowViewModel.swift | 23 ++++++++++++---- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 49802c5efe..90feb04478 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -89,17 +89,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { var authToken = AccountManager().authToken ?? Constants.empty return Subscription(token: authToken) } - - func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - // WIP - return nil - } - - func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - // WIP - return nil - } - + func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { let subscriptionOptions: [SubscriptionOption] @@ -122,12 +112,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { return message } - + func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { let message = original - if #available(macOS 12.0, iOS 15, *) { + if #available(iOS 15, *) { guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") return nil @@ -155,6 +145,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { return nil } + func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + // WIP + return nil + } + + func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + // WIP + return nil + } + func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { print(">>> Selected to activate a subscription -- show the activation settings screen") return nil diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index 4c3f08977d..03f5f9f9fe 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -19,6 +19,7 @@ import Foundation import UserScript +import Combine class SubscriptionFlowViewModel: ObservableObject { @@ -26,20 +27,32 @@ class SubscriptionFlowViewModel: ObservableObject { let subFeature: Subfeature let purchaseURL = URL.purchaseSubscription let viewTitle = SubscriptionUserText.navigationTitle + let purchaseManager = PurchaseManager.shared @Published var isLoadingProducts = true + private var cancellables = Set() init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: Subfeature = SubscriptionPagesUseSubscriptionFeature()) { self.userScript = userScript self.subFeature = subFeature - Task { await updateStoreProducts() } + Task { await setupProductObserver() } } - // Fetch available products from Storekit - private func updateStoreProducts() async { - await PurchaseManager.shared.updateAvailableProducts() - await setProductsLoading(false) + // Fetch available Products from the AppStore + private func setupProductObserver() async { + purchaseManager.$availableProducts + .dropFirst() + .sink { [weak self] products in + guard let self = self else { return } + if !products.isEmpty { + Task { await self.setProductsLoading(false) } + } else { + assertionFailure("Could not load products from the App Store") + } + } + .store(in: &cancellables) + await purchaseManager.updateAvailableProducts() } @MainActor From f7621dc08ba81a92bfd4dcbd41e69d9730333794 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 30 Nov 2023 22:49:41 +0100 Subject: [PATCH 58/99] Base Purchase Flow --- DuckDuckGo.xcodeproj/project.pbxproj | 556 +++++++++++++++--- .../xcschemes/DuckDuckGo-Alpha.xcscheme | 11 +- .../PrivacyPro/PrivacyPro.storekit | 10 +- .../AppStoreAccountManagementFlow.swift | 2 +- .../PurchaseFlows/AppStorePurchaseFlow.swift | 54 +- .../PurchaseFlows/AppStoreRestoreFlow.swift | 26 +- .../PurchaseFlow.swift} | 52 +- .../AccountManager.swift | 1 + .../AccountKeychainStorage.swift | 0 .../AccountStorage/AccountStorage.swift | 0 .../{Account => Subscription}/Logging.swift | 0 .../PurchaseManager.swift | 33 +- .../Services/APIService.swift | 0 .../Services/AuthService.swift | 0 .../Services/SubscriptionService.swift | 17 + ...scriptionPagesUseSubscriptionFeature.swift | 19 +- .../ViewModel/SubscriptionFlowViewModel.swift | 12 +- .../PrivacyPro/Views/HeadlessWebView.swift | 8 +- .../Views/SubscriptionFlowView.swift | 4 +- LocalPackages/Subscription/Package.swift | 23 - .../AccountTests/AccountsTests.swift | 29 - .../PurchaseTests/PurchaseTests.swift | 29 - .../SubscriptionTests/SubscriptionTests.swift | 29 - 23 files changed, 658 insertions(+), 257 deletions(-) rename PrivacyPro.storekit => DuckDuckGo/PrivacyPro/PrivacyPro.storekit (94%) rename DuckDuckGo/PrivacyPro/{Models/Subscription.swift => PurchaseFlows/PurchaseFlow.swift} (62%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/AccountManager.swift (99%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/AccountStorage/AccountKeychainStorage.swift (100%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/AccountStorage/AccountStorage.swift (100%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/Logging.swift (100%) rename DuckDuckGo/PrivacyPro/{Purchase => Subscription}/PurchaseManager.swift (88%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/Services/APIService.swift (100%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/Services/AuthService.swift (100%) rename DuckDuckGo/PrivacyPro/{Account => Subscription}/Services/SubscriptionService.swift (76%) delete mode 100644 LocalPackages/Subscription/Package.swift delete mode 100644 LocalPackages/Subscription/Tests/SubscriptionTests/AccountTests/AccountsTests.swift delete mode 100644 LocalPackages/Subscription/Tests/SubscriptionTests/PurchaseTests/PurchaseTests.swift delete mode 100644 LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests/SubscriptionTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 53eee61116..15c2fbd02e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -767,27 +767,27 @@ CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; D62529672B154637002A372F /* PrivacyPro in Resources */ = {isa = PBXBuildFile; fileRef = D62529662B154637002A372F /* PrivacyPro */; }; D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529742B155BCB002A372F /* SubscriptionFlowView.swift */; }; - D625298A2B15E44A002A372F /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625297D2B15E449002A372F /* Logging.swift */; }; - D625298B2B15E44A002A372F /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625297E2B15E449002A372F /* AccountManager.swift */; }; - D625298C2B15E44A002A372F /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529802B15E449002A372F /* AccountKeychainStorage.swift */; }; - D625298D2B15E44A002A372F /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529812B15E449002A372F /* AccountStorage.swift */; }; - D625298E2B15E44A002A372F /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529832B15E449002A372F /* SubscriptionService.swift */; }; - D625298F2B15E44A002A372F /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529842B15E449002A372F /* APIService.swift */; }; - D62529902B15E44A002A372F /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529852B15E449002A372F /* AuthService.swift */; }; - D62529912B15E44A002A372F /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529872B15E449002A372F /* PurchaseManager.swift */; }; D62529922B15E44A002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */; }; D62529942B15E454002A372F /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529932B15E454002A372F /* URL+Subscription.swift */; }; - D62529992B15E4B9002A372F /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */; }; - D625299A2B15E4B9002A372F /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */; }; - D625299B2B15E4B9002A372F /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; + D637D1B62B19028E00C94784 /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B22B19028E00C94784 /* AppStoreAccountManagementFlow.swift */; }; + D637D1B72B19028E00C94784 /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B32B19028E00C94784 /* AppStoreRestoreFlow.swift */; }; + D637D1B82B19028E00C94784 /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B42B19028E00C94784 /* PurchaseFlow.swift */; }; + D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B52B19028E00C94784 /* AppStorePurchaseFlow.swift */; }; + D647EA3F2B1908E500BABD93 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA352B1908E500BABD93 /* Logging.swift */; }; + D647EA402B1908E500BABD93 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA362B1908E500BABD93 /* AccountManager.swift */; }; + D647EA412B1908E500BABD93 /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA382B1908E500BABD93 /* AccountKeychainStorage.swift */; }; + D647EA422B1908E500BABD93 /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA392B1908E500BABD93 /* AccountStorage.swift */; }; + D647EA432B1908E500BABD93 /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3B2B1908E500BABD93 /* SubscriptionService.swift */; }; + D647EA442B1908E500BABD93 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3C2B1908E500BABD93 /* APIService.swift */; }; + D647EA452B1908E500BABD93 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3D2B1908E500BABD93 /* AuthService.swift */; }; + D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */; }; D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */; }; D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */; }; D6BBC4692B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; D6FC2BAF2B178FF800744491 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6FC2BAE2B178FF800744491 /* StoreKit.framework */; }; - D6FC2BB52B17AD6300744491 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FC2BB42B17AD6300744491 /* Subscription.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2387,28 +2387,28 @@ CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; D62529662B154637002A372F /* PrivacyPro */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PrivacyPro; sourceTree = ""; }; D62529742B155BCB002A372F /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; - D625297D2B15E449002A372F /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - D625297E2B15E449002A372F /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; - D62529802B15E449002A372F /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; - D62529812B15E449002A372F /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; - D62529832B15E449002A372F /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; - D62529842B15E449002A372F /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; - D62529852B15E449002A372F /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; - D62529872B15E449002A372F /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; D62529932B15E454002A372F /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; - D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; - D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; - D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; + D637D1B22B19028E00C94784 /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; + D637D1B32B19028E00C94784 /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; + D637D1B42B19028E00C94784 /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; + D637D1B52B19028E00C94784 /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; + D647EA352B1908E500BABD93 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D647EA362B1908E500BABD93 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D647EA382B1908E500BABD93 /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; + D647EA392B1908E500BABD93 /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; + D647EA3B2B1908E500BABD93 /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; + D647EA3C2B1908E500BABD93 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; + D647EA3D2B1908E500BABD93 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; + D6BA61002B18EE5E0032C67F /* PrivacyPro.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; D6FC2BAE2B178FF800744491 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; - D6FC2BB42B17AD6300744491 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; - D6FC2BB62B17B6DD00744491 /* PrivacyPro.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -3657,7 +3657,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - D6FC2BB62B17B6DD00744491 /* PrivacyPro.storekit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -4481,10 +4480,10 @@ D625296A2B15465E002A372F /* PrivacyPro */ = { isa = PBXGroup; children = ( - D6FC2BB32B17AD5800744491 /* Models */, + D6BA61002B18EE5E0032C67F /* PrivacyPro.storekit */, D6BBC4632B1665270027507E /* ViewModel */, D625296B2B15467E002A372F /* Views */, - D625297C2B15E449002A372F /* Account */, + D647EA342B1908E500BABD93 /* Subscription */, D62529862B15E449002A372F /* Purchase */, D62529952B15E4B9002A372F /* PurchaseFlows */, D62529882B15E449002A372F /* UserScripts */, @@ -4503,61 +4502,62 @@ path = Views; sourceTree = ""; }; - D625297C2B15E449002A372F /* Account */ = { + D62529862B15E449002A372F /* Purchase */ = { isa = PBXGroup; children = ( - D625297D2B15E449002A372F /* Logging.swift */, - D625297E2B15E449002A372F /* AccountManager.swift */, - D625297F2B15E449002A372F /* AccountStorage */, - D62529822B15E449002A372F /* Services */, ); - path = Account; + path = Purchase; sourceTree = ""; }; - D625297F2B15E449002A372F /* AccountStorage */ = { + D62529882B15E449002A372F /* UserScripts */ = { isa = PBXGroup; children = ( - D62529802B15E449002A372F /* AccountKeychainStorage.swift */, - D62529812B15E449002A372F /* AccountStorage.swift */, + D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */, + D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */, ); - path = AccountStorage; + path = UserScripts; sourceTree = ""; }; - D62529822B15E449002A372F /* Services */ = { + D62529952B15E4B9002A372F /* PurchaseFlows */ = { isa = PBXGroup; children = ( - D62529832B15E449002A372F /* SubscriptionService.swift */, - D62529842B15E449002A372F /* APIService.swift */, - D62529852B15E449002A372F /* AuthService.swift */, + D637D1B22B19028E00C94784 /* AppStoreAccountManagementFlow.swift */, + D637D1B52B19028E00C94784 /* AppStorePurchaseFlow.swift */, + D637D1B32B19028E00C94784 /* AppStoreRestoreFlow.swift */, + D637D1B42B19028E00C94784 /* PurchaseFlow.swift */, ); - path = Services; + path = PurchaseFlows; sourceTree = ""; }; - D62529862B15E449002A372F /* Purchase */ = { + D647EA342B1908E500BABD93 /* Subscription */ = { isa = PBXGroup; children = ( - D62529872B15E449002A372F /* PurchaseManager.swift */, + D647EA352B1908E500BABD93 /* Logging.swift */, + D647EA362B1908E500BABD93 /* AccountManager.swift */, + D647EA372B1908E500BABD93 /* AccountStorage */, + D647EA3A2B1908E500BABD93 /* Services */, + D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */, ); - path = Purchase; + path = Subscription; sourceTree = ""; }; - D62529882B15E449002A372F /* UserScripts */ = { + D647EA372B1908E500BABD93 /* AccountStorage */ = { isa = PBXGroup; children = ( - D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */, - D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */, + D647EA382B1908E500BABD93 /* AccountKeychainStorage.swift */, + D647EA392B1908E500BABD93 /* AccountStorage.swift */, ); - path = UserScripts; + path = AccountStorage; sourceTree = ""; }; - D62529952B15E4B9002A372F /* PurchaseFlows */ = { + D647EA3A2B1908E500BABD93 /* Services */ = { isa = PBXGroup; children = ( - D62529962B15E4B9002A372F /* AppStorePurchaseFlow.swift */, - D62529972B15E4B9002A372F /* AppStoreRestoreFlow.swift */, - D62529982B15E4B9002A372F /* AppStoreAccountManagementFlow.swift */, + D647EA3B2B1908E500BABD93 /* SubscriptionService.swift */, + D647EA3C2B1908E500BABD93 /* APIService.swift */, + D647EA3D2B1908E500BABD93 /* AuthService.swift */, ); - path = PurchaseFlows; + path = Services; sourceTree = ""; }; D6BBC4632B1665270027507E /* ViewModel */ = { @@ -4577,14 +4577,6 @@ path = Extensions; sourceTree = ""; }; - D6FC2BB32B17AD5800744491 /* Models */ = { - isa = PBXGroup; - children = ( - D6FC2BB42B17AD6300744491 /* Subscription.swift */, - ); - path = Models; - sourceTree = ""; - }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -6389,7 +6381,6 @@ 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, - D6FC2BB52B17AD6300744491 /* Subscription.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, @@ -6425,10 +6416,11 @@ 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, 854A01332A558B3A00FCC628 /* UIView+Constraints.swift in Sources */, - D625298D2B15E44A002A372F /* AccountStorage.swift in Sources */, C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */, + D647EA432B1908E500BABD93 /* SubscriptionService.swift in Sources */, 83134D7D20E2D725006CE65D /* FeedbackSender.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, + D637D1B62B19028E00C94784 /* AppStoreAccountManagementFlow.swift in Sources */, 314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */, 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */, 4BBBBA8F2B031B4200D965DA /* VPNWaitlistView.swift in Sources */, @@ -6441,7 +6433,9 @@ D62529942B15E454002A372F /* URL+Subscription.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */, + D647EA402B1908E500BABD93 /* AccountManager.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, + D647EA412B1908E500BABD93 /* AccountKeychainStorage.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */, @@ -6462,7 +6456,6 @@ 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, - D62529992B15E4B9002A372F /* AppStorePurchaseFlow.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, @@ -6476,7 +6469,6 @@ 8586A10D24CBA7070049720E /* FindInPageActivity.swift in Sources */, 1E1626072968413B0004127F /* ViewExtension.swift in Sources */, 31A42566285A0A6300049386 /* FaviconViewModel.swift in Sources */, - D625298B2B15E44A002A372F /* AccountManager.swift in Sources */, 8C4838B5221C8F7F008A6739 /* GestureToolbarButton.swift in Sources */, EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */, 310ECFDD282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift in Sources */, @@ -6503,10 +6495,13 @@ 37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */, 85DFEDED24C7CCA500973FE7 /* AppWidthObserver.swift in Sources */, 4B6484F327FD1E350050A7A1 /* MenuControllerView.swift in Sources */, + D637D1B82B19028E00C94784 /* PurchaseFlow.swift in Sources */, 1EE7C299294227EC0026C8CB /* AutoconsentSettingsViewController.swift in Sources */, 4BCD14632B05AF2B000B1E4C /* NetworkProtectionAccessController.swift in Sources */, 1E8AD1D527C2E22900ABA377 /* DownloadsListSectionViewModel.swift in Sources */, EE0798C52B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, + D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */, + 4BC6DD1C2A60E6AD001EC129 /* ReportBrokenSiteView.swift in Sources */, 31584616281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift in Sources */, C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */, F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */, @@ -6544,6 +6539,7 @@ 1EEF12502851016B003DDE57 /* PrivacyIconAndTrackersAnimator.swift in Sources */, 31CB4251273AF50700FA0F3F /* SpeechRecognizerProtocol.swift in Sources */, 319A37172829C8AD0079FBCE /* UITableViewExtension.swift in Sources */, + D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */, 85EE7F59224673C5000FE757 /* WebContainerNavigationController.swift in Sources */, F4C9FBF528340DDA002281CC /* AutofillInterfaceEmailTruncator.swift in Sources */, 1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */, @@ -6561,7 +6557,6 @@ F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */, 85449EF523FDA02800512AAF /* KeyboardSettingsViewController.swift in Sources */, 85C11E4C2090888C00BFFEB4 /* HomeRowReminder.swift in Sources */, - D625298E2B15E44A002A372F /* SubscriptionService.swift in Sources */, 31B2F11F287846320040427A /* NoMicPermissionAlert.swift in Sources */, 310C4B45281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift in Sources */, 1EFDCBC127D2393C00916BC5 /* DownloadsDeleteHelper.swift in Sources */, @@ -6581,14 +6576,12 @@ EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, - D625298A2B15E44A002A372F /* Logging.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */, 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */, 8C4724502217A14B004C9B2D /* TabViewControllerLongPressBookmarkExtension.swift in Sources */, 1EDE39D22705D4A200C99C72 /* FileSizeDebugViewController.swift in Sources */, - D625299B2B15E4B9002A372F /* AppStoreAccountManagementFlow.swift in Sources */, 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, @@ -6644,7 +6637,6 @@ 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, 312E5746283BB04A00C18FA0 /* AutofillEmptySearchView.swift in Sources */, - D62529902B15E44A002A372F /* AuthService.swift in Sources */, F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */, 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */, 0290472829E861BE0008FE3C /* AppTPTrackerDetailViewModel.swift in Sources */, @@ -6656,7 +6648,6 @@ 85449EFD23FDA71F00512AAF /* KeyboardSettings.swift in Sources */, 980891A222369ADB00313A70 /* FeedbackUserText.swift in Sources */, 4BCD14692B05BDD5000B1E4C /* AppDelegate+Waitlists.swift in Sources */, - D62529912B15E44A002A372F /* PurchaseManager.swift in Sources */, 988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */, 850ABD012AC3961100A733DF /* MainViewController+Segues.swift in Sources */, 9817C9C321EF594700884F65 /* AutoClear.swift in Sources */, @@ -6692,6 +6683,7 @@ 981CA7EA2617797500E119D5 /* MainViewController+AddFavoriteFlow.swift in Sources */, 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */, 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */, + D647EA422B1908E500BABD93 /* AccountStorage.swift in Sources */, F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */, 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */, D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */, @@ -6710,6 +6702,7 @@ F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */, 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, + D647EA442B1908E500BABD93 /* APIService.swift in Sources */, 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */, 0290472E29E99A2F0008FE3C /* GenericIconView.swift in Sources */, 1E7A71172934EB6400B7EA19 /* OmniBarNotificationAnimator.swift in Sources */, @@ -6731,7 +6724,6 @@ 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, - D625299A2B15E4B9002A372F /* AppStoreRestoreFlow.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, 314C92B827C3DD660042EC96 /* QuickLookPreviewView.swift in Sources */, F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */, @@ -6745,6 +6737,7 @@ 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, + D637D1B72B19028E00C94784 /* AppStoreRestoreFlow.swift in Sources */, 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, @@ -6772,6 +6765,7 @@ 85BA585A1F3506AE00C6E8CA /* AppSettings.swift in Sources */, 3151F0EA27357FBA00226F58 /* SpeechRecognizer.swift in Sources */, F17922E21E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift in Sources */, + D647EA452B1908E500BABD93 /* AuthService.swift in Sources */, 0290472229E723260008FE3C /* AppTPManageTrackerCell.swift in Sources */, 985AAE4524899369007A43EC /* HomeScreenTransition.swift in Sources */, 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, @@ -6779,6 +6773,7 @@ 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 9865DFF922A8220D00D27829 /* FavoritesOverlay.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, + D647EA3F2B1908E500BABD93 /* Logging.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, @@ -6787,7 +6782,6 @@ 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, C1B0F6422AB08BE9001EAF05 /* MockPrivacyConfiguration.swift in Sources */, 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, - D625298F2B15E44A002A372F /* APIService.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, F17669D71E43401C003D3222 /* MainViewController.swift in Sources */, 984D60B2222A1284003B9E3B /* FeedbackFormViewController.swift in Sources */, @@ -6801,7 +6795,6 @@ 3132FA2827A0788400DD7A12 /* PassKitPreviewHelper.swift in Sources */, 8505836C219F424500ED4EDB /* TextFieldWithInsets.swift in Sources */, CBD4F13F279EBFAF00B20FD7 /* HomeMessageViewModel.swift in Sources */, - D625298C2B15E44A002A372F /* AccountKeychainStorage.swift in Sources */, 1E162613296C62820004127F /* CookieConsentDaxDialogViewModel.swift in Sources */, 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */, 2DC3FC65C6D9DA634426672D /* AutofillNoAuthAvailableView.swift in Sources */, @@ -8706,6 +8699,404 @@ }; name = Release; }; + D6BA60F32B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTION"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + }; + name = "Alpha Debug"; + }; + D6BA60F42B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; + CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; + PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development App - Alpha"; + SWIFT_VERSION = 5.0; + }; + name = "Alpha Debug"; + }; + D6BA60F52B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).ShareExtension"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.duckduckgo.mobile.ios.alpha.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D6BA60F62B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcons; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OpenAction/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).OpenAction2"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.duckduckgo.mobile.ios.alpha.OpenAction2; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D6BA60F72B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = WidgetsExtensionAlpha.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; + DEAD_CODE_STRIPPING = NO; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Widgets/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).Widgets"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.duckduckgo.mobile.ios.alpha.Widgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D6BA60F82B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PacketTunnelProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PacketTunnelProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 DuckDuckGo. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ""; + OTHER_SWIFT_FLAGS = "-D NETWORK_EXTENSION"; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.NetworkExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D6BA60F92B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 2; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Core/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Alpha Debug"; + }; + D6BA60FA2B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CODE_SIGN_STYLE = Automatic; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = ""; + }; + name = "Alpha Debug"; + }; + D6BA60FB2B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = IntegrationTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = "Alpha Debug"; + }; + D6BA60FC2B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = ""; + INFOPLIST_FILE = DuckDuckGoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ""; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; + D6BA60FD2B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = "Alpha Debug"; + }; + D6BA60FE2B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = IntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; + D6BA60FF2B18DEF70032C67F /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; EE5A7C462A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; @@ -8786,7 +9177,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.alpha"; SWIFT_VERSION = 5.0; @@ -9186,6 +9577,7 @@ buildConfigurations = ( 0202566D298818B200E694E7 /* Debug */, EE5A7C4B2A82BBB700387C84 /* Alpha */, + D6BA60F82B18DEF70032C67F /* Alpha Debug */, 0202566E298818B200E694E7 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9196,6 +9588,7 @@ buildConfigurations = ( 025CCFE92582601C001CD5BB /* Debug */, EE5A7C502A82BBB700387C84 /* Alpha */, + D6BA60FD2B18DEF70032C67F /* Alpha Debug */, 025CCFEA2582601C001CD5BB /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9206,6 +9599,7 @@ buildConfigurations = ( 8390447820BDCE10006461CD /* Debug */, EE5A7C482A82BBB700387C84 /* Alpha */, + D6BA60F52B18DEF70032C67F /* Alpha Debug */, 8390447920BDCE10006461CD /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9216,6 +9610,7 @@ buildConfigurations = ( 84E341B81E2F7EFC00BDBA6F /* Debug */, EE5A7C462A82BBB700387C84 /* Alpha */, + D6BA60F32B18DEF70032C67F /* Alpha Debug */, 84E341B91E2F7EFC00BDBA6F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9226,6 +9621,7 @@ buildConfigurations = ( 84E341BB1E2F7EFC00BDBA6F /* Debug */, EE5A7C472A82BBB700387C84 /* Alpha */, + D6BA60F42B18DEF70032C67F /* Alpha Debug */, 84E341BC1E2F7EFC00BDBA6F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9236,6 +9632,7 @@ buildConfigurations = ( 84E341BE1E2F7EFC00BDBA6F /* Debug */, EE5A7C4F2A82BBB700387C84 /* Alpha */, + D6BA60FC2B18DEF70032C67F /* Alpha Debug */, 84E341BF1E2F7EFC00BDBA6F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9246,6 +9643,7 @@ buildConfigurations = ( 8512EA5E24ED30D30073EE19 /* Debug */, EE5A7C4A2A82BBB700387C84 /* Alpha */, + D6BA60F72B18DEF70032C67F /* Alpha Debug */, 8512EA5F24ED30D30073EE19 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9256,6 +9654,7 @@ buildConfigurations = ( 85482D952462DCD100EDEDD1 /* Debug */, EE5A7C492A82BBB700387C84 /* Alpha */, + D6BA60F62B18DEF70032C67F /* Alpha Debug */, 85482D962462DCD100EDEDD1 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9266,6 +9665,7 @@ buildConfigurations = ( 85D33FD325C97B6E002B91A6 /* Debug */, EE5A7C512A82BBB700387C84 /* Alpha */, + D6BA60FE2B18DEF70032C67F /* Alpha Debug */, 85D33FD425C97B6E002B91A6 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9276,6 +9676,7 @@ buildConfigurations = ( 85F21DB4210F5E32002631A6 /* Debug */, EE5A7C4E2A82BBB700387C84 /* Alpha */, + D6BA60FB2B18DEF70032C67F /* Alpha Debug */, 85F21DB5210F5E32002631A6 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9286,6 +9687,7 @@ buildConfigurations = ( 9825F9D5293F2DE900F220F2 /* Debug */, EE5A7C522A82BBB700387C84 /* Alpha */, + D6BA60FF2B18DEF70032C67F /* Alpha Debug */, 9825F9D6293F2DE900F220F2 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9296,6 +9698,7 @@ buildConfigurations = ( 98A54A8622AFCB2D00E541F4 /* Debug */, EE5A7C4D2A82BBB700387C84 /* Alpha */, + D6BA60FA2B18DEF70032C67F /* Alpha Debug */, 98A54A8722AFCB2D00E541F4 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9306,6 +9709,7 @@ buildConfigurations = ( F143C2EE1E4A4CD400CFDE3A /* Debug */, EE5A7C4C2A82BBB700387C84 /* Alpha */, + D6BA60F92B18DEF70032C67F /* Alpha Debug */, F143C2EF1E4A4CD400CFDE3A /* Release */, ); defaultConfigurationIsVisible = 0; diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme index 2d1a0f8907..4d85e9b636 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme @@ -23,14 +23,14 @@ - - + buildConfiguration = "Alpha Debug"> Result { - // Trigger sign in pop-up - switch await PurchaseManager.shared.syncAppleIDAccount() { - case .success: - break - case .failure: - return .failure(.appStoreAuthenticationFailed) - } + public static func subscriptionOptions() async -> Result { + + let products = PurchaseManager.shared.availableProducts + + let monthly = products.first(where: { $0.id.contains("1month") }) + let yearly = products.first(where: { $0.id.contains("1year") }) + + guard let monthly, let yearly else { return .failure(.noProductsFound) } + + let options = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: "monthly")), + SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: "yearly"))] + let features = SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) } + + return .success(SubscriptionOptions(platform: SubscriptionPlatformName.macos.rawValue, + options: options, + features: features)) + } + + public static func purchaseSubscription(with subscriptionIdentifier: String, emailAccessToken: String?) async -> Result { let externalID: String // Check for past transactions most recent + switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { - case .success(let existingExternalID): - externalID = existingExternalID + case .success(let success): + guard !success.isActive else { return .failure(.activeSubscriptionAlreadyPresent)} + externalID = success.externalID case .failure(let error): switch error { - case .missingAccountOrTransactions: + case .missingAccountOrTransactions, .pastTransactionAuthenticationFailure: // No history, create new account switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { case .success(let response): @@ -62,7 +80,7 @@ public final class AppStorePurchaseFlow { } // Make the purchase - switch await PurchaseManager.shared.purchaseSubscription(with: identifier, externalID: externalID) { + switch await PurchaseManager.shared.purchaseSubscription(with: subscriptionIdentifier, externalID: externalID) { case .success: return .success(()) case .failure(let error): @@ -72,6 +90,14 @@ public final class AppStorePurchaseFlow { } } + @discardableResult + public static func completeSubscriptionPurchase() async -> Result { + + let result = await checkForEntitlements(wait: 2.0, retry: 15) + + return result ? .success(PurchaseUpdate(type: "completed")) : .failure(.missingEntitlements) + } + @discardableResult public static func checkForEntitlements(wait waitTime: Double, retry retryCount: Int) async -> Bool { var count = 0 diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift index 32ca71ea4c..6996c2a111 100644 --- a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift +++ b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift @@ -19,17 +19,20 @@ import Foundation import StoreKit -@available(macOS 12.0, *) +@available(macOS 12.0, iOS 15.0, *) public final class AppStoreRestoreFlow { + public typealias Success = (externalID: String, isActive: Bool) + public enum Error: Swift.Error { case missingAccountOrTransactions case pastTransactionAuthenticationFailure case accessTokenObtainingError +// case subscriptionExpired case somethingWentWrong } - public static func restoreAccountFromPastPurchase() async -> Result { + public static func restoreAccountFromPastPurchase() async -> Result { guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } // Do the store login to get short-lived token @@ -42,11 +45,26 @@ public final class AppStoreRestoreFlow { return .failure(.pastTransactionAuthenticationFailure) } + let externalID: String + switch await AccountManager().exchangeAndStoreTokens(with: authToken) { - case .success(let externalID): - return .success(externalID) + case .success(let existingExternalID): + externalID = existingExternalID case .failure: return .failure(.accessTokenObtainingError) } + + let accessToken = AccountManager().accessToken ?? "" + var isActive = false + + switch await SubscriptionService.getSubscriptionInfo(token: accessToken) { + case .success(let response): + isActive = response.status != "Expired" && response.status != "Inactive" + case .failure: + return .failure(.somethingWentWrong) + } + + // TOOD: Fix this by probably splitting/changing result of exchangeAndStoreTokens + return .success((externalID: externalID, isActive: isActive)) } } diff --git a/DuckDuckGo/PrivacyPro/Models/Subscription.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift similarity index 62% rename from DuckDuckGo/PrivacyPro/Models/Subscription.swift rename to DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift index a946e514cc..d1a666bde2 100644 --- a/DuckDuckGo/PrivacyPro/Models/Subscription.swift +++ b/DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift @@ -1,6 +1,5 @@ // -// Subscription.swift -// DuckDuckGo +// AppStorePurchaseFlow.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -19,35 +18,33 @@ import Foundation -struct Subscription: Encodable { - let token: String -} +protocol PurchaseFlow { -/// Values that the Frontend can use to determine the current state. -struct SubscriptionValues: Codable { - enum CodingKeys: String, CodingKey { - case token - } - let token: String } -struct SubscriptionOptions: Encodable { +public struct SubscriptionOptions: Encodable { let platform: String let options: [SubscriptionOption] let features: [SubscriptionFeature] } -struct SubscriptionOption: Encodable { +public struct SubscriptionOption: Encodable { let id: String - let cost: SubscriptionCost + let cost: SubscriptionOptionCost +} - struct SubscriptionCost: Encodable { - let displayPrice: String - let recurrence: String - } +struct SubscriptionOptionCost: Encodable { + let displayPrice: String + let recurrence: String +} + +public struct SubscriptionFeature: Encodable { + let name: String } -enum SubscriptionFeatureName: String, CaseIterable { +// MARK: - + +public enum SubscriptionFeatureName: String, CaseIterable { case privateBrowsing = "private-browsing" case privateSearch = "private-search" case emailProtection = "email-protection" @@ -57,10 +54,19 @@ enum SubscriptionFeatureName: String, CaseIterable { case identityTheftRestoration = "identity-theft-restoration" } -struct SubscriptionFeature: Encodable { - let name: String +public enum SubscriptionPlatformName: String { + case macos + case stripe } -struct SubscriptionSelection: Decodable { - let id: String +// MARK: - + +public struct PurchaseUpdate: Codable { + let type: String + let token: String? + + public init(type: String, token: String? = nil) { + self.type = type + self.token = token + } } diff --git a/DuckDuckGo/PrivacyPro/Account/AccountManager.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift similarity index 99% rename from DuckDuckGo/PrivacyPro/Account/AccountManager.swift rename to DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift index fb8261ca46..727acfe597 100644 --- a/DuckDuckGo/PrivacyPro/Account/AccountManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift @@ -39,6 +39,7 @@ public class AccountManager { public init(storage: AccountStorage = AccountKeychainStorage()) { self.storage = storage + try? self.storage.clearAuthenticationState() } public var authToken: String? { diff --git a/DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountKeychainStorage.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountKeychainStorage.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountKeychainStorage.swift rename to DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountKeychainStorage.swift diff --git a/DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountStorage.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountStorage.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/Account/AccountStorage/AccountStorage.swift rename to DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountStorage.swift diff --git a/DuckDuckGo/PrivacyPro/Account/Logging.swift b/DuckDuckGo/PrivacyPro/Subscription/Logging.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/Account/Logging.swift rename to DuckDuckGo/PrivacyPro/Subscription/Logging.swift diff --git a/DuckDuckGo/PrivacyPro/Purchase/PurchaseManager.swift b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift similarity index 88% rename from DuckDuckGo/PrivacyPro/Purchase/PurchaseManager.swift rename to DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift index 30fca439b1..3c0cf3fe24 100644 --- a/DuckDuckGo/PrivacyPro/Purchase/PurchaseManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift @@ -19,9 +19,9 @@ import Foundation import StoreKit -@available(macOS 12.0, iOS 15, *) typealias Transaction = StoreKit.Transaction -@available(macOS 12.0, iOS 15, *) typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo -@available(macOS 12.0, iOS 15, *) typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState +@available(macOS 12.0, iOS 15.0, *) typealias Transaction = StoreKit.Transaction +@available(macOS 12.0, iOS 15.0, *) typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo +@available(macOS 12.0, iOS 15.0, *) typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState public enum StoreError: Error { case failedVerification @@ -37,12 +37,17 @@ enum PurchaseManagerError: Error { case unknownError } -@available(macOS 12.0, iOS 15, *) +@available(macOS 12.0, iOS 15.0, *) public final class PurchaseManager: ObservableObject { - static let productIdentifiers = ["ios.subscription.1month", "ios.subscription.1year", - "subscription.1week", "subscription.1month", "subscription.1year", - "review.subscription.1week", "review.subscription.1month", "review.subscription.1year"] + static let productIdentifiers = ["subscription.1week", + "subscription.1month", + "subscription.1year", + "review.subscription.1week", + "review.subscription.1month", + "review.subscription.1year", + "ios.subscription.1month", + "ios.subscription.1year"] public static let shared = PurchaseManager() @@ -181,6 +186,20 @@ public final class PurchaseManager: ObservableObject { return transactions.first?.jwsRepresentation } + @MainActor + public static func hasActiveSubscription() async -> Bool { + print(" -- [PurchaseManager] hasActiveSubscription()") + + var transactions: [VerificationResult] = [] + + for await result in Transaction.currentEntitlements { + transactions.append(result) + } + + print(" -- [PurchaseManager] hasActiveSubscription(): fetched \(transactions.count) transactions") + + return !transactions.isEmpty + } @MainActor public func purchaseSubscription(with identifier: String, externalID: String) async -> Result { diff --git a/DuckDuckGo/PrivacyPro/Account/Services/APIService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/APIService.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/Account/Services/APIService.swift rename to DuckDuckGo/PrivacyPro/Subscription/Services/APIService.swift diff --git a/DuckDuckGo/PrivacyPro/Account/Services/AuthService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/AuthService.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/Account/Services/AuthService.swift rename to DuckDuckGo/PrivacyPro/Subscription/Services/AuthService.swift diff --git a/DuckDuckGo/PrivacyPro/Account/Services/SubscriptionService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift similarity index 76% rename from DuckDuckGo/PrivacyPro/Account/Services/SubscriptionService.swift rename to DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift index ad34078a27..877a45f245 100644 --- a/DuckDuckGo/PrivacyPro/Account/Services/SubscriptionService.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift @@ -41,4 +41,21 @@ public struct SubscriptionService: APIService { public let platform: String public let status: String } + + // MARK: - + + public static func getProducts() async -> Result { + await executeAPICall(method: "GET", endpoint: "products") + } + + public typealias GetProductsResponse = [GetProductsItem] + + public struct GetProductsItem: Decodable { + public let productId: String + public let productLabel: String + public let billingPeriod: String + public let price: String + public let currency: String + } + } diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 90feb04478..624423425d 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -84,9 +84,22 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { return nil } } + + struct Subscription: Encodable { + let token: String + } + + /// Values that the Frontend can use to determine the current state. + struct SubscriptionValues: Codable { + enum CodingKeys: String, CodingKey { + case token + } + let token: String + } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { var authToken = AccountManager().authToken ?? Constants.empty + // let authToken = "" return Subscription(token: authToken) } @@ -114,7 +127,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - + + struct SubscriptionSelection: Decodable { + let id: String + } + let message = original if #available(iOS 15, *) { diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index 03f5f9f9fe..9d0ea0c7d5 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -36,20 +36,14 @@ class SubscriptionFlowViewModel: ObservableObject { subFeature: Subfeature = SubscriptionPagesUseSubscriptionFeature()) { self.userScript = userScript self.subFeature = subFeature - Task { await setupProductObserver() } } // Fetch available Products from the AppStore - private func setupProductObserver() async { + func setupProductObserver() async { purchaseManager.$availableProducts - .dropFirst() - .sink { [weak self] products in + .sink { [weak self] _ in guard let self = self else { return } - if !products.isEmpty { - Task { await self.setProductsLoading(false) } - } else { - assertionFailure("Could not load products from the App Store") - } + Task { await self.setProductsLoading(false) } } .store(in: &cancellables) await purchaseManager.updateAvailableProducts() diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index 6c2604b2dc..e6f5c388cc 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -22,6 +22,7 @@ import WebKit import UserScript import SwiftUI import DesignResourcesKit +import Core struct HeadlessWebview: UIViewRepresentable { let userScript: UserScriptMessaging @@ -38,7 +39,12 @@ struct HeadlessWebview: UIViewRepresentable { configuration.userContentController = userContentController let webView = WKWebView(frame: .zero, configuration: configuration) - webView.load(URLRequest(url: url)) + // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent + webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)" + + // DispatchQueue.main.asyncAfter(deadline: .now() + 10) { + webView.load(URLRequest(url: url)) + // } if #available(iOS 16.4, *) { webView.isInspectable = true diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index a3092c130e..0db7e8511a 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -34,7 +34,9 @@ struct SubscriptionFlowView: View { SwiftUI.ProgressView() } } - + .onAppear(perform: { + Task { await viewModel.setupProductObserver() } + }) .navigationTitle(viewModel.viewTitle) } } diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift deleted file mode 100644 index 2c8b7f01e1..0000000000 --- a/LocalPackages/Subscription/Package.swift +++ /dev/null @@ -1,23 +0,0 @@ -// swift-tools-version: 5.8 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "Subscription", - platforms: [ .macOS(.v11), .iOS(.v15) ], - products: [ - .library( - name: "Subscription", - targets: ["Subscription"]), - ], - dependencies: [], - targets: [ - .target( - name: "Subscription", - dependencies: []), - .testTarget( - name: "SubscriptionTests", - dependencies: ["Subscription"]), - ] -) diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/AccountTests/AccountsTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/AccountTests/AccountsTests.swift deleted file mode 100644 index 09f797525f..0000000000 --- a/LocalPackages/Subscription/Tests/SubscriptionTests/AccountTests/AccountsTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// AccountTests.swift -// -// 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 XCTest -@testable import Account - -final class AccountTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(Account().text, "Hello, World!") - } -} diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/PurchaseTests/PurchaseTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/PurchaseTests/PurchaseTests.swift deleted file mode 100644 index 9c6b73d0a1..0000000000 --- a/LocalPackages/Subscription/Tests/SubscriptionTests/PurchaseTests/PurchaseTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// PurchaseTests.swift -// -// 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 XCTest -@testable import Purchase - -final class PurchaseTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(Purchase().text, "Hello, World!") - } -} diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests/SubscriptionTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests/SubscriptionTests.swift deleted file mode 100644 index 16c144c04f..0000000000 --- a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests/SubscriptionTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// SubscriptionTests.swift -// -// 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 XCTest -@testable import Subscription - -final class SubscriptionTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(Subscription().text, "Hello, World!") - } -} From c816b2b2ca09b00ae0f144eae82add9c96056ef2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 30 Nov 2023 23:11:32 +0100 Subject: [PATCH 59/99] Remove unwanted reference --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 15c2fbd02e..8e82298b8b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -765,7 +765,6 @@ CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DDE29A6736A00832877 /* APIHeadersTests.swift */; }; CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; - D62529672B154637002A372F /* PrivacyPro in Resources */ = {isa = PBXBuildFile; fileRef = D62529662B154637002A372F /* PrivacyPro */; }; D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529742B155BCB002A372F /* SubscriptionFlowView.swift */; }; D62529922B15E44A002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */; }; D62529942B15E454002A372F /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529932B15E454002A372F /* URL+Subscription.swift */; }; @@ -2385,7 +2384,6 @@ CBF14FC227970072001D94D0 /* HomeMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageView.swift; sourceTree = ""; }; CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; - D62529662B154637002A372F /* PrivacyPro */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PrivacyPro; sourceTree = ""; }; D62529742B155BCB002A372F /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; D62529932B15E454002A372F /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; @@ -2894,7 +2892,6 @@ 02025B0A29884CF300E694E7 /* AppTrackingProtection */ = { isa = PBXGroup; children = ( - D62529662B154637002A372F /* PrivacyPro */, 02341FA22A435E42008A1531 /* AppTPOnboarding */, 0290471C29E7085D0008FE3C /* AppTPManageTrackersView */, 0290472629E8619B0008FE3C /* AppTPTrackerDetailView */, @@ -5985,7 +5982,6 @@ 85F98F98296F4CB100742F4A /* SyncAssets.xcassets in Resources */, 984147AB24F025F700362052 /* Autocomplete.storyboard in Resources */, AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, - D62529672B154637002A372F /* PrivacyPro in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, F1E4A4451EE89460006F2EAE /* Bookmarks.storyboard in Resources */, From 66755fcab7d4492db331e37f2e02013334948c81 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 1 Dec 2023 02:32:32 +0100 Subject: [PATCH 60/99] Add transaction progress view --- DuckDuckGo.xcodeproj/project.pbxproj | 4 + ...scriptionPagesUseSubscriptionFeature.swift | 118 ++++++++++-------- .../ViewModel/SubscriptionFlowViewModel.swift | 36 +++--- .../Views/PurchaseInProgressView.swift | 45 +++++++ .../Views/SubscriptionFlowView.swift | 17 +-- 5 files changed, 144 insertions(+), 76 deletions(-) create mode 100644 DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8e82298b8b..a6efcd6054 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -781,6 +781,7 @@ D647EA442B1908E500BABD93 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3C2B1908E500BABD93 /* APIService.swift */; }; D647EA452B1908E500BABD93 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3D2B1908E500BABD93 /* AuthService.swift */; }; D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */; }; + D69D3D862B1968220090E88A /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */; }; D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */; }; D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; @@ -2400,6 +2401,7 @@ D647EA3C2B1908E500BABD93 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; D647EA3D2B1908E500BABD93 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; + D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; D6BA61002B18EE5E0032C67F /* PrivacyPro.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; @@ -4495,6 +4497,7 @@ children = ( D6BBC4612B1663190027507E /* HeadlessWebView.swift */, D62529742B155BCB002A372F /* SubscriptionFlowView.swift */, + D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */, ); path = Views; sourceTree = ""; @@ -6731,6 +6734,7 @@ 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */, 1EF24235273BB9D200DE3D02 /* IntervalSlider.swift in Sources */, 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, + D69D3D862B1968220090E88A /* PurchaseInProgressView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, D637D1B72B19028E00C94784 /* AppStoreRestoreFlow.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 624423425d..92389f79c6 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -17,15 +17,13 @@ // limitations under the License. // -#if SUBSCRIPTION - import BrowserServicesKit import Common import Foundation import WebKit import UserScript -final class SubscriptionPagesUseSubscriptionFeature: Subfeature { +final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObject { struct Constants { static let featureName = "useSubscription" @@ -58,6 +56,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { static let year = "yearly" } + @Published var transactionInProgress = false + var broker: UserScriptMessageBroker? var featureName = Constants.featureName @@ -96,70 +96,82 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } let token: String } + + // Manage transation in progress flag + private func withTransactionInProgress(_ work: () async throws -> T) async rethrows -> T { + transactionInProgress = true + defer { + transactionInProgress = false + } + return try await work() + } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { var authToken = AccountManager().authToken ?? Constants.empty - // let authToken = "" return Subscription(token: authToken) } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - let subscriptionOptions: [SubscriptionOption] - - if #available(iOS 15, *) { - let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.monthly) }) - let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.yearly) }) - - guard let monthly, let yearly else { return nil } - - subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: RecurrenceOptions.month)), - SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: RecurrenceOptions.year))] - } else { - return nil + + await withTransactionInProgress { + let subscriptionOptions: [SubscriptionOption] + + if #available(iOS 15, *) { + let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.monthly) }) + let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.yearly) }) + + guard let monthly, let yearly else { return nil } + + subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: RecurrenceOptions.month)), + SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: RecurrenceOptions.year))] + } else { + return nil + } + + let message = SubscriptionOptions(platform: Constants.os, + options: subscriptionOptions, + features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) + + return message } - - let message = SubscriptionOptions(platform: Constants.os, - options: subscriptionOptions, - features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) - - return message } func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - - struct SubscriptionSelection: Decodable { - let id: String - } - - let message = original - if #available(iOS 15, *) { - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") - return nil + await withTransactionInProgress { + struct SubscriptionSelection: Decodable { + let id: String } - - print("Selected: \(subscriptionSelection.id)") - - let emailAccessToken = try? EmailManager().getToken() - - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { - case .success: - break - case .failure(let error): - print("Purchase failed: \(error)") - return nil - } - - await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) - - DispatchQueue.main.async { - self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) + + let message = original + + if #available(iOS 15, *) { + guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + return nil + } + + print("Selected: \(subscriptionSelection.id)") + + let emailAccessToken = try? EmailManager().getToken() + + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + case .success: + break + case .failure(let error): + print("Purchase failed: \(error)") + return nil + } + + await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) + + DispatchQueue.main.async { + self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) + } } + + return nil } - - return nil } func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { @@ -206,5 +218,3 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { broker.push(method: method.rawValue, params: params, for: self, into: webView) } } - -#endif diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index 9d0ea0c7d5..6f145fed82 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -21,36 +21,44 @@ import Foundation import UserScript import Combine -class SubscriptionFlowViewModel: ObservableObject { +final class SubscriptionFlowViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript - let subFeature: Subfeature + let subFeature: SubscriptionPagesUseSubscriptionFeature + let purchaseManager: PurchaseManager + let purchaseURL = URL.purchaseSubscription let viewTitle = SubscriptionUserText.navigationTitle - let purchaseManager = PurchaseManager.shared - - @Published var isLoadingProducts = true + + @Published var transactionInProgress = false private var cancellables = Set() init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), - subFeature: Subfeature = SubscriptionPagesUseSubscriptionFeature()) { + subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), + purchaseManager: PurchaseManager = PurchaseManager.shared) { self.userScript = userScript self.subFeature = subFeature + self.purchaseManager = purchaseManager } - // Fetch available Products from the AppStore - func setupProductObserver() async { - purchaseManager.$availableProducts - .sink { [weak self] _ in + // Observe transaction status + private func setupTransactionObserver() async { + subFeature.$transactionInProgress + .sink { [weak self] status in guard let self = self else { return } - Task { await self.setProductsLoading(false) } + Task { await self.setTransactionInProgress(status) } } .store(in: &cancellables) - await purchaseManager.updateAvailableProducts() } @MainActor - private func setProductsLoading(_ isLoading: Bool) { - self.isLoadingProducts = isLoading + private func setTransactionInProgress(_ inProgress: Bool) { + self.transactionInProgress = inProgress } + + func initializeViewData() async { + await self.setupTransactionObserver() + await purchaseManager.updateAvailableProducts() + } + } diff --git a/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift b/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift new file mode 100644 index 0000000000..1eacd2731c --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift @@ -0,0 +1,45 @@ +// +// PurchaseInProgressView.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 + +struct PurchaseInProgressView: View { + @Environment(\.colorScheme) var colorScheme + + var body: some View { + ZStack { + Color(colorScheme == .dark ? .black : .white) + .opacity(0.4) + .edgesIgnoringSafeArea(.all) + .disabled(true) + + ZStack { + RoundedRectangle(cornerRadius: 12) + .fill(colorScheme == .dark ? .black : .white) + .frame(width: 120, height: 120) + .shadow(color: colorScheme == .dark ? .gray90 : .gray10, radius: 10) + + // ProgressView + SwiftUI.ProgressView() + .scaleEffect(2) + .progressViewStyle(CircularProgressViewStyle(tint: Color(colorScheme == .dark ? .gray30 : .gray70))) + } + + } + } +} diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index 0db7e8511a..526d9efc4a 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -21,21 +21,22 @@ import SwiftUI import Foundation struct SubscriptionFlowView: View { - + @ObservedObject var viewModel: SubscriptionFlowViewModel var body: some View { ZStack { - if !viewModel.isLoadingProducts { - AsyncHeadlessWebView(url: viewModel.purchaseURL, - userScript: viewModel.userScript, - subFeature: viewModel.subFeature).background() - } else { - SwiftUI.ProgressView() + AsyncHeadlessWebView(url: viewModel.purchaseURL, + userScript: viewModel.userScript, + subFeature: viewModel.subFeature).background() + + // Overlay that appears when transaction is in progress + if viewModel.transactionInProgress { + PurchaseInProgressView() } } .onAppear(perform: { - Task { await viewModel.setupProductObserver() } + Task { await viewModel.initializeViewData() } }) .navigationTitle(viewModel.viewTitle) } From 5762d05a3176f87728ca5aa211eac41f90500b52 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 1 Dec 2023 02:34:42 +0100 Subject: [PATCH 61/99] Adjust progress view colors --- DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift b/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift index 1eacd2731c..370ad5016f 100644 --- a/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift +++ b/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift @@ -24,7 +24,7 @@ struct PurchaseInProgressView: View { var body: some View { ZStack { Color(colorScheme == .dark ? .black : .white) - .opacity(0.4) + .opacity(0.0) .edgesIgnoringSafeArea(.all) .disabled(true) @@ -32,7 +32,7 @@ struct PurchaseInProgressView: View { RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? .black : .white) .frame(width: 120, height: 120) - .shadow(color: colorScheme == .dark ? .gray90 : .gray10, radius: 10) + .shadow(color: colorScheme == .dark ? .black : .gray30, radius: 10) // ProgressView SwiftUI.ProgressView() From 4595d466b3e25ce94862e09a11d8a3bdb86628db Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 1 Dec 2023 13:21:07 +0100 Subject: [PATCH 62/99] Debug Settings --- DuckDuckGo.xcodeproj/project.pbxproj | 9 + DuckDuckGo/Debug.storyboard | 61 ++++++- .../Subscription/AccountManager.swift | 1 - .../Views/PurchaseInProgressView.swift | 6 +- DuckDuckGo/SubscriptionDebugModel.swift | 169 ++++++++++++++++++ .../SubscriptionDebugViewController.swift | 142 +++++++++++++++ 6 files changed, 378 insertions(+), 10 deletions(-) create mode 100644 DuckDuckGo/SubscriptionDebugModel.swift create mode 100644 DuckDuckGo/SubscriptionDebugViewController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a6efcd6054..114e812fc8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -782,6 +782,8 @@ D647EA452B1908E500BABD93 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3D2B1908E500BABD93 /* AuthService.swift */; }; D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */; }; D69D3D862B1968220090E88A /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */; }; + D69D3D882B1971770090E88A /* SubscriptionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */; }; + D69D3D8A2B1973CC0090E88A /* SubscriptionDebugModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */; }; D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */; }; D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; @@ -2402,6 +2404,8 @@ D647EA3D2B1908E500BABD93 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; + D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugViewController.swift; sourceTree = ""; }; + D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugModel.swift; sourceTree = ""; }; D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; D6BA61002B18EE5E0032C67F /* PrivacyPro.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; @@ -3854,6 +3858,7 @@ children = ( 85449EFC23FDA71F00512AAF /* KeyboardSettings.swift */, 4B53648926718D0E001AA041 /* EmailWaitlist.swift */, + D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */, ); name = Model; sourceTree = ""; @@ -3884,6 +3889,7 @@ F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */, 983D71B02A286E810072E26D /* SyncDebugViewController.swift */, EE72CA842A862D000043B5B3 /* NetworkProtectionDebugViewController.swift */, + D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */, ); name = Debug; sourceTree = ""; @@ -6436,6 +6442,7 @@ 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, D647EA412B1908E500BABD93 /* AccountKeychainStorage.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, + D69D3D8A2B1973CC0090E88A /* SubscriptionDebugModel.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */, F13B4BD31F1822C700814661 /* Tab.swift in Sources */, @@ -6498,6 +6505,8 @@ 1EE7C299294227EC0026C8CB /* AutoconsentSettingsViewController.swift in Sources */, 4BCD14632B05AF2B000B1E4C /* NetworkProtectionAccessController.swift in Sources */, 1E8AD1D527C2E22900ABA377 /* DownloadsListSectionViewModel.swift in Sources */, + D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */, + D69D3D882B1971770090E88A /* SubscriptionDebugViewController.swift in Sources */, EE0798C52B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */, 4BC6DD1C2A60E6AD001EC129 /* ReportBrokenSiteView.swift in Sources */, diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 8e770932b8..8e14c81b79 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -200,8 +200,28 @@ + + + + + + + + + + + + + + - + @@ -210,7 +230,7 @@ - + @@ -219,7 +239,7 @@ - + @@ -228,7 +248,7 @@ - + @@ -237,7 +257,7 @@ - + @@ -246,7 +266,7 @@ - + @@ -799,6 +819,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift index 727acfe597..fb8261ca46 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift @@ -39,7 +39,6 @@ public class AccountManager { public init(storage: AccountStorage = AccountKeychainStorage()) { self.storage = storage - try? self.storage.clearAuthenticationState() } public var authToken: String? { diff --git a/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift b/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift index 370ad5016f..796ae24342 100644 --- a/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift +++ b/DuckDuckGo/PrivacyPro/Views/PurchaseInProgressView.swift @@ -20,7 +20,8 @@ import SwiftUI struct PurchaseInProgressView: View { @Environment(\.colorScheme) var colorScheme - + + // TODO: Update colors and design var body: some View { ZStack { Color(colorScheme == .dark ? .black : .white) @@ -33,8 +34,7 @@ struct PurchaseInProgressView: View { .fill(colorScheme == .dark ? .black : .white) .frame(width: 120, height: 120) .shadow(color: colorScheme == .dark ? .black : .gray30, radius: 10) - - // ProgressView + SwiftUI.ProgressView() .scaleEffect(2) .progressViewStyle(CircularProgressViewStyle(tint: Color(colorScheme == .dark ? .gray30 : .gray70))) diff --git a/DuckDuckGo/SubscriptionDebugModel.swift b/DuckDuckGo/SubscriptionDebugModel.swift new file mode 100644 index 0000000000..9bc813254d --- /dev/null +++ b/DuckDuckGo/SubscriptionDebugModel.swift @@ -0,0 +1,169 @@ +// +// SubscriptionModel.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 + +// public final class SubscriptionDebugModel { + /* + private let accountManager = AccountManager() + + private var _purchaseManager: Any? + + @available(macOS 12.0, iOS 15.0, *) + fileprivate var purchaseManager: PurchaseManager { + if _purchaseManager == nil { + _purchaseManager = PurchaseManager() + } + // swiftlint:disable:next force_cast + return _purchaseManager as! PurchaseManager + } + + @objc + func simulateSubscriptionActiveState() { + accountManager.storeAccount(token: "fake-token", email: "fake@email.com", externalID: "123") + } + + @objc + func signOut() { + accountManager.signOut() + } + + @objc + func showAccountDetails() { + let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" + let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", + "AccessToken: \(accountManager.accessToken ?? "")", + "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil + showAlert(title: title, message: message) + } + + @objc + func validateToken() { + Task { + guard let token = accountManager.accessToken else { return } + switch await AuthService.validateToken(accessToken: token) { + case .success(let response): + showAlert(title: "Validate token", message: "\(response)") + case .failure(let error): + showAlert(title: "Validate token", message: "\(error)") + } + } + } + + @objc + func checkEntitlements() { + Task { + var results: [String] = [] + + for entitlementName in ["fake", "dummy1", "dummy2", "dummy3"] { + let result = await AccountManager().hasEntitlement(for: entitlementName) + let resultSummary = "Entitlement check for \(entitlementName): \(result)" + results.append(resultSummary) + print(resultSummary) + } + + showAlert(title: "Check Entitlements", message: results.joined(separator: "\n")) + } + } + + @objc + func getSubscriptionInfo() { + Task { + guard let token = accountManager.accessToken else { return } + switch await SubscriptionService.getSubscriptionInfo(token: token) { + case .success(let response): + showAlert(title: "Subscription info", message: "\(response)") + case .failure(let error): + showAlert(title: "Subscription info", message: "\(error)") + } + } + } + + @available(macOS 12.0, *) + @objc + func syncAppleIDAccount() { + Task { + await purchaseManager.syncAppleIDAccount() + } + } + + @available(macOS 12.0, *) + @objc + func checkProductsAvailability() { + Task { + + let result = await purchaseManager.hasProductsAvailable() + showAlert(title: "Check App Store Product Availability", + message: "Can purchase: \(result ? "YES" : "NO")") + } + } + + @objc + func restorePurchases(_ sender: Any?) { + if #available(macOS 12.0, *) { + Task { + await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + } + } + } + + /*/ + @objc + func testError1(_ sender: Any?) { + Task { @MainActor in + let alert = NSAlert.init() + alert.messageText = "Something Went Wrong" + alert.informativeText = "The App Store was not able to process your purchase. Please try again later." + alert.addButton(withTitle: "OK") + alert.runModal() + } + } + + @objc + func testError2(_ sender: Any?) { + Task { @MainActor in + let alert = NSAlert.init() + alert.messageText = "Subscription Not Found" + alert.informativeText = "The subscription associated with this Apple ID is no longer active." + alert.addButton(withTitle: "View Plans") + alert.addButton(withTitle: "Cancel") + alert.runModal() + } + } + + @IBAction func showPurchaseView(_ sender: Any?) { + if #available(macOS 12.0, *) { + currentViewController()?.presentAsSheet(DebugPurchaseViewController()) + } + } + + private func showAlert(title: String, message: String? = nil) { + Task { @MainActor in + let alert = NSAlert.init() + alert.messageText = title + if let message = message { + alert.informativeText = message + } + alert.addButton(withTitle: "OK") + alert.runModal() + } + } + */ +} +*/ diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift new file mode 100644 index 0000000000..299f44a006 --- /dev/null +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -0,0 +1,142 @@ +// +// SubscriptionDebugViewController.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 + +#if !SUBSCRIPTION + +final class SubscriptionDebugViewController: UITableViewController { + // Just an empty VC +} + +#else + +final class SubscriptionDebugViewController: UITableViewController { + + private let accountManager = AccountManager() + + @available(macOS 12.0, iOS 15.0, *) + fileprivate var purchaseManager: PurchaseManager = PurchaseManager.shared + + private let titles = [ + Sections.authorization: "Authentication", + ] + + enum Sections: Int, CaseIterable { + case authorization + } + + enum AuthorizationRows: Int, CaseIterable { + case showDetails + case simulateActive + case clearAuthData + + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return Sections.allCases.count + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + guard let section = Sections(rawValue: section) else { return nil } + return titles[section] + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + + cell.detailTextLabel?.text = nil + + switch Sections(rawValue: indexPath.section) { + + case .authorization: + switch AuthorizationRows(rawValue: indexPath.row) { + case .clearAuthData: + cell.textLabel?.text = "Clear Authorization Data (Sign out)" + case .simulateActive: + cell.textLabel?.text = "Simulate Active State (Inject Fake Token)" + case .showDetails: + cell.textLabel?.text = "Show Account Details" + case .none: + break + } + + case.none: + break + } + + return cell + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch Sections(rawValue: section) { + case .authorization: return AuthorizationRows.allCases.count + case .none: return 0 + + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch Sections(rawValue: indexPath.section) { + case .authorization: + switch AuthorizationRows(rawValue: indexPath.row) { + case .clearAuthData: clearAuthData() + case .simulateActive: simulateActive() + case .showDetails: showDetails() + default: break + } + case .none: + break + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + private func showAlert(title: String, message: String? = nil) { + DispatchQueue.main.async { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) + alertController.addAction(okAction) + + // Assuming this function is in a UIViewController subclass + self.present(alertController, animated: true, completion: nil) + } + } + + // MARK: Account Status Actions + private func clearAuthData() { + accountManager.signOut() + } + + private func simulateActive() { + accountManager.storeAccount(token: "a-fake-token", + email: "a.fake@email.com", + externalID: "666") + } + + private func showDetails() { + let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" + let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", + "AccessToken: \(accountManager.accessToken ?? "")", + "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil + showAlert(title: title, message: message) + } +} + +#endif From ca4b13367a327057eab2cb4b40b4ad4e18e27b7f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 4 Dec 2023 12:22:18 +0100 Subject: [PATCH 63/99] Update Debug Options --- DuckDuckGo/SubscriptionDebugViewController.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 299f44a006..5b8517fcd8 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -44,7 +44,6 @@ final class SubscriptionDebugViewController: UITableViewController { enum AuthorizationRows: Int, CaseIterable { case showDetails - case simulateActive case clearAuthData } @@ -69,8 +68,6 @@ final class SubscriptionDebugViewController: UITableViewController { switch AuthorizationRows(rawValue: indexPath.row) { case .clearAuthData: cell.textLabel?.text = "Clear Authorization Data (Sign out)" - case .simulateActive: - cell.textLabel?.text = "Simulate Active State (Inject Fake Token)" case .showDetails: cell.textLabel?.text = "Show Account Details" case .none: @@ -97,7 +94,6 @@ final class SubscriptionDebugViewController: UITableViewController { case .authorization: switch AuthorizationRows(rawValue: indexPath.row) { case .clearAuthData: clearAuthData() - case .simulateActive: simulateActive() case .showDetails: showDetails() default: break } From d6be0133bf08402d4c7d10c8e708367b4f149a50 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 4 Dec 2023 15:04:02 +0100 Subject: [PATCH 64/99] Update Settings view to dynamicall show PrivacyPro settings --- ...scriptionPagesUseSubscriptionFeature.swift | 2 +- DuckDuckGo/SettingsViewController.swift | 78 +++++++++++++++++-- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 92389f79c6..d0f56f27df 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -107,7 +107,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - var authToken = AccountManager().authToken ?? Constants.empty + let authToken = AccountManager().authToken ?? Constants.empty return Subscription(token: authToken) } diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 00cdd877d9..b7bd848399 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -75,6 +75,11 @@ class SettingsViewController: UITableViewController { @IBOutlet weak var voiceSearchToggle: UISwitch! @IBOutlet weak var privacyProSignupCell: UITableViewCell! @IBOutlet weak var privacyProLearnMoreCell: UITableViewCell! + @IBOutlet weak var privacyProVPNCell: UITableViewCell! + @IBOutlet weak var privacyProDBPCell: UITableViewCell! + @IBOutlet weak var privacyProITPCell: UITableViewCell! + @IBOutlet weak var privacyProSubscriptionSettingsCell: UITableViewCell! + @IBOutlet var labels: [UILabel]! @IBOutlet var accessoryLabels: [UILabel]! @@ -90,7 +95,6 @@ class SettingsViewController: UITableViewController { private let bookmarksDatabase: CoreDataDatabase private lazy var emailManager = EmailManager() - private lazy var versionProvider: AppVersion = AppVersion.shared fileprivate lazy var privacyStore = PrivacyUserDefaults() fileprivate lazy var appSettings = AppDependencyProvider.shared.appSettings @@ -139,6 +143,10 @@ class SettingsViewController: UITableViewController { return false #endif }() + +#if SUBSCRIPTION + private lazy var accountManger = AccountManager() +#endif private lazy var shouldShowPrivacyPro: Bool = { #if SUBSCRIPTION @@ -151,6 +159,18 @@ class SettingsViewController: UITableViewController { return false #endif }() + + private lazy var privacyProIsAuthenticated: Bool = { +#if SUBSCRIPTION + if accountManger.isUserAuthenticated { + return true + } + return false +#else + return false +#endif + }() + override func viewDidLoad() { super.viewDidLoad() @@ -171,6 +191,7 @@ class SettingsViewController: UITableViewController { configureVoiceSearchCell() configureNetPCell() configurePrivacyPro() + applyTheme(ThemeManager.shared.currentTheme) internalUserDecider.isInternalUserPublisher.dropFirst().sink(receiveValue: { [weak self] _ in @@ -188,6 +209,7 @@ class SettingsViewController: UITableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + self.isModalInPresentation = false configureFireButtonAnimationCellAccessory() configureAddressBarPositionCell() configureTextSizeCell() @@ -204,6 +226,10 @@ class SettingsViewController: UITableViewController { #if NETWORK_PROTECTION updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) #endif + +#if SUBSCRIPTION + updatePrivacyProSettings() +#endif // Make sure multiline labels are correctly presented tableView.setNeedsLayout() @@ -373,13 +399,36 @@ class SettingsViewController: UITableViewController { } } - private func configurePrivacyPro() { +#if SUBSCRIPTION + + private func updatePrivacyProSettings() { + configurePrivacyPro() + UIView.transition(with: tableView, + duration: 0.35, + options: .transitionCrossDissolve, + animations: { self.tableView.reloadData() }) + } + + @objc private func configurePrivacyPro() { + let isAuthenticated = accountManger.isUserAuthenticated + + // Signup Cells + privacyProSignupCell.isHidden = isAuthenticated + privacyProLearnMoreCell.isHidden = isAuthenticated privacyProSignupCell.accessoryType = .none privacyProSignupCell.isUserInteractionEnabled = false + // Management Cells + privacyProVPNCell.isHidden = !isAuthenticated + privacyProDBPCell.isHidden = !isAuthenticated + privacyProITPCell.isHidden = !isAuthenticated + privacyProSubscriptionSettingsCell.isHidden = !isAuthenticated + + } - - +#endif + + private func configureNetPCell() { netPCell.isHidden = !shouldShowNetPCell #if NETWORK_PROTECTION @@ -398,10 +447,15 @@ class SettingsViewController: UITableViewController { switch NetworkProtectionAccessController().networkProtectionAccessType() { case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: netPCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle + privacyProVPNCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle case .waitlistInvited, .inviteCodeInvited: switch connectionStatus { - case .connected: netPCell.detailTextLabel?.text = UserText.netPCellConnected - default: netPCell.detailTextLabel?.text = UserText.netPCellDisconnected + case .connected: + netPCell.detailTextLabel?.text = UserText.netPCellConnected + privacyProVPNCell.detailTextLabel?.text = UserText.netPCellConnected + default: + netPCell.detailTextLabel?.text = UserText.netPCellDisconnected + privacyProVPNCell.detailTextLabel?.text = UserText.netPCellDisconnected } } } @@ -488,6 +542,7 @@ class SettingsViewController: UITableViewController { #if SUBSCRIPTION @available(iOS 15, *) private func showPrivacyPro() { + self.isModalInPresentation = true let privacyProview = SubscriptionFlowView(viewModel: SubscriptionFlowViewModel()) let hostingController = UIHostingController(rootView: privacyProview) navigationController?.pushViewController(hostingController, animated: true) @@ -536,7 +591,6 @@ class SettingsViewController: UITableViewController { #else break #endif - } case privacyProLearnMoreCell: @@ -546,7 +600,15 @@ class SettingsViewController: UITableViewController { #else break #endif - + } + + case privacyProVPNCell: + if #available(iOS 15, *) { +#if SUBSCRIPTION && NETWORK_PROTECTION + showNetP() +#else + break +#endif } default: break From 2547e9c6b4ac91ca26237086e176542ecbdf23f1 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 4 Dec 2023 15:40:16 +0100 Subject: [PATCH 65/99] Inject Fake Authentication --- DuckDuckGo/SubscriptionDebugViewController.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 5b8517fcd8..77454c95c6 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -45,6 +45,7 @@ final class SubscriptionDebugViewController: UITableViewController { enum AuthorizationRows: Int, CaseIterable { case showDetails case clearAuthData + case injectCredentials } @@ -70,6 +71,8 @@ final class SubscriptionDebugViewController: UITableViewController { cell.textLabel?.text = "Clear Authorization Data (Sign out)" case .showDetails: cell.textLabel?.text = "Show Account Details" + case .injectCredentials: + cell.textLabel?.text = "Simulate Authentication (Inject Fake token)" case .none: break } @@ -95,6 +98,7 @@ final class SubscriptionDebugViewController: UITableViewController { switch AuthorizationRows(rawValue: indexPath.row) { case .clearAuthData: clearAuthData() case .showDetails: showDetails() + case .injectCredentials: injectCredentials() default: break } case .none: @@ -120,7 +124,7 @@ final class SubscriptionDebugViewController: UITableViewController { accountManager.signOut() } - private func simulateActive() { + private func injectCredentials() { accountManager.storeAccount(token: "a-fake-token", email: "a.fake@email.com", externalID: "666") From a0f60b48d6a6eda67f935f33fd9517f21310453f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 14:07:11 +0100 Subject: [PATCH 66/99] Updated agent --- DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index e6f5c388cc..0e12056015 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -39,8 +39,10 @@ struct HeadlessWebview: UIViewRepresentable { configuration.userContentController = userContentController let webView = WKWebView(frame: .zero, configuration: configuration) - // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent + + // We're using the macOS agent as the config for iOS has not been deployed in test env webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)" + // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent // DispatchQueue.main.asyncAfter(deadline: .now() + 10) { webView.load(URLRequest(url: url)) From 855774ca11c2e173cc6eda6e54d4441e3e0a0196 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 14:30:34 +0100 Subject: [PATCH 67/99] Fix conflicts --- Core/FeatureFlag.swift | 8 +-- DuckDuckGo.xcodeproj/project.pbxproj | 65 ++++++++++++++----------- DuckDuckGo/SettingsViewController.swift | 22 ++++----- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index b3c019f78e..d218b4bd32 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -40,13 +40,7 @@ public enum FeatureFlag: String { extension FeatureFlag: FeatureFlagSourceProviding { public var source: FeatureFlagSource { switch self { - case .debugMenu, - .sync, - .appTrackingProtection, - .networkProtection, - .networkProtectionWaitlistAccess, - .networkProtectionWaitlistActive, - .privacyPro: + case .debugMenu, .sync, .appTrackingProtection, .privacyPro: return .internalOnly case .networkProtection: return .remoteReleasable(.feature(.networkProtection)) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 114e812fc8..262a4adebd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -148,7 +148,6 @@ 1E8146AD28C8ABF000D1AF63 /* TrackerAnimationLogicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8146A728C8AB3F00D1AF63 /* TrackerAnimationLogicTests.swift */; }; 1E8146AE28C8ABF400D1AF63 /* PrivacyIconLogicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8146A928C8AB8200D1AF63 /* PrivacyIconLogicTests.swift */; }; 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */; }; - 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */; }; 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8AD1C627BE9B2900ABA377 /* DownloadsListDataSource.swift */; }; 1E8AD1C927BFAD1500ABA377 /* DirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8AD1C827BFAD1500ABA377 /* DirectoryMonitor.swift */; }; 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8AD1CE27C0009F00ABA377 /* CompleteDownloadRow.swift */; }; @@ -545,7 +544,6 @@ 984147AE24F0261A00362052 /* Feedback.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B024F0261A00362052 /* Feedback.storyboard */; }; 984147B124F0264300362052 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B324F0264300362052 /* Home.storyboard */; }; 984147B424F0264B00362052 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B624F0264B00362052 /* Authentication.storyboard */; }; - 984147B724F0268D00362052 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */; }; 984147C024F026A300362052 /* Tab.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147C224F026A300362052 /* Tab.storyboard */; }; 984147C324F026C800362052 /* HomeRow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147C524F026C800362052 /* HomeRow.storyboard */; }; 984147C924F02E9E00362052 /* DaxOnboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147CB24F02E9E00362052 /* DaxOnboarding.storyboard */; }; @@ -571,7 +569,6 @@ 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */; }; 987130C8294AAB9F00AB05E0 /* BookmarksTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */; }; 987130C9294AAB9F00AB05E0 /* BookmarkUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */; }; - 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98728E812417E3300033960E /* BrokenSiteInfo.swift */; }; 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */; }; 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9874F9ED2187AFCE00CAF33D /* Themable.swift */; }; 9875E00722316B8400B1373F /* Instruments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9875E00622316B8400B1373F /* Instruments.swift */; }; @@ -781,6 +778,9 @@ D647EA442B1908E500BABD93 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3C2B1908E500BABD93 /* APIService.swift */; }; D647EA452B1908E500BABD93 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3D2B1908E500BABD93 /* AuthService.swift */; }; D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */; }; + D664C7882B28957500CBFA76 /* BrokenSiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7842B28957500CBFA76 /* BrokenSiteInfo.swift */; }; + D664C78A2B28957500CBFA76 /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7872B28957500CBFA76 /* PrivacyDashboardViewController.swift */; }; + D664C78D2B28972C00CBFA76 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D664C78B2B28972C00CBFA76 /* PrivacyDashboard.storyboard */; }; D69D3D862B1968220090E88A /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */; }; D69D3D882B1971770090E88A /* SubscriptionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */; }; D69D3D8A2B1973CC0090E88A /* SubscriptionDebugModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */; }; @@ -1213,7 +1213,6 @@ 1E8146A728C8AB3F00D1AF63 /* TrackerAnimationLogicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerAnimationLogicTests.swift; sourceTree = ""; }; 1E8146A928C8AB8200D1AF63 /* PrivacyIconLogicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyIconLogicTests.swift; sourceTree = ""; }; 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextSizeSettingsViewController.swift; sourceTree = ""; }; - 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; 1E8AD1C627BE9B2900ABA377 /* DownloadsListDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListDataSource.swift; sourceTree = ""; }; 1E8AD1C827BFAD1500ABA377 /* DirectoryMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryMonitor.swift; sourceTree = ""; }; 1E8AD1CE27C0009F00ABA377 /* CompleteDownloadRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteDownloadRow.swift; sourceTree = ""; }; @@ -1667,7 +1666,6 @@ 984147AF24F0261A00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Feedback.storyboard; sourceTree = ""; }; 984147B224F0264300362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Home.storyboard; sourceTree = ""; }; 984147B524F0264B00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Authentication.storyboard; sourceTree = ""; }; - 984147B824F0268D00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PrivacyDashboard.storyboard; sourceTree = ""; }; 984147C124F026A300362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Tab.storyboard; sourceTree = ""; }; 984147C424F026C800362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/HomeRow.storyboard; sourceTree = ""; }; 984147CA24F02E9E00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DaxOnboarding.storyboard; sourceTree = ""; }; @@ -2094,7 +2092,6 @@ 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBookmarksViewModelTests.swift; sourceTree = ""; }; 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksTestHelpers.swift; sourceTree = ""; }; 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkUtilsTests.swift; sourceTree = ""; }; - 98728E812417E3300033960E /* BrokenSiteInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSiteInfo.swift; sourceTree = ""; }; 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPreviewsSource.swift; sourceTree = ""; }; 9874F9ED2187AFCE00CAF33D /* Themable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themable.swift; sourceTree = ""; }; 9875E00622316B8400B1373F /* Instruments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instruments.swift; sourceTree = ""; }; @@ -2403,6 +2400,9 @@ D647EA3C2B1908E500BABD93 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; D647EA3D2B1908E500BABD93 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; + D664C7842B28957500CBFA76 /* BrokenSiteInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrokenSiteInfo.swift; sourceTree = ""; }; + D664C7872B28957500CBFA76 /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; + D664C78C2B28972C00CBFA76 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PrivacyDashboard.storyboard; sourceTree = ""; }; D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugViewController.swift; sourceTree = ""; }; D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugModel.swift; sourceTree = ""; }; @@ -3681,6 +3681,7 @@ 83ED3B8D1FA8E63700B47556 /* README.md */, 83ED3B8C1FA8E61D00B47556 /* ManualTestsScript.md */, 85A313962028E78A00327D00 /* release_notes.txt */, + D664C7822B2894BE00CBFA76 /* Recovered References */, ); sourceTree = ""; }; @@ -3733,7 +3734,7 @@ 85AE668C20971FCA0014CF04 /* Notifications */, F1C4A70C1E5771F800A6CA1B /* OmniBar */, F1AE54DB1F0425BB00D9A700 /* Privacy */, - 1E87615728A1515400C7C5CE /* PrivacyDashboard */, + D664C7832B28957500CBFA76 /* PrivacyDashboard */, D625296A2B15465E002A372F /* PrivacyPro */, 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */, C1B7B51D28941F160098FD6A /* RemoteMessaging */, @@ -4566,6 +4567,23 @@ path = Services; sourceTree = ""; }; + D664C7822B2894BE00CBFA76 /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; + D664C7832B28957500CBFA76 /* PrivacyDashboard */ = { + isa = PBXGroup; + children = ( + D664C78B2B28972C00CBFA76 /* PrivacyDashboard.storyboard */, + D664C7842B28957500CBFA76 /* BrokenSiteInfo.swift */, + D664C7872B28957500CBFA76 /* PrivacyDashboardViewController.swift */, + ); + path = PrivacyDashboard; + sourceTree = ""; + }; D6BBC4632B1665270027507E /* ViewModel */ = { isa = PBXGroup; children = ( @@ -5393,16 +5411,6 @@ name = UserInterface; sourceTree = ""; }; - F1DF09502B039E6E008CC908 /* PrivacyDashboard */ = { - isa = PBXGroup; - children = ( - 98728E812417E3300033960E /* BrokenSiteInfo.swift */, - 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */, - 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */, - ); - path = PrivacyDashboard; - sourceTree = ""; - }; F1E092B31E92A6B900732CCC /* Core */ = { isa = PBXGroup; children = ( @@ -6023,8 +6031,8 @@ 984147A824F0259000362052 /* Onboarding.storyboard in Resources */, AA4D6AF723DF0312007E8790 /* AppIconRed60x60@2x.png in Resources */, AA4D6AE923DE4D33007E8790 /* AppIconGreen29x29@3x.png in Resources */, + D664C78D2B28972C00CBFA76 /* PrivacyDashboard.storyboard in Resources */, 984147AE24F0261A00362052 /* Feedback.storyboard in Resources */, - 984147B724F0268D00362052 /* PrivacyDashboard.storyboard in Resources */, AA4D6AA723DE4CC4007E8790 /* AppIconBlue60x60@2x.png in Resources */, 1EEF12532851D32B003DDE57 /* trackers-2.json in Resources */, F176699F1E40BC86003D3222 /* Settings.storyboard in Resources */, @@ -6381,7 +6389,6 @@ 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */, - 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */, EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */, 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, @@ -6509,7 +6516,6 @@ D69D3D882B1971770090E88A /* SubscriptionDebugViewController.swift in Sources */, EE0798C52B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */, - 4BC6DD1C2A60E6AD001EC129 /* ReportBrokenSiteView.swift in Sources */, 31584616281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift in Sources */, C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */, F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */, @@ -6522,6 +6528,7 @@ 85058366219AE9EA00ED4EDB /* HomePageConfiguration.swift in Sources */, EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */, C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */, + D664C7882B28957500CBFA76 /* BrokenSiteInfo.swift in Sources */, 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */, @@ -6698,6 +6705,7 @@ 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, + D664C78A2B28957500CBFA76 /* PrivacyDashboardViewController.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, @@ -6753,7 +6761,6 @@ 1E7A711C2934EEBC00B7EA19 /* OmniBarNotification.swift in Sources */, 02EC02C429AFA33000557F1A /* AppTPBreakageFormView.swift in Sources */, F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, - 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, @@ -7468,14 +7475,6 @@ name = Authentication.storyboard; sourceTree = ""; }; - 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 984147B824F0268D00362052 /* Base */, - ); - name = PrivacyDashboard.storyboard; - sourceTree = ""; - }; 984147C224F026A300362052 /* Tab.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -7827,6 +7826,14 @@ name = InfoPlist.strings; sourceTree = ""; }; + D664C78B2B28972C00CBFA76 /* PrivacyDashboard.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D664C78C2B28972C00CBFA76 /* Base */, + ); + name = PrivacyDashboard.storyboard; + sourceTree = ""; + }; EEDFE2DC2AC6ED4F00F0E19C /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index b7bd848399..449c3f67aa 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -413,16 +413,16 @@ class SettingsViewController: UITableViewController { let isAuthenticated = accountManger.isUserAuthenticated // Signup Cells - privacyProSignupCell.isHidden = isAuthenticated - privacyProLearnMoreCell.isHidden = isAuthenticated - privacyProSignupCell.accessoryType = .none - privacyProSignupCell.isUserInteractionEnabled = false + // privacyProSignupCell.isHidden = isAuthenticated + // privacyProLearnMoreCell.isHidden = isAuthenticated + // privacyProSignupCell.accessoryType = .none + // privacyProSignupCell.isUserInteractionEnabled = false // Management Cells - privacyProVPNCell.isHidden = !isAuthenticated - privacyProDBPCell.isHidden = !isAuthenticated - privacyProITPCell.isHidden = !isAuthenticated - privacyProSubscriptionSettingsCell.isHidden = !isAuthenticated + // privacyProVPNCell.isHidden = !isAuthenticated + // privacyProDBPCell.isHidden = !isAuthenticated + // privacyProITPCell.isHidden = !isAuthenticated + // privacyProSubscriptionSettingsCell.isHidden = !isAuthenticated } @@ -447,15 +447,15 @@ class SettingsViewController: UITableViewController { switch NetworkProtectionAccessController().networkProtectionAccessType() { case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: netPCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle - privacyProVPNCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle + // privacyProVPNCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle case .waitlistInvited, .inviteCodeInvited: switch connectionStatus { case .connected: netPCell.detailTextLabel?.text = UserText.netPCellConnected - privacyProVPNCell.detailTextLabel?.text = UserText.netPCellConnected + // privacyProVPNCell.detailTextLabel?.text = UserText.netPCellConnected default: netPCell.detailTextLabel?.text = UserText.netPCellDisconnected - privacyProVPNCell.detailTextLabel?.text = UserText.netPCellDisconnected + // privacyProVPNCell.detailTextLabel?.text = UserText.netPCellDisconnected } } } From 90c084984d61c99e7fbfed1282a6888fa4165b37 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 14:48:43 +0100 Subject: [PATCH 68/99] Added compiler conditional --- DuckDuckGo.xcodeproj/project.pbxproj | 883 ++++++------------ ...scriptionPagesUseSubscriptionFeature.swift | 2 + .../ViewModel/SubscriptionFlowViewModel.swift | 3 + .../Views/SubscriptionFlowView.swift | 2 + 4 files changed, 274 insertions(+), 616 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 262a4adebd..560263a498 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -148,6 +148,7 @@ 1E8146AD28C8ABF000D1AF63 /* TrackerAnimationLogicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8146A728C8AB3F00D1AF63 /* TrackerAnimationLogicTests.swift */; }; 1E8146AE28C8ABF400D1AF63 /* PrivacyIconLogicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8146A928C8AB8200D1AF63 /* PrivacyIconLogicTests.swift */; }; 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */; }; + 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */; }; 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8AD1C627BE9B2900ABA377 /* DownloadsListDataSource.swift */; }; 1E8AD1C927BFAD1500ABA377 /* DirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8AD1C827BFAD1500ABA377 /* DirectoryMonitor.swift */; }; 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8AD1CE27C0009F00ABA377 /* CompleteDownloadRow.swift */; }; @@ -428,7 +429,6 @@ 85514FFD2372DA0100DBC528 /* ios13-home-row.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 85514FFC2372DA0000DBC528 /* ios13-home-row.mp4 */; }; 8551912724746EDC0010FDD0 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */; }; 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */; }; - 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */; }; 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */; }; 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */; }; 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */; }; @@ -544,6 +544,7 @@ 984147AE24F0261A00362052 /* Feedback.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B024F0261A00362052 /* Feedback.storyboard */; }; 984147B124F0264300362052 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B324F0264300362052 /* Home.storyboard */; }; 984147B424F0264B00362052 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B624F0264B00362052 /* Authentication.storyboard */; }; + 984147B724F0268D00362052 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */; }; 984147C024F026A300362052 /* Tab.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147C224F026A300362052 /* Tab.storyboard */; }; 984147C324F026C800362052 /* HomeRow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147C524F026C800362052 /* HomeRow.storyboard */; }; 984147C924F02E9E00362052 /* DaxOnboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147CB24F02E9E00362052 /* DaxOnboarding.storyboard */; }; @@ -569,6 +570,7 @@ 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */; }; 987130C8294AAB9F00AB05E0 /* BookmarksTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */; }; 987130C9294AAB9F00AB05E0 /* BookmarkUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */; }; + 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98728E812417E3300033960E /* BrokenSiteInfo.swift */; }; 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */; }; 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9874F9ED2187AFCE00CAF33D /* Themable.swift */; }; 9875E00722316B8400B1373F /* Instruments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9875E00622316B8400B1373F /* Instruments.swift */; }; @@ -577,7 +579,6 @@ 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9880722925FA497B0039EF4B /* MenuButton.swift */; }; 9880723725FA4E450039EF4B /* menu_dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 9880723525FA4E440039EF4B /* menu_dark.json */; }; 9880723825FA4E450039EF4B /* menu_light.json in Resources */ = {isa = PBXBuildFile; fileRef = 9880723625FA4E450039EF4B /* menu_light.json */; }; - 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */; }; 9887DC252354D2AA005C85F5 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9887DC242354D2AA005C85F5 /* Database.swift */; }; 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9888F77A2224980500C46159 /* FeedbackViewController.swift */; }; 988AC355257E47C100793C64 /* RequeryLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988AC354257E47C100793C64 /* RequeryLogic.swift */; }; @@ -762,34 +763,48 @@ CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DDE29A6736A00832877 /* APIHeadersTests.swift */; }; CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; - D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529742B155BCB002A372F /* SubscriptionFlowView.swift */; }; - D62529922B15E44A002A372F /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */; }; - D62529942B15E454002A372F /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62529932B15E454002A372F /* URL+Subscription.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; - D637D1B62B19028E00C94784 /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B22B19028E00C94784 /* AppStoreAccountManagementFlow.swift */; }; - D637D1B72B19028E00C94784 /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B32B19028E00C94784 /* AppStoreRestoreFlow.swift */; }; - D637D1B82B19028E00C94784 /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B42B19028E00C94784 /* PurchaseFlow.swift */; }; - D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D637D1B52B19028E00C94784 /* AppStorePurchaseFlow.swift */; }; - D647EA3F2B1908E500BABD93 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA352B1908E500BABD93 /* Logging.swift */; }; - D647EA402B1908E500BABD93 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA362B1908E500BABD93 /* AccountManager.swift */; }; - D647EA412B1908E500BABD93 /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA382B1908E500BABD93 /* AccountKeychainStorage.swift */; }; - D647EA422B1908E500BABD93 /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA392B1908E500BABD93 /* AccountStorage.swift */; }; - D647EA432B1908E500BABD93 /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3B2B1908E500BABD93 /* SubscriptionService.swift */; }; - D647EA442B1908E500BABD93 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3C2B1908E500BABD93 /* APIService.swift */; }; - D647EA452B1908E500BABD93 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3D2B1908E500BABD93 /* AuthService.swift */; }; - D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */; }; - D664C7882B28957500CBFA76 /* BrokenSiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7842B28957500CBFA76 /* BrokenSiteInfo.swift */; }; - D664C78A2B28957500CBFA76 /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7872B28957500CBFA76 /* PrivacyDashboardViewController.swift */; }; - D664C78D2B28972C00CBFA76 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D664C78B2B28972C00CBFA76 /* PrivacyDashboard.storyboard */; }; - D69D3D862B1968220090E88A /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */; }; - D69D3D882B1971770090E88A /* SubscriptionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */; }; - D69D3D8A2B1973CC0090E88A /* SubscriptionDebugModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */; }; - D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */; }; - D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4612B1663190027507E /* HeadlessWebView.swift */; }; - D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */; }; - D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */; }; - D6BBC4692B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */; }; - D6FC2BAF2B178FF800744491 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6FC2BAE2B178FF800744491 /* StoreKit.framework */; }; + D664C7B62B289AA200CBFA76 /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */; }; + D664C7B72B289AA200CBFA76 /* PrivacyPro.storekit in Resources */ = {isa = PBXBuildFile; fileRef = D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */; }; + D664C7B82B289AA200CBFA76 /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7972B289AA000CBFA76 /* URL+Subscription.swift */; }; + D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */; }; + D664C7BA2B289AA200CBFA76 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79A2B289AA000CBFA76 /* Logging.swift */; }; + D664C7BB2B289AA200CBFA76 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79B2B289AA000CBFA76 /* AccountManager.swift */; }; + D664C7BC2B289AA200CBFA76 /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */; }; + D664C7BD2B289AA200CBFA76 /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79E2B289AA000CBFA76 /* AccountStorage.swift */; }; + D664C7BE2B289AA200CBFA76 /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */; }; + D664C7BF2B289AA200CBFA76 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A12B289AA000CBFA76 /* APIService.swift */; }; + D664C7C02B289AA200CBFA76 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A22B289AA000CBFA76 /* AuthService.swift */; }; + D664C7C12B289AA200CBFA76 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */; }; + D664C7C22B289AA200CBFA76 /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */; }; + D664C7C32B289AA200CBFA76 /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */; }; + D664C7C42B289AA200CBFA76 /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */; }; + D664C7C52B289AA200CBFA76 /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */; }; + D664C7C62B289AA200CBFA76 /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */; }; + D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */; }; + D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */; }; + D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */; }; + D664C7CA2B289AA200CBFA76 /* SimpleUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */; }; + D664C7CB2B289AA200CBFA76 /* SubscriptionOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B22B289AA000CBFA76 /* SubscriptionOptions.swift */; }; + D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */; }; + D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B42B289AA000CBFA76 /* TestUserScript.swift */; }; + D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; + D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; + D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; + D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; + D6E83C382B1F2236006C8AFB /* SettingsGeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */; }; + D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */; }; + D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */; }; + D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */; }; + D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */; }; + D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */; }; + D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */; }; + D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */; }; + D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; + D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */; }; + D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C632B238432006C8AFB /* SettingsAboutView.swift */; }; + D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */; }; + D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C672B23B6A3006C8AFB /* FontSettings.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -887,7 +902,6 @@ F198D7981E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; }; F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A568391E70F98E0081082E /* AutocompleteRequest.swift */; }; F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A886771F29394E0096251E /* WebCacheManager.swift */; }; - F1AB2B421E3F7D5C00868554 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */; }; F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */; }; F1C4A70E1E57725800A6CA1B /* OmniBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4A70D1E57725800A6CA1B /* OmniBar.swift */; }; @@ -921,7 +935,6 @@ F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */; }; F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */; }; F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */; }; - F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */; }; F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */; }; F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47E53D8250A97330037C686 /* OnboardingDefaultBroswerViewController.swift */; }; F47E53DB250A9A1C0037C686 /* Onboarding.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */; }; @@ -1213,6 +1226,7 @@ 1E8146A728C8AB3F00D1AF63 /* TrackerAnimationLogicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerAnimationLogicTests.swift; sourceTree = ""; }; 1E8146A928C8AB8200D1AF63 /* PrivacyIconLogicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyIconLogicTests.swift; sourceTree = ""; }; 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextSizeSettingsViewController.swift; sourceTree = ""; }; + 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; 1E8AD1C627BE9B2900ABA377 /* DownloadsListDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListDataSource.swift; sourceTree = ""; }; 1E8AD1C827BFAD1500ABA377 /* DirectoryMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryMonitor.swift; sourceTree = ""; }; 1E8AD1CE27C0009F00ABA377 /* CompleteDownloadRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteDownloadRow.swift; sourceTree = ""; }; @@ -1489,7 +1503,6 @@ 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerRadarIntegrationTests.swift; sourceTree = ""; }; 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+PDFRendering.swift"; sourceTree = ""; }; - 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarPositionSettingsViewController.swift; sourceTree = ""; }; 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherTransition.swift; sourceTree = ""; }; 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserChromeManager.swift; sourceTree = ""; }; 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotification.swift; sourceTree = ""; }; @@ -1666,6 +1679,7 @@ 984147AF24F0261A00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Feedback.storyboard; sourceTree = ""; }; 984147B224F0264300362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Home.storyboard; sourceTree = ""; }; 984147B524F0264B00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Authentication.storyboard; sourceTree = ""; }; + 984147B824F0268D00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PrivacyDashboard.storyboard; sourceTree = ""; }; 984147C124F026A300362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Tab.storyboard; sourceTree = ""; }; 984147C424F026C800362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/HomeRow.storyboard; sourceTree = ""; }; 984147CA24F02E9E00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DaxOnboarding.storyboard; sourceTree = ""; }; @@ -2092,6 +2106,7 @@ 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBookmarksViewModelTests.swift; sourceTree = ""; }; 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksTestHelpers.swift; sourceTree = ""; }; 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkUtilsTests.swift; sourceTree = ""; }; + 98728E812417E3300033960E /* BrokenSiteInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSiteInfo.swift; sourceTree = ""; }; 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPreviewsSource.swift; sourceTree = ""; }; 9874F9ED2187AFCE00CAF33D /* Themable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themable.swift; sourceTree = ""; }; 9875E00622316B8400B1373F /* Instruments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instruments.swift; sourceTree = ""; }; @@ -2137,7 +2152,6 @@ 9880722925FA497B0039EF4B /* MenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuButton.swift; sourceTree = ""; }; 9880723525FA4E440039EF4B /* menu_dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = menu_dark.json; sourceTree = ""; }; 9880723625FA4E450039EF4B /* menu_light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = menu_light.json; sourceTree = ""; }; - 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsViewController.swift; sourceTree = ""; }; 9887DC242354D2AA005C85F5 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 9888F77A2224980500C46159 /* FeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewController.swift; sourceTree = ""; }; 988AC354257E47C100793C64 /* RequeryLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequeryLogic.swift; sourceTree = ""; }; @@ -2384,35 +2398,48 @@ CBF14FC227970072001D94D0 /* HomeMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageView.swift; sourceTree = ""; }; CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; - D62529742B155BCB002A372F /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; - D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; - D62529932B15E454002A372F /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; - D637D1B22B19028E00C94784 /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; - D637D1B32B19028E00C94784 /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; - D637D1B42B19028E00C94784 /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; - D637D1B52B19028E00C94784 /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; - D647EA352B1908E500BABD93 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - D647EA362B1908E500BABD93 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; - D647EA382B1908E500BABD93 /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; - D647EA392B1908E500BABD93 /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; - D647EA3B2B1908E500BABD93 /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; - D647EA3C2B1908E500BABD93 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; - D647EA3D2B1908E500BABD93 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; - D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; - D664C7842B28957500CBFA76 /* BrokenSiteInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrokenSiteInfo.swift; sourceTree = ""; }; - D664C7872B28957500CBFA76 /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; - D664C78C2B28972C00CBFA76 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PrivacyDashboard.storyboard; sourceTree = ""; }; - D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; - D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugViewController.swift; sourceTree = ""; }; - D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugModel.swift; sourceTree = ""; }; - D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; - D6BA61002B18EE5E0032C67F /* PrivacyPro.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; - D6BBC4612B1663190027507E /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; - D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; - D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; - D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; - D6FC2BAE2B178FF800744491 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; + D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; + D664C7972B289AA000CBFA76 /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; + D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; + D664C79A2B289AA000CBFA76 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D664C79B2B289AA000CBFA76 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; + D664C79E2B289AA000CBFA76 /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; + D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; + D664C7A12B289AA000CBFA76 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; + D664C7A22B289AA000CBFA76 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; + D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; + D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; + D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; + D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; + D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; + D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; + D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; + D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; + D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleUserScript.swift; sourceTree = ""; }; + D664C7B22B289AA000CBFA76 /* SubscriptionOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionOptions.swift; sourceTree = ""; }; + D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; + D664C7B42B289AA000CBFA76 /* TestUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUserScript.swift; sourceTree = ""; }; + D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; + D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; + D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsGeneralView.swift; sourceTree = ""; }; + D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSyncView.swift; sourceTree = ""; }; + D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLoginsView.swift; sourceTree = ""; }; + D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppeareanceView.swift; sourceTree = ""; }; + D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHostingController.swift; sourceTree = ""; }; + D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLegacyViewProvider.swift; sourceTree = ""; }; + D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; + D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCustomizeView.swift; sourceTree = ""; }; + D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; + D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsMoreView.swift; sourceTree = ""; }; + D6E83C632B238432006C8AFB /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; + D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDebugView.swift; sourceTree = ""; }; + D6E83C672B23B6A3006C8AFB /* FontSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSettings.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -2542,7 +2569,6 @@ F1A568391E70F98E0081082E /* AutocompleteRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteRequest.swift; sourceTree = ""; }; F1A886771F29394E0096251E /* WebCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCacheManager.swift; sourceTree = ""; }; F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; F1B745211E549D550072547E /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIColorExtension.swift; path = ../Core/UIColorExtension.swift; sourceTree = ""; }; F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialSettings.swift; sourceTree = ""; }; @@ -2577,7 +2603,6 @@ F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptView.swift; sourceTree = ""; }; F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModel.swift; sourceTree = ""; }; F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewController.swift; sourceTree = ""; }; - F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireButtonAnimationSettingsViewController.swift; sourceTree = ""; }; F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainItemsDebugViewController.swift; sourceTree = ""; }; F47E53D8250A97330037C686 /* OnboardingDefaultBroswerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDefaultBroswerViewController.swift; sourceTree = ""; }; F47E53DA250A9A1C0037C686 /* Onboarding.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Onboarding.xcassets; sourceTree = ""; }; @@ -2638,7 +2663,6 @@ 85875B6129912A9900115F05 /* SyncUI in Frameworks */, F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, - D6FC2BAF2B178FF800744491 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3681,7 +3705,6 @@ 83ED3B8D1FA8E63700B47556 /* README.md */, 83ED3B8C1FA8E61D00B47556 /* ManualTestsScript.md */, 85A313962028E78A00327D00 /* release_notes.txt */, - D664C7822B2894BE00CBFA76 /* Recovered References */, ); sourceTree = ""; }; @@ -3734,8 +3757,8 @@ 85AE668C20971FCA0014CF04 /* Notifications */, F1C4A70C1E5771F800A6CA1B /* OmniBar */, F1AE54DB1F0425BB00D9A700 /* Privacy */, - D664C7832B28957500CBFA76 /* PrivacyDashboard */, - D625296A2B15465E002A372F /* PrivacyPro */, + D664C7922B289AA000CBFA76 /* PrivacyPro */, + F1DF09502B039E6E008CC908 /* PrivacyDashboard */, 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */, C1B7B51D28941F160098FD6A /* RemoteMessaging */, F1AB2B401E3F75A000868554 /* Settings */, @@ -3832,36 +3855,23 @@ name = Renderers; sourceTree = ""; }; - 85449EF623FDA03100512AAF /* UI */ = { + 85449EF623FDA03100512AAF /* UIkit */ = { isa = PBXGroup; children = ( + F176699D1E40BC86003D3222 /* Settings.storyboard */, F1CDD3F11F16911700BE0581 /* AboutViewController.swift */, - 855D45D22ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift */, AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */, AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */, 98F0FC1F21FF18E700CE77AB /* AutoClearSettingsViewController.swift */, 1EE7C298294227EC0026C8CB /* AutoconsentSettingsViewController.swift */, 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */, - F456B3B425810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift */, 85449EF423FDA02800512AAF /* KeyboardSettingsViewController.swift */, 8540BD5523D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift */, - F176699D1E40BC86003D3222 /* Settings.storyboard */, - F1AB2B411E3F7D5C00868554 /* SettingsViewController.swift */, 1E865AEF272042DB001C74F3 /* TextSizeSettingsViewController.swift */, - 9881439B23326DC200573F7C /* ThemeSettingsViewController.swift */, 8531A08D1F9950E6000484F0 /* UnprotectedSitesViewController.swift */, + D6E83C672B23B6A3006C8AFB /* FontSettings.swift */, ); - name = UI; - sourceTree = ""; - }; - 85449EF723FDA03D00512AAF /* Model */ = { - isa = PBXGroup; - children = ( - 85449EFC23FDA71F00512AAF /* KeyboardSettings.swift */, - 4B53648926718D0E001AA041 /* EmailWaitlist.swift */, - D69D3D892B1973CC0090E88A /* SubscriptionDebugModel.swift */, - ); - name = Model; + name = UIkit; sourceTree = ""; }; 85482D892462DCD100EDEDD1 /* OpenAction */ = { @@ -3890,7 +3900,6 @@ F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */, 983D71B02A286E810072E26D /* SyncDebugViewController.swift */, EE72CA842A862D000043B5B3 /* NetworkProtectionDebugViewController.swift */, - D69D3D872B1971770090E88A /* SubscriptionDebugViewController.swift */, ); name = Debug; sourceTree = ""; @@ -4483,122 +4492,156 @@ name = Resources; sourceTree = ""; }; - D625296A2B15465E002A372F /* PrivacyPro */ = { + D664C7922B289AA000CBFA76 /* PrivacyPro */ = { isa = PBXGroup; children = ( - D6BA61002B18EE5E0032C67F /* PrivacyPro.storekit */, - D6BBC4632B1665270027507E /* ViewModel */, - D625296B2B15467E002A372F /* Views */, - D647EA342B1908E500BABD93 /* Subscription */, - D62529862B15E449002A372F /* Purchase */, - D62529952B15E4B9002A372F /* PurchaseFlows */, - D62529882B15E449002A372F /* UserScripts */, - D6BBC46A2B16B68C0027507E /* Extensions */, - D6BA60ED2B17BAE20032C67F /* SubscriptionUserText.swift */, + D664C7932B289AA000CBFA76 /* ViewModel */, + D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */, + D664C7962B289AA000CBFA76 /* Extensions */, + D664C7992B289AA000CBFA76 /* Subscription */, + D664C7A42B289AA000CBFA76 /* Purchase */, + D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */, + D664C7A62B289AA000CBFA76 /* Account */, + D664C7A72B289AA000CBFA76 /* PurchaseFlows */, + D664C7AC2B289AA000CBFA76 /* Views */, + D664C7B02B289AA000CBFA76 /* UserScripts */, ); path = PrivacyPro; sourceTree = ""; }; - D625296B2B15467E002A372F /* Views */ = { + D664C7932B289AA000CBFA76 /* ViewModel */ = { isa = PBXGroup; children = ( - D6BBC4612B1663190027507E /* HeadlessWebView.swift */, - D62529742B155BCB002A372F /* SubscriptionFlowView.swift */, - D69D3D852B1968220090E88A /* PurchaseInProgressView.swift */, + D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */, ); - path = Views; + path = ViewModel; sourceTree = ""; }; - D62529862B15E449002A372F /* Purchase */ = { + D664C7962B289AA000CBFA76 /* Extensions */ = { isa = PBXGroup; children = ( + D664C7972B289AA000CBFA76 /* URL+Subscription.swift */, + D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, ); - path = Purchase; + path = Extensions; sourceTree = ""; }; - D62529882B15E449002A372F /* UserScripts */ = { + D664C7992B289AA000CBFA76 /* Subscription */ = { isa = PBXGroup; children = ( - D62529892B15E449002A372F /* SubscriptionPagesUserScript.swift */, - D6BBC4682B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift */, + D664C79A2B289AA000CBFA76 /* Logging.swift */, + D664C79B2B289AA000CBFA76 /* AccountManager.swift */, + D664C79C2B289AA000CBFA76 /* AccountStorage */, + D664C79F2B289AA000CBFA76 /* Services */, + D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */, ); - path = UserScripts; + path = Subscription; sourceTree = ""; }; - D62529952B15E4B9002A372F /* PurchaseFlows */ = { + D664C79C2B289AA000CBFA76 /* AccountStorage */ = { isa = PBXGroup; children = ( - D637D1B22B19028E00C94784 /* AppStoreAccountManagementFlow.swift */, - D637D1B52B19028E00C94784 /* AppStorePurchaseFlow.swift */, - D637D1B32B19028E00C94784 /* AppStoreRestoreFlow.swift */, - D637D1B42B19028E00C94784 /* PurchaseFlow.swift */, + D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */, + D664C79E2B289AA000CBFA76 /* AccountStorage.swift */, ); - path = PurchaseFlows; + path = AccountStorage; sourceTree = ""; }; - D647EA342B1908E500BABD93 /* Subscription */ = { + D664C79F2B289AA000CBFA76 /* Services */ = { isa = PBXGroup; children = ( - D647EA352B1908E500BABD93 /* Logging.swift */, - D647EA362B1908E500BABD93 /* AccountManager.swift */, - D647EA372B1908E500BABD93 /* AccountStorage */, - D647EA3A2B1908E500BABD93 /* Services */, - D647EA3E2B1908E500BABD93 /* PurchaseManager.swift */, + D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */, + D664C7A12B289AA000CBFA76 /* APIService.swift */, + D664C7A22B289AA000CBFA76 /* AuthService.swift */, ); - path = Subscription; + path = Services; sourceTree = ""; }; - D647EA372B1908E500BABD93 /* AccountStorage */ = { + D664C7A42B289AA000CBFA76 /* Purchase */ = { isa = PBXGroup; children = ( - D647EA382B1908E500BABD93 /* AccountKeychainStorage.swift */, - D647EA392B1908E500BABD93 /* AccountStorage.swift */, ); - path = AccountStorage; + path = Purchase; sourceTree = ""; }; - D647EA3A2B1908E500BABD93 /* Services */ = { + D664C7A62B289AA000CBFA76 /* Account */ = { isa = PBXGroup; children = ( - D647EA3B2B1908E500BABD93 /* SubscriptionService.swift */, - D647EA3C2B1908E500BABD93 /* APIService.swift */, - D647EA3D2B1908E500BABD93 /* AuthService.swift */, ); - path = Services; + path = Account; sourceTree = ""; }; - D664C7822B2894BE00CBFA76 /* Recovered References */ = { + D664C7A72B289AA000CBFA76 /* PurchaseFlows */ = { isa = PBXGroup; children = ( + D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */, + D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */, + D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */, + D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */, ); - name = "Recovered References"; + path = PurchaseFlows; sourceTree = ""; }; - D664C7832B28957500CBFA76 /* PrivacyDashboard */ = { + D664C7AC2B289AA000CBFA76 /* Views */ = { isa = PBXGroup; children = ( - D664C78B2B28972C00CBFA76 /* PrivacyDashboard.storyboard */, - D664C7842B28957500CBFA76 /* BrokenSiteInfo.swift */, - D664C7872B28957500CBFA76 /* PrivacyDashboardViewController.swift */, + D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, + D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, + D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */, ); - path = PrivacyDashboard; + path = Views; sourceTree = ""; }; - D6BBC4632B1665270027507E /* ViewModel */ = { + D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D6BBC4642B1665EB0027507E /* SubscriptionFlowViewModel.swift */, + D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, + D664C7B22B289AA000CBFA76 /* SubscriptionOptions.swift */, + D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, + D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, ); - path = ViewModel; + path = UserScripts; sourceTree = ""; }; - D6BBC46A2B16B68C0027507E /* Extensions */ = { + D6E83C322B1F1279006C8AFB /* Sections */ = { isa = PBXGroup; children = ( - D62529932B15E454002A372F /* URL+Subscription.swift */, - D6BBC4662B16B4040027507E /* WKUserContentController+Handler.swift */, + D6E83C372B1F2236006C8AFB /* SettingsGeneralView.swift */, + D6E83C392B1F231A006C8AFB /* SettingsSyncView.swift */, + D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */, + D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, + D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, + D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, + D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */, + D6E83C632B238432006C8AFB /* SettingsAboutView.swift */, + D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */, ); - path = Extensions; + name = Sections; + sourceTree = ""; + }; + D6E83C3B2B1F27BA006C8AFB /* Views */ = { + isa = PBXGroup; + children = ( + D6E83C472B20C812006C8AFB /* SettingsHostingController.swift */, + D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */, + D6E83C302B1EA309006C8AFB /* SettingsCell.swift */, + D6E83C322B1F1279006C8AFB /* Sections */, + 85449EF623FDA03100512AAF /* UIkit */, + ); + name = Views; + sourceTree = ""; + }; + D6E83C492B20C883006C8AFB /* Model */ = { + isa = PBXGroup; + children = ( + D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */, + D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */, + D6E83C552B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift */, + 85449EFC23FDA71F00512AAF /* KeyboardSettings.swift */, + 4B53648926718D0E001AA041 /* EmailWaitlist.swift */, + ); + name = Model; sourceTree = ""; }; EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */ = { @@ -5218,7 +5261,6 @@ F1AA545F1E48D90700223211 /* Frameworks */ = { isa = PBXGroup; children = ( - D6FC2BAE2B178FF800744491 /* StoreKit.framework */, F1AA54601E48D90700223211 /* NotificationCenter.framework */, 8512EA4E24ED30D20073EE19 /* WidgetKit.framework */, 8512EA5024ED30D20073EE19 /* SwiftUI.framework */, @@ -5230,9 +5272,9 @@ F1AB2B401E3F75A000868554 /* Settings */ = { isa = PBXGroup; children = ( + D6E83C492B20C883006C8AFB /* Model */, + D6E83C3B2B1F27BA006C8AFB /* Views */, 858566F1252E55AE007501B8 /* Debug */, - 85449EF723FDA03D00512AAF /* Model */, - 85449EF623FDA03100512AAF /* UI */, ); name = Settings; sourceTree = ""; @@ -5411,6 +5453,16 @@ name = UserInterface; sourceTree = ""; }; + F1DF09502B039E6E008CC908 /* PrivacyDashboard */ = { + isa = PBXGroup; + children = ( + 98728E812417E3300033960E /* BrokenSiteInfo.swift */, + 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */, + 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */, + ); + path = PrivacyDashboard; + sourceTree = ""; + }; F1E092B31E92A6B900732CCC /* Core */ = { isa = PBXGroup; children = ( @@ -6013,6 +6065,7 @@ 1EE411FD2858B9300003FE64 /* dark-trackers-2.json in Resources */, AA4D6ABC23DE4D15007E8790 /* AppIconYellow60x60@3x.png in Resources */, 98D98A9B25ED954100D8E3DF /* BrowsingMenuButton.xib in Resources */, + D664C7B72B289AA200CBFA76 /* PrivacyPro.storekit in Resources */, AA4D6AA823DE4CC4007E8790 /* AppIconBlue40x40@2x.png in Resources */, AA4D6AE723DE4D33007E8790 /* AppIconGreen29x29@2x.png in Resources */, 1EE412002858B9300003FE64 /* dark-shield-dot.json in Resources */, @@ -6031,8 +6084,8 @@ 984147A824F0259000362052 /* Onboarding.storyboard in Resources */, AA4D6AF723DF0312007E8790 /* AppIconRed60x60@2x.png in Resources */, AA4D6AE923DE4D33007E8790 /* AppIconGreen29x29@3x.png in Resources */, - D664C78D2B28972C00CBFA76 /* PrivacyDashboard.storyboard in Resources */, 984147AE24F0261A00362052 /* Feedback.storyboard in Resources */, + 984147B724F0268D00362052 /* PrivacyDashboard.storyboard in Resources */, AA4D6AA723DE4CC4007E8790 /* AppIconBlue60x60@2x.png in Resources */, 1EEF12532851D32B003DDE57 /* trackers-2.json in Resources */, F176699F1E40BC86003D3222 /* Settings.storyboard in Resources */, @@ -6389,6 +6442,7 @@ 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */, + 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */, EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */, 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, @@ -6397,18 +6451,22 @@ 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, + D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, 31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */, EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 0290472029E708B70008FE3C /* AppTPManageTrackersViewModel.swift in Sources */, - 9881439C23326DC200573F7C /* ThemeSettingsViewController.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, + D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, + D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */, + D664C7BB2B289AA200CBFA76 /* AccountManager.swift in Sources */, 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */, + D664C7BE2B289AA200CBFA76 /* SubscriptionService.swift in Sources */, 4BBBBA922B03291700D965DA /* VPNWaitlistUserText.swift in Sources */, F4E1936625AF722F001D2666 /* HighlightCutOutView.swift in Sources */, 1E162605296840D80004127F /* Triangle.swift in Sources */, @@ -6418,21 +6476,23 @@ 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */, F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */, 3151F0F02735802800226F58 /* VoiceSearchViewController.swift in Sources */, + D664C7C02B289AA200CBFA76 /* AuthService.swift in Sources */, 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */, F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, B6BA95C328891E33004ABA20 /* BrowsingMenuAnimator.swift in Sources */, + D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, + D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, 854A01332A558B3A00FCC628 /* UIView+Constraints.swift in Sources */, C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */, - D647EA432B1908E500BABD93 /* SubscriptionService.swift in Sources */, 83134D7D20E2D725006CE65D /* FeedbackSender.swift in Sources */, + D6E83C382B1F2236006C8AFB /* SettingsGeneralView.swift in Sources */, B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */, - D637D1B62B19028E00C94784 /* AppStoreAccountManagementFlow.swift in Sources */, 314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */, 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */, 4BBBBA8F2B031B4200D965DA /* VPNWaitlistView.swift in Sources */, @@ -6442,14 +6502,12 @@ EE458D0D2AB1DA4600FC651A /* EventMapping+NetworkProtectionError.swift in Sources */, 85047C752A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift in Sources */, F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */, - D62529942B15E454002A372F /* URL+Subscription.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, + D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */, F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */, - D647EA402B1908E500BABD93 /* AccountManager.swift in Sources */, + D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, - D647EA412B1908E500BABD93 /* AccountKeychainStorage.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, - D69D3D8A2B1973CC0090E88A /* SubscriptionDebugModel.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */, F13B4BD31F1822C700814661 /* Tab.swift in Sources */, @@ -6462,7 +6520,6 @@ CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, 85F200002215C17B006BB258 /* FindInPage.swift in Sources */, F1386BA41E6846C40062FC3C /* TabDelegate.swift in Sources */, - D6BBC4692B16B5F70027507E /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, C1B924B72ACD6E6800EE7B06 /* AutofillNeverSavedTableViewCell.swift in Sources */, 020108A929A7C1CD00644F9D /* AppTrackerImageCache.swift in Sources */, 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */, @@ -6470,6 +6527,7 @@ C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, + D664C7C12B289AA200CBFA76 /* PurchaseManager.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, 020108A329A561C300644F9D /* AppTPActivityView.swift in Sources */, @@ -6488,6 +6546,7 @@ 1E908BF329827C480008C8F3 /* AutoconsentManagement.swift in Sources */, CB9B8739278C8E72001F4906 /* WidgetEducationViewController.swift in Sources */, F4D9C4FA25117A0F00814B71 /* HomeMessageStorage.swift in Sources */, + D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */, AA3D854523D9942200788410 /* AppIconSettingsViewController.swift in Sources */, 85C297042476C1FD0063A335 /* DaxDialogsSettings.swift in Sources */, 8505836F219F424500ED4EDB /* UIViewExtension.swift in Sources */, @@ -6508,14 +6567,10 @@ 37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */, 85DFEDED24C7CCA500973FE7 /* AppWidthObserver.swift in Sources */, 4B6484F327FD1E350050A7A1 /* MenuControllerView.swift in Sources */, - D637D1B82B19028E00C94784 /* PurchaseFlow.swift in Sources */, 1EE7C299294227EC0026C8CB /* AutoconsentSettingsViewController.swift in Sources */, 4BCD14632B05AF2B000B1E4C /* NetworkProtectionAccessController.swift in Sources */, 1E8AD1D527C2E22900ABA377 /* DownloadsListSectionViewModel.swift in Sources */, - D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */, - D69D3D882B1971770090E88A /* SubscriptionDebugViewController.swift in Sources */, EE0798C52B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, - D637D1B92B19028E00C94784 /* AppStorePurchaseFlow.swift in Sources */, 31584616281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift in Sources */, C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */, F47E53D9250A97330037C686 /* OnboardingDefaultBroswerViewController.swift in Sources */, @@ -6528,16 +6583,16 @@ 85058366219AE9EA00ED4EDB /* HomePageConfiguration.swift in Sources */, EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */, C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */, - D664C7882B28957500CBFA76 /* BrokenSiteInfo.swift in Sources */, 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, + D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, - D6BBC4672B16B4040027507E /* WKUserContentController+Handler.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */, 1EEC460627A9499600E75FCB /* DownloadsList.swift in Sources */, + D6E83C5A2B2213ED006C8AFB /* SettingsPrivacyView.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, @@ -6547,6 +6602,7 @@ 3157B43327F497E90042D3D7 /* SaveLoginView.swift in Sources */, F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */, 0290472529E8496A0008FE3C /* AppTPActivityIconView.swift in Sources */, + D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */, EE458D142ABB652900FC651A /* NetworkProtectionDebugUtilities.swift in Sources */, 8528AE7C212EF4A200D0BD74 /* AppRatingPrompt.swift in Sources */, CB2A7EEF283D185100885F67 /* RulesCompilationMonitor.swift in Sources */, @@ -6554,7 +6610,6 @@ 1EEF12502851016B003DDE57 /* PrivacyIconAndTrackersAnimator.swift in Sources */, 31CB4251273AF50700FA0F3F /* SpeechRecognizerProtocol.swift in Sources */, 319A37172829C8AD0079FBCE /* UITableViewExtension.swift in Sources */, - D647EA462B1908E500BABD93 /* PurchaseManager.swift in Sources */, 85EE7F59224673C5000FE757 /* WebContainerNavigationController.swift in Sources */, F4C9FBF528340DDA002281CC /* AutofillInterfaceEmailTruncator.swift in Sources */, 1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */, @@ -6562,6 +6617,7 @@ EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */, EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */, B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */, + D664C7C42B289AA200CBFA76 /* AppStoreRestoreFlow.swift in Sources */, 981FED6E22025151008488D7 /* BlankSnapshotViewController.swift in Sources */, 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */, 851B128822200575004781BC /* Onboarding.swift in Sources */, @@ -6574,10 +6630,13 @@ 85C11E4C2090888C00BFFEB4 /* HomeRowReminder.swift in Sources */, 31B2F11F287846320040427A /* NoMicPermissionAlert.swift in Sources */, 310C4B45281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift in Sources */, + D664C7B62B289AA200CBFA76 /* SubscriptionFlowViewModel.swift in Sources */, 1EFDCBC127D2393C00916BC5 /* DownloadsDeleteHelper.swift in Sources */, 85374D3C21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift in Sources */, 85DFEDF124C7EEA400973FE7 /* LargeOmniBarState.swift in Sources */, 9880722A25FA497B0039EF4B /* MenuButton.swift in Sources */, + D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */, + D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, @@ -6590,6 +6649,7 @@ 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, + D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, @@ -6603,6 +6663,7 @@ 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, 989B337522D7EF2100437824 /* EmptyCollectionReusableView.swift in Sources */, 8524CC94246C5C8900E59D45 /* DaxDialogViewController.swift in Sources */, + D664C7BA2B289AA200CBFA76 /* Logging.swift in Sources */, F42EF9312614BABE00101FB9 /* ActionSheetDaxDialogViewController.swift in Sources */, EEC02C142B0519DE0045CE11 /* NetworkProtectionVPNLocationViewModel.swift in Sources */, F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */, @@ -6620,10 +6681,12 @@ F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */, 85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */, + D664C7BD2B289AA200CBFA76 /* AccountStorage.swift in Sources */, 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, + D664C7C52B289AA200CBFA76 /* AppStoreAccountManagementFlow.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */, @@ -6636,10 +6699,10 @@ 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, 31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */, + D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */, - D62529922B15E44A002A372F /* SubscriptionPagesUserScript.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, @@ -6648,6 +6711,7 @@ 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */, F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */, 85F2FFCD2211F615006BB258 /* MainViewController+KeyCommands.swift in Sources */, + D6E83C412B1FC285006C8AFB /* SettingsAppeareanceView.swift in Sources */, 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */, 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, @@ -6658,7 +6722,6 @@ 311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */, C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */, 316931D727BD10BB0095F5ED /* SaveToDownloadsAlert.swift in Sources */, - D6BBC4622B1663190027507E /* HeadlessWebView.swift in Sources */, 31C70B5B2804C61000FB6AD1 /* SaveAutofillLoginManager.swift in Sources */, 85449EFD23FDA71F00512AAF /* KeyboardSettings.swift in Sources */, 980891A222369ADB00313A70 /* FeedbackUserText.swift in Sources */, @@ -6684,13 +6747,13 @@ 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */, 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, + D664C7C22B289AA200CBFA76 /* SubscriptionUserText.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, + D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, - F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */, 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */, - D6BBC4652B1665EB0027507E /* SubscriptionFlowViewModel.swift in Sources */, 1EEF387D285B1A1100383393 /* TrackerImageCache.swift in Sources */, 3151F0EC27357FEE00226F58 /* VoiceSearchFeedbackViewModel.swift in Sources */, 85010502292FB1000033978F /* FireproofFaviconUpdater.swift in Sources */, @@ -6698,15 +6761,14 @@ 981CA7EA2617797500E119D5 /* MainViewController+AddFavoriteFlow.swift in Sources */, 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */, 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */, - D647EA422B1908E500BABD93 /* AccountStorage.swift in Sources */, F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */, + D664C7C32B289AA200CBFA76 /* AppStorePurchaseFlow.swift in Sources */, 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */, - D6BA60EE2B17BAE20032C67F /* SubscriptionUserText.swift in Sources */, 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, - D664C78A2B28957500CBFA76 /* PrivacyDashboardViewController.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, + D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, @@ -6718,13 +6780,13 @@ F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */, 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, - D647EA442B1908E500BABD93 /* APIService.swift in Sources */, 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */, 0290472E29E99A2F0008FE3C /* GenericIconView.swift in Sources */, 1E7A71172934EB6400B7EA19 /* OmniBarNotificationAnimator.swift in Sources */, 85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */, F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */, 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, + D664C7C62B289AA200CBFA76 /* PurchaseFlow.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, @@ -6738,29 +6800,30 @@ 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */, 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */, 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, + D664C7CB2B289AA200CBFA76 /* SubscriptionOptions.swift in Sources */, C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, + D664C7BF2B289AA200CBFA76 /* APIService.swift in Sources */, 314C92B827C3DD660042EC96 /* QuickLookPreviewView.swift in Sources */, F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */, 020108AE29A7F91600644F9D /* AppTPTrackerCell.swift in Sources */, - D62529752B155BCB002A372F /* SubscriptionFlowView.swift in Sources */, 983D71B12A286E810072E26D /* SyncDebugViewController.swift in Sources */, F103073B1E7C91330059FEC7 /* BookmarksDataSource.swift in Sources */, EE0153E62A6FE106002A8B26 /* NetworkProtectionRootViewModel.swift in Sources */, 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */, 1EF24235273BB9D200DE3D02 /* IntervalSlider.swift in Sources */, 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, - D69D3D862B1968220090E88A /* PurchaseInProgressView.swift in Sources */, + D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, - D637D1B72B19028E00C94784 /* AppStoreRestoreFlow.swift in Sources */, - 855D45D32ACD7DD1008F7AC6 /* AddressBarPositionSettingsViewController.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, 1E7A711C2934EEBC00B7EA19 /* OmniBarNotification.swift in Sources */, 02EC02C429AFA33000557F1A /* AppTPBreakageFormView.swift in Sources */, F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, + 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */, + D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, @@ -6771,7 +6834,6 @@ AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, - F1AB2B421E3F7D5C00868554 /* SettingsViewController.swift in Sources */, 8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */, B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */, 0290471E29E708750008FE3C /* AppTPManageTrackersView.swift in Sources */, @@ -6781,18 +6843,21 @@ 85BA585A1F3506AE00C6E8CA /* AppSettings.swift in Sources */, 3151F0EA27357FBA00226F58 /* SpeechRecognizer.swift in Sources */, F17922E21E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift in Sources */, - D647EA452B1908E500BABD93 /* AuthService.swift in Sources */, 0290472229E723260008FE3C /* AppTPManageTrackerCell.swift in Sources */, 985AAE4524899369007A43EC /* HomeScreenTransition.swift in Sources */, 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, + D664C7CA2B289AA200CBFA76 /* SimpleUserScript.swift in Sources */, 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 9865DFF922A8220D00D27829 /* FavoritesOverlay.swift in Sources */, + D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, - D647EA3F2B1908E500BABD93 /* Logging.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, + D664C7BC2B289AA200CBFA76 /* AccountKeychainStorage.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, + D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, + D664C7B82B289AA200CBFA76 /* URL+Subscription.swift in Sources */, C1B7B52428941F2A0098FD6A /* RemoteMessageRequest.swift in Sources */, EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, @@ -7475,6 +7540,14 @@ name = Authentication.storyboard; sourceTree = ""; }; + 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 984147B824F0268D00362052 /* Base */, + ); + name = PrivacyDashboard.storyboard; + sourceTree = ""; + }; 984147C224F026A300362052 /* Tab.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -7826,14 +7899,6 @@ name = InfoPlist.strings; sourceTree = ""; }; - D664C78B2B28972C00CBFA76 /* PrivacyDashboard.storyboard */ = { - isa = PBXVariantGroup; - children = ( - D664C78C2B28972C00CBFA76 /* Base */, - ); - name = PrivacyDashboard.storyboard; - sourceTree = ""; - }; EEDFE2DC2AC6ED4F00F0E19C /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -8082,7 +8147,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8110,7 +8175,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8234,11 +8299,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8290,7 +8355,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = NETWORK_PROTECTION; @@ -8315,7 +8380,6 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8338,7 +8402,6 @@ CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8404,7 +8467,7 @@ DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8441,7 +8504,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8532,7 +8595,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8561,7 +8624,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8642,7 +8705,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8673,7 +8736,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8715,404 +8778,6 @@ }; name = Release; }; - D6BA60F32B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTION"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; - }; - name = "Alpha Debug"; - }; - D6BA60F42B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; - CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_ASSET_PATHS = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; - INFOPLIST_FILE = DuckDuckGo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; - PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development App - Alpha"; - SWIFT_VERSION = 5.0; - }; - name = "Alpha Debug"; - }; - D6BA60F52B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = HKE973VLUW; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = ShareExtension/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).ShareExtension"; - "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.duckduckgo.mobile.ios.alpha.ShareExtension; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D6BA60F62B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcons; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = HKE973VLUW; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OpenAction/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).OpenAction2"; - "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.duckduckgo.mobile.ios.alpha.OpenAction2; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D6BA60F72B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = WidgetsExtensionAlpha.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; - DEAD_CODE_STRIPPING = NO; - DEVELOPMENT_TEAM = HKE973VLUW; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).Widgets"; - "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.duckduckgo.mobile.ios.alpha.Widgets; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D6BA60F82B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = HKE973VLUW; - GCC_C_LANGUAGE_STANDARD = gnu11; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = PacketTunnelProvider/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = PacketTunnelProvider; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 DuckDuckGo. All rights reserved."; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - OTHER_CFLAGS = ""; - OTHER_SWIFT_FLAGS = "-D NETWORK_EXTENSION"; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.NetworkExtension; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D6BA60F92B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Core/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = NO; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = "Alpha Debug"; - }; - D6BA60FA2B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_STYLE = Automatic; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - VERSIONING_SYSTEM = ""; - }; - name = "Alpha Debug"; - }; - D6BA60FB2B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = IntegrationTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.IntegrationTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = DuckDuckGo; - }; - name = "Alpha Debug"; - }; - D6BA60FC2B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = ""; - INFOPLIST_FILE = DuckDuckGoTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ""; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; - }; - name = "Alpha Debug"; - }; - D6BA60FD2B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = DuckDuckGo; - }; - name = "Alpha Debug"; - }; - D6BA60FE2B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; - }; - name = "Alpha Debug"; - }; - D6BA60FF2B18DEF70032C67F /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; - }; - name = "Alpha Debug"; - }; EE5A7C462A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; @@ -9164,11 +8829,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -9188,12 +8853,11 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha; PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.alpha"; SWIFT_VERSION = 5.0; @@ -9285,7 +8949,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9444,7 +9108,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9473,7 +9137,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9503,7 +9167,7 @@ CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9593,7 +9257,6 @@ buildConfigurations = ( 0202566D298818B200E694E7 /* Debug */, EE5A7C4B2A82BBB700387C84 /* Alpha */, - D6BA60F82B18DEF70032C67F /* Alpha Debug */, 0202566E298818B200E694E7 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9604,7 +9267,6 @@ buildConfigurations = ( 025CCFE92582601C001CD5BB /* Debug */, EE5A7C502A82BBB700387C84 /* Alpha */, - D6BA60FD2B18DEF70032C67F /* Alpha Debug */, 025CCFEA2582601C001CD5BB /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9615,7 +9277,6 @@ buildConfigurations = ( 8390447820BDCE10006461CD /* Debug */, EE5A7C482A82BBB700387C84 /* Alpha */, - D6BA60F52B18DEF70032C67F /* Alpha Debug */, 8390447920BDCE10006461CD /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9626,7 +9287,6 @@ buildConfigurations = ( 84E341B81E2F7EFC00BDBA6F /* Debug */, EE5A7C462A82BBB700387C84 /* Alpha */, - D6BA60F32B18DEF70032C67F /* Alpha Debug */, 84E341B91E2F7EFC00BDBA6F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9637,7 +9297,6 @@ buildConfigurations = ( 84E341BB1E2F7EFC00BDBA6F /* Debug */, EE5A7C472A82BBB700387C84 /* Alpha */, - D6BA60F42B18DEF70032C67F /* Alpha Debug */, 84E341BC1E2F7EFC00BDBA6F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9648,7 +9307,6 @@ buildConfigurations = ( 84E341BE1E2F7EFC00BDBA6F /* Debug */, EE5A7C4F2A82BBB700387C84 /* Alpha */, - D6BA60FC2B18DEF70032C67F /* Alpha Debug */, 84E341BF1E2F7EFC00BDBA6F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9659,7 +9317,6 @@ buildConfigurations = ( 8512EA5E24ED30D30073EE19 /* Debug */, EE5A7C4A2A82BBB700387C84 /* Alpha */, - D6BA60F72B18DEF70032C67F /* Alpha Debug */, 8512EA5F24ED30D30073EE19 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9670,7 +9327,6 @@ buildConfigurations = ( 85482D952462DCD100EDEDD1 /* Debug */, EE5A7C492A82BBB700387C84 /* Alpha */, - D6BA60F62B18DEF70032C67F /* Alpha Debug */, 85482D962462DCD100EDEDD1 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9681,7 +9337,6 @@ buildConfigurations = ( 85D33FD325C97B6E002B91A6 /* Debug */, EE5A7C512A82BBB700387C84 /* Alpha */, - D6BA60FE2B18DEF70032C67F /* Alpha Debug */, 85D33FD425C97B6E002B91A6 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9692,7 +9347,6 @@ buildConfigurations = ( 85F21DB4210F5E32002631A6 /* Debug */, EE5A7C4E2A82BBB700387C84 /* Alpha */, - D6BA60FB2B18DEF70032C67F /* Alpha Debug */, 85F21DB5210F5E32002631A6 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9703,7 +9357,6 @@ buildConfigurations = ( 9825F9D5293F2DE900F220F2 /* Debug */, EE5A7C522A82BBB700387C84 /* Alpha */, - D6BA60FF2B18DEF70032C67F /* Alpha Debug */, 9825F9D6293F2DE900F220F2 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9714,7 +9367,6 @@ buildConfigurations = ( 98A54A8622AFCB2D00E541F4 /* Debug */, EE5A7C4D2A82BBB700387C84 /* Alpha */, - D6BA60FA2B18DEF70032C67F /* Alpha Debug */, 98A54A8722AFCB2D00E541F4 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -9725,7 +9377,6 @@ buildConfigurations = ( F143C2EE1E4A4CD400CFDE3A /* Debug */, EE5A7C4C2A82BBB700387C84 /* Alpha */, - D6BA60F92B18DEF70032C67F /* Alpha Debug */, F143C2EF1E4A4CD400CFDE3A /* Release */, ); defaultConfigurationIsVisible = 0; diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index d0f56f27df..09aed84e03 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -17,6 +17,7 @@ // limitations under the License. // +#if SUBSCRIPTION import BrowserServicesKit import Common import Foundation @@ -218,3 +219,4 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec broker.push(method: method.rawValue, params: params, for: self, into: webView) } } +#endif diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index 6f145fed82..addc18fdd9 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -21,6 +21,8 @@ import Foundation import UserScript import Combine +#if SUBSCRIPTION +@available(iOS 15.0, *) final class SubscriptionFlowViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript @@ -62,3 +64,4 @@ final class SubscriptionFlowViewModel: ObservableObject { } } +#endif diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index 526d9efc4a..68a2cb0d6e 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -17,6 +17,7 @@ // limitations under the License. // +#if SUBSCRIPTION import SwiftUI import Foundation @@ -41,3 +42,4 @@ struct SubscriptionFlowView: View { .navigationTitle(viewModel.viewTitle) } } +#endif From a981a0788e310f1bf82d48ee3cfd3f3ca2c7f44a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 14:56:25 +0100 Subject: [PATCH 69/99] Remove SettingsVC --- DuckDuckGo.xcodeproj/project.pbxproj | 405 ++++++++++++ DuckDuckGo/SettingsViewController.swift | 826 ------------------------ 2 files changed, 405 insertions(+), 826 deletions(-) delete mode 100644 DuckDuckGo/SettingsViewController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 560263a498..96f5356bd4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8778,6 +8778,398 @@ }; name = Release; }; + D664C7CF2B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + }; + name = "Alpha Debug"; + }; + D664C7D02B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; + CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + INFOPLIST_FILE = DuckDuckGo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; + SWIFT_VERSION = 5.0; + }; + name = "Alpha Debug"; + }; + D664C7D12B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).ShareExtension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7D22B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcons; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OpenAction/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).OpenAction2"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7D32B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Widgets/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).Widgets"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7D42B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + 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_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PacketTunnelProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PacketTunnelProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 DuckDuckGo. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ""; + OTHER_SWIFT_FLAGS = "-D NETWORK_EXTENSION"; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.NetworkExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7D52B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Core/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Alpha Debug"; + }; + D664C7D62B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CODE_SIGN_STYLE = Automatic; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = ""; + }; + name = "Alpha Debug"; + }; + D664C7D72B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = IntegrationTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = "Alpha Debug"; + }; + D664C7D82B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = DuckDuckGoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; + D664C7D92B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = "Alpha Debug"; + }; + D664C7DA2B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = IntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; + D664C7DB2B289D6C00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; EE5A7C462A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; @@ -9256,6 +9648,7 @@ isa = XCConfigurationList; buildConfigurations = ( 0202566D298818B200E694E7 /* Debug */, + D664C7D42B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4B2A82BBB700387C84 /* Alpha */, 0202566E298818B200E694E7 /* Release */, ); @@ -9266,6 +9659,7 @@ isa = XCConfigurationList; buildConfigurations = ( 025CCFE92582601C001CD5BB /* Debug */, + D664C7D92B289D6C00CBFA76 /* Alpha Debug */, EE5A7C502A82BBB700387C84 /* Alpha */, 025CCFEA2582601C001CD5BB /* Release */, ); @@ -9276,6 +9670,7 @@ isa = XCConfigurationList; buildConfigurations = ( 8390447820BDCE10006461CD /* Debug */, + D664C7D12B289D6C00CBFA76 /* Alpha Debug */, EE5A7C482A82BBB700387C84 /* Alpha */, 8390447920BDCE10006461CD /* Release */, ); @@ -9286,6 +9681,7 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341B81E2F7EFC00BDBA6F /* Debug */, + D664C7CF2B289D6C00CBFA76 /* Alpha Debug */, EE5A7C462A82BBB700387C84 /* Alpha */, 84E341B91E2F7EFC00BDBA6F /* Release */, ); @@ -9296,6 +9692,7 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341BB1E2F7EFC00BDBA6F /* Debug */, + D664C7D02B289D6C00CBFA76 /* Alpha Debug */, EE5A7C472A82BBB700387C84 /* Alpha */, 84E341BC1E2F7EFC00BDBA6F /* Release */, ); @@ -9306,6 +9703,7 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341BE1E2F7EFC00BDBA6F /* Debug */, + D664C7D82B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4F2A82BBB700387C84 /* Alpha */, 84E341BF1E2F7EFC00BDBA6F /* Release */, ); @@ -9316,6 +9714,7 @@ isa = XCConfigurationList; buildConfigurations = ( 8512EA5E24ED30D30073EE19 /* Debug */, + D664C7D32B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4A2A82BBB700387C84 /* Alpha */, 8512EA5F24ED30D30073EE19 /* Release */, ); @@ -9326,6 +9725,7 @@ isa = XCConfigurationList; buildConfigurations = ( 85482D952462DCD100EDEDD1 /* Debug */, + D664C7D22B289D6C00CBFA76 /* Alpha Debug */, EE5A7C492A82BBB700387C84 /* Alpha */, 85482D962462DCD100EDEDD1 /* Release */, ); @@ -9336,6 +9736,7 @@ isa = XCConfigurationList; buildConfigurations = ( 85D33FD325C97B6E002B91A6 /* Debug */, + D664C7DA2B289D6C00CBFA76 /* Alpha Debug */, EE5A7C512A82BBB700387C84 /* Alpha */, 85D33FD425C97B6E002B91A6 /* Release */, ); @@ -9346,6 +9747,7 @@ isa = XCConfigurationList; buildConfigurations = ( 85F21DB4210F5E32002631A6 /* Debug */, + D664C7D72B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4E2A82BBB700387C84 /* Alpha */, 85F21DB5210F5E32002631A6 /* Release */, ); @@ -9356,6 +9758,7 @@ isa = XCConfigurationList; buildConfigurations = ( 9825F9D5293F2DE900F220F2 /* Debug */, + D664C7DB2B289D6C00CBFA76 /* Alpha Debug */, EE5A7C522A82BBB700387C84 /* Alpha */, 9825F9D6293F2DE900F220F2 /* Release */, ); @@ -9366,6 +9769,7 @@ isa = XCConfigurationList; buildConfigurations = ( 98A54A8622AFCB2D00E541F4 /* Debug */, + D664C7D62B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4D2A82BBB700387C84 /* Alpha */, 98A54A8722AFCB2D00E541F4 /* Release */, ); @@ -9376,6 +9780,7 @@ isa = XCConfigurationList; buildConfigurations = ( F143C2EE1E4A4CD400CFDE3A /* Debug */, + D664C7D52B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4C2A82BBB700387C84 /* Alpha */, F143C2EF1E4A4CD400CFDE3A /* Release */, ); diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift deleted file mode 100644 index 449c3f67aa..0000000000 --- a/DuckDuckGo/SettingsViewController.swift +++ /dev/null @@ -1,826 +0,0 @@ -// -// SettingsViewController.swift -// DuckDuckGo -// -// Copyright © 2017 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit -import Core -import BrowserServicesKit -import Persistence -import SwiftUI -import Common -import DDGSync -import Combine - -#if APP_TRACKING_PROTECTION -import NetworkExtension -#endif - -#if NETWORK_PROTECTION -import NetworkProtection -#endif - -// swiftlint:disable file_length type_body_length -class SettingsViewController: UITableViewController { - - @IBOutlet weak var defaultBrowserCell: UITableViewCell! - @IBOutlet weak var themeAccessoryText: UILabel! - @IBOutlet weak var fireButtonAnimationAccessoryText: UILabel! - @IBOutlet weak var addressBarPositionCell: UITableViewCell! - @IBOutlet weak var addressBarPositionAccessoryText: UILabel! - @IBOutlet weak var appIconCell: UITableViewCell! - @IBOutlet weak var appIconImageView: UIImageView! - @IBOutlet weak var autocompleteToggle: UISwitch! - @IBOutlet weak var authenticationToggle: UISwitch! - @IBOutlet weak var autoClearAccessoryText: UILabel! - @IBOutlet weak var versionText: UILabel! - @IBOutlet weak var openUniversalLinksToggle: UISwitch! - @IBOutlet weak var longPressPreviewsToggle: UISwitch! - @IBOutlet weak var rememberLoginsCell: UITableViewCell! - @IBOutlet weak var rememberLoginsAccessoryText: UILabel! - @IBOutlet weak var doNotSellCell: UITableViewCell! - @IBOutlet weak var doNotSellAccessoryText: UILabel! - @IBOutlet weak var autoconsentCell: UITableViewCell! - @IBOutlet weak var autoconsentAccessoryText: UILabel! - @IBOutlet weak var emailProtectionCell: UITableViewCell! - @IBOutlet weak var emailProtectionAccessoryText: UILabel! - @IBOutlet weak var macBrowserWaitlistCell: UITableViewCell! - @IBOutlet weak var macBrowserWaitlistAccessoryText: UILabel! - @IBOutlet weak var windowsBrowserWaitlistCell: UITableViewCell! - @IBOutlet weak var windowsBrowserWaitlistAccessoryText: UILabel! - @IBOutlet weak var netPCell: UITableViewCell! - @IBOutlet weak var longPressCell: UITableViewCell! - @IBOutlet weak var versionCell: UITableViewCell! - @IBOutlet weak var textSizeCell: UITableViewCell! - @IBOutlet weak var textSizeAccessoryText: UILabel! - @IBOutlet weak var widgetEducationCell: UITableViewCell! - @IBOutlet weak var syncCell: UITableViewCell! - @IBOutlet weak var autofillCell: UITableViewCell! - @IBOutlet weak var debugCell: UITableViewCell! - @IBOutlet weak var voiceSearchCell: UITableViewCell! - @IBOutlet weak var voiceSearchToggle: UISwitch! - @IBOutlet weak var privacyProSignupCell: UITableViewCell! - @IBOutlet weak var privacyProLearnMoreCell: UITableViewCell! - @IBOutlet weak var privacyProVPNCell: UITableViewCell! - @IBOutlet weak var privacyProDBPCell: UITableViewCell! - @IBOutlet weak var privacyProITPCell: UITableViewCell! - @IBOutlet weak var privacyProSubscriptionSettingsCell: UITableViewCell! - - - @IBOutlet var labels: [UILabel]! - @IBOutlet var accessoryLabels: [UILabel]! - - private let syncSectionIndex = 1 - private let autofillSectionIndex = 2 - private let appearanceSectionIndex = 3 - private let privacyProSectionIndex = 5 - private let moreFromDDGSectionIndex = 7 - private let debugSectionIndex = 9 - - - private let bookmarksDatabase: CoreDataDatabase - - private lazy var emailManager = EmailManager() - private lazy var versionProvider: AppVersion = AppVersion.shared - fileprivate lazy var privacyStore = PrivacyUserDefaults() - fileprivate lazy var appSettings = AppDependencyProvider.shared.appSettings - fileprivate lazy var variantManager = AppDependencyProvider.shared.variantManager - fileprivate lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger - fileprivate let syncService: DDGSyncing - fileprivate let syncDataProviders: SyncDataProviders - fileprivate let internalUserDecider: InternalUserDecider -#if NETWORK_PROTECTION - private let connectionObserver = ConnectionStatusObserverThroughSession() -#endif - private var cancellables: Set = [] - - private var shouldShowDebugCell: Bool { - return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild - } - - private var shouldShowVoiceSearchCell: Bool { - AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable - } - - private var shouldShowAutofillCell: Bool { - return featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) - } - - private var shouldShowSyncCell: Bool { - return featureFlagger.isFeatureOn(.sync) - } - - private var shouldShowTextSizeCell: Bool { - return UIDevice.current.userInterfaceIdiom != .pad - } - - private var shouldShowAddressBarPositionCell: Bool { - return UIDevice.current.userInterfaceIdiom != .pad - } - - private lazy var shouldShowNetPCell: Bool = { -#if NETWORK_PROTECTION - if #available(iOS 15, *) { - return featureFlagger.isFeatureOn(.networkProtection) - } else { - return false - } -#else - return false -#endif - }() - -#if SUBSCRIPTION - private lazy var accountManger = AccountManager() -#endif - - private lazy var shouldShowPrivacyPro: Bool = { -#if SUBSCRIPTION - if #available(iOS 15, *) { - return featureFlagger.isFeatureOn(.privacyPro) - } else { - return false - } -#else - return false -#endif - }() - - private lazy var privacyProIsAuthenticated: Bool = { -#if SUBSCRIPTION - if accountManger.isUserAuthenticated { - return true - } - return false -#else - return false -#endif - }() - - - override func viewDidLoad() { - super.viewDidLoad() - - configureAutofillCell() - configureSyncCell() - configureThemeCellAccessory() - configureFireButtonAnimationCellAccessory() - configureAddressBarPositionCell() - configureTextSizeCell() - configureDisableAutocompleteToggle() - configureSecurityToggles() - configureVersionText() - configureUniversalLinksToggle() - configureLinkPreviewsToggle() - configureRememberLogins() - configureDebugCell() - configureVoiceSearchCell() - configureNetPCell() - configurePrivacyPro() - - applyTheme(ThemeManager.shared.currentTheme) - - internalUserDecider.isInternalUserPublisher.dropFirst().sink(receiveValue: { [weak self] _ in - self?.configureAutofillCell() - self?.configureSyncCell() - self?.configureDebugCell() - self?.tableView.reloadData() - - // Scroll to force-redraw section headers and footers - self?.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) - }) - .store(in: &cancellables) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - self.isModalInPresentation = false - configureFireButtonAnimationCellAccessory() - configureAddressBarPositionCell() - configureTextSizeCell() - configureAutoClearCellAccessory() - configureRememberLogins() - configureDoNotSell() - configureAutoconsent() - configureIconViews() - configureEmailProtectionAccessoryText() - configureMacBrowserWaitlistCell() - configureWindowsBrowserWaitlistCell() - configureSyncCell() - -#if NETWORK_PROTECTION - updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) -#endif - -#if SUBSCRIPTION - updatePrivacyProSettings() -#endif - - // Make sure multiline labels are correctly presented - tableView.setNeedsLayout() - tableView.layoutIfNeeded() - } - - init?(coder: NSCoder, - bookmarksDatabase: CoreDataDatabase, - syncService: DDGSyncing, - syncDataProviders: SyncDataProviders, - internalUserDecider: InternalUserDecider) { - - self.bookmarksDatabase = bookmarksDatabase - self.syncService = syncService - self.syncDataProviders = syncDataProviders - self.internalUserDecider = internalUserDecider - super.init(coder: coder) - } - - required init?(coder: NSCoder) { - fatalError("Not implemented") - } - - func openLogins() { - showAutofill() - } - - func openLogins(accountDetails: SecureVaultModels.WebsiteAccount) { - showAutofillAccountDetails(accountDetails) - } - - func openCookiePopupManagement() { - showCookiePopupManagement(animated: true) - } - - @IBSegueAction func onCreateRootDebugScreen(_ coder: NSCoder, sender: Any?, segueIdentifier: String?) -> RootDebugViewController { - guard let controller = RootDebugViewController(coder: coder, - sync: syncService, - bookmarksDatabase: bookmarksDatabase, - internalUserDecider: AppDependencyProvider.shared.internalUserDecider) else { - fatalError("Failed to create controller") - } - - return controller - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.destination is DoNotSellSettingsViewController { - Pixel.fire(pixel: .settingsDoNotSellShown) - return - } else if segue.destination is AutoconsentSettingsViewController { - Pixel.fire(pixel: .settingsAutoconsentShown) - return - } else if let textSizeSettings = segue.destination as? TextSizeSettingsViewController { - Pixel.fire(pixel: .textSizeSettingsShown) - presentationController?.delegate = textSizeSettings - return - } - - if let navController = segue.destination as? UINavigationController, navController.topViewController is FeedbackViewController { - if UIDevice.current.userInterfaceIdiom == .pad { - segue.destination.modalPresentationStyle = .formSheet - } - } - } - - private func configureAutofillCell() { - autofillCell.isHidden = !shouldShowAutofillCell - } - - private func configureSyncCell() { - syncCell.textLabel?.text = "Sync & Backup" - if SyncBookmarksAdapter.isSyncBookmarksPaused || SyncCredentialsAdapter.isSyncCredentialsPaused { - syncCell.textLabel?.text = "⚠️ " + "Sync & Backup" - } - syncCell.isHidden = !shouldShowSyncCell - } - - private func configureVoiceSearchCell() { - voiceSearchCell.isHidden = !shouldShowVoiceSearchCell - voiceSearchToggle.isOn = appSettings.voiceSearchEnabled - } - - private func configureThemeCellAccessory() { - switch appSettings.currentThemeName { - case .systemDefault: - themeAccessoryText.text = UserText.themeAccessoryDefault - case .light: - themeAccessoryText.text = UserText.themeAccessoryLight - case .dark: - themeAccessoryText.text = UserText.themeAccessoryDark - } - } - - private func configureFireButtonAnimationCellAccessory() { - fireButtonAnimationAccessoryText.text = appSettings.currentFireButtonAnimation.descriptionText - } - - private func configureAddressBarPositionCell() { - addressBarPositionCell.isHidden = !shouldShowAddressBarPositionCell - addressBarPositionAccessoryText.text = appSettings.currentAddressBarPosition.descriptionText - } - - private func configureTextSizeCell() { - textSizeCell.isHidden = !shouldShowTextSizeCell - textSizeAccessoryText.text = "\(appSettings.textSize)%" - } - - private func configureIconViews() { - if AppIconManager.shared.isAppIconChangeSupported { - appIconImageView.image = AppIconManager.shared.appIcon.smallImage - } else { - appIconCell.isHidden = true - } - } - - private func configureDisableAutocompleteToggle() { - autocompleteToggle.isOn = appSettings.autocomplete - } - - private func configureSecurityToggles() { - authenticationToggle.isOn = privacyStore.authenticationEnabled - } - - private func configureAutoClearCellAccessory() { - if AutoClearSettingsModel(settings: appSettings) != nil { - autoClearAccessoryText.text = UserText.autoClearAccessoryOn - } else { - autoClearAccessoryText.text = UserText.autoClearAccessoryOff - } - } - - private func configureDoNotSell() { - doNotSellAccessoryText.text = appSettings.sendDoNotSell ? UserText.doNotSellEnabled : UserText.doNotSellDisabled - } - - private func configureAutoconsent() { - autoconsentAccessoryText.text = appSettings.autoconsentEnabled ? UserText.autoconsentEnabled : UserText.autoconsentDisabled - } - - private func configureRememberLogins() { - rememberLoginsAccessoryText.text = PreserveLogins.shared.allowedDomains.isEmpty ? "" : "\(PreserveLogins.shared.allowedDomains.count)" - } - - private func configureVersionText() { - versionText.text = versionProvider.versionAndBuildNumber - } - - private func configureUniversalLinksToggle() { - openUniversalLinksToggle.isOn = appSettings.allowUniversalLinks - } - - private func configureLinkPreviewsToggle() { - longPressCell.isHidden = false - longPressPreviewsToggle.isOn = appSettings.longPressPreviews - } - - private func configureMacBrowserWaitlistCell() { - macBrowserWaitlistCell.detailTextLabel?.text = MacBrowserWaitlist.shared.settingsSubtitle - } - - private func configureWindowsBrowserWaitlistCell() { - windowsBrowserWaitlistCell.isHidden = !WindowsBrowserWaitlist.shared.isAvailable - - if WindowsBrowserWaitlist.shared.isAvailable { - windowsBrowserWaitlistCell.detailTextLabel?.text = WindowsBrowserWaitlist.shared.settingsSubtitle - } - } - -#if SUBSCRIPTION - - private func updatePrivacyProSettings() { - configurePrivacyPro() - UIView.transition(with: tableView, - duration: 0.35, - options: .transitionCrossDissolve, - animations: { self.tableView.reloadData() }) - } - - @objc private func configurePrivacyPro() { - let isAuthenticated = accountManger.isUserAuthenticated - - // Signup Cells - // privacyProSignupCell.isHidden = isAuthenticated - // privacyProLearnMoreCell.isHidden = isAuthenticated - // privacyProSignupCell.accessoryType = .none - // privacyProSignupCell.isUserInteractionEnabled = false - - // Management Cells - // privacyProVPNCell.isHidden = !isAuthenticated - // privacyProDBPCell.isHidden = !isAuthenticated - // privacyProITPCell.isHidden = !isAuthenticated - // privacyProSubscriptionSettingsCell.isHidden = !isAuthenticated - - - } -#endif - - - private func configureNetPCell() { - netPCell.isHidden = !shouldShowNetPCell -#if NETWORK_PROTECTION - updateNetPCellSubtitle(connectionStatus: connectionObserver.recentValue) - connectionObserver.publisher - .receive(on: DispatchQueue.main) - .sink { [weak self] status in - self?.updateNetPCellSubtitle(connectionStatus: status) - } - .store(in: &cancellables) -#endif - } - -#if NETWORK_PROTECTION - private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { - switch NetworkProtectionAccessController().networkProtectionAccessType() { - case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: - netPCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle - // privacyProVPNCell.detailTextLabel?.text = VPNWaitlist.shared.settingsSubtitle - case .waitlistInvited, .inviteCodeInvited: - switch connectionStatus { - case .connected: - netPCell.detailTextLabel?.text = UserText.netPCellConnected - // privacyProVPNCell.detailTextLabel?.text = UserText.netPCellConnected - default: - netPCell.detailTextLabel?.text = UserText.netPCellDisconnected - // privacyProVPNCell.detailTextLabel?.text = UserText.netPCellDisconnected - } - } - } -#endif - - private func configureDebugCell() { - debugCell.isHidden = !shouldShowDebugCell - } - - func showSync(animated: Bool = true) { - let controller = SyncSettingsViewController(syncService: syncService, syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) - navigationController?.pushViewController(controller, animated: animated) - } - - private func showAutofill(animated: Bool = true) { - let autofillController = AutofillLoginSettingsListViewController( - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders - ) - autofillController.delegate = self - Pixel.fire(pixel: .autofillSettingsOpened) - navigationController?.pushViewController(autofillController, animated: animated) - } - - func showAutofillAccountDetails(_ account: SecureVaultModels.WebsiteAccount) { - let autofillController = AutofillLoginSettingsListViewController( - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders - ) - autofillController.delegate = self - let detailsController = autofillController.makeAccountDetailsScreen(account) - - var controllers = navigationController?.viewControllers ?? [] - controllers.append(autofillController) - controllers.append(detailsController) - navigationController?.viewControllers = controllers - } - - private func configureEmailProtectionAccessoryText() { - if let userEmail = emailManager.userEmail { - emailProtectionAccessoryText.text = userEmail - } else { - emailProtectionAccessoryText.text = UserText.emailSettingsSubtitle - } - } - - private func showEmailWebDashboard() { - UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) - } - - private func showMacBrowserWaitlistViewController() { - navigationController?.pushViewController(MacWaitlistViewController(nibName: nil, bundle: nil), animated: true) - } - -#if NETWORK_PROTECTION - @available(iOS 15, *) - private func showNetP() { - switch NetworkProtectionAccessController().networkProtectionAccessType() { - case .inviteCodeInvited, .waitlistInvited: - // This will be tidied up as part of https://app.asana.com/0/0/1205084446087078/f - let rootViewController = NetworkProtectionRootViewController { [weak self] in - self?.navigationController?.popViewController(animated: true) - let newRootViewController = NetworkProtectionRootViewController() - self?.pushNetP(newRootViewController) - } - - pushNetP(rootViewController) - default: - navigationController?.pushViewController(VPNWaitlistViewController(nibName: nil, bundle: nil), animated: true) - } - } - - @available(iOS 15, *) - private func pushNetP(_ rootViewController: NetworkProtectionRootViewController) { - navigationController?.pushViewController( - rootViewController, - animated: true - ) - } -#endif - -#if SUBSCRIPTION - @available(iOS 15, *) - private func showPrivacyPro() { - self.isModalInPresentation = true - let privacyProview = SubscriptionFlowView(viewModel: SubscriptionFlowViewModel()) - let hostingController = UIHostingController(rootView: privacyProview) - navigationController?.pushViewController(hostingController, animated: true) - } -#endif - - private func showWindowsBrowserWaitlistViewController() { - navigationController?.pushViewController(WindowsWaitlistViewController(nibName: nil, bundle: nil), animated: true) - } - - func showCookiePopupManagement(animated: Bool = true) { - navigationController?.pushViewController(AutoconsentSettingsViewController.loadFromStoryboard(), animated: animated) - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let cell = tableView.cellForRow(at: indexPath) - - switch cell { - - case defaultBrowserCell: - Pixel.fire(pixel: .defaultBrowserButtonPressedSettings) - guard let url = URL(string: UIApplication.openSettingsURLString) else { return } - UIApplication.shared.open(url) - - case emailProtectionCell: - showEmailWebDashboard() - - case macBrowserWaitlistCell: - showMacBrowserWaitlistViewController() - - case windowsBrowserWaitlistCell: - showWindowsBrowserWaitlistViewController() - - case autofillCell: - showAutofill() - - case syncCell: - showSync() - - case netPCell: - if #available(iOS 15, *) { -#if NETWORK_PROTECTION - showNetP() -#else - break -#endif - } - - case privacyProLearnMoreCell: - if #available(iOS 15, *) { -#if SUBSCRIPTION - showPrivacyPro() -#else - break -#endif - } - - case privacyProVPNCell: - if #available(iOS 15, *) { -#if SUBSCRIPTION && NETWORK_PROTECTION - showNetP() -#else - break -#endif - } - - default: break - } - - } - - override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - let theme = ThemeManager.shared.currentTheme - cell.backgroundColor = theme.tableCellBackgroundColor - - if cell == netPCell { - DailyPixel.fire(pixel: .networkProtectionSettingsRowDisplayed) - } - } - - override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection: Int) { - if let view = view as? UITableViewHeaderFooterView { - let theme = ThemeManager.shared.currentTheme - view.textLabel?.textColor = theme.tableHeaderTextColor - } - } - - override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection: Int) { - if let view = view as? UITableViewHeaderFooterView { - let theme = ThemeManager.shared.currentTheme - view.textLabel?.textColor = theme.tableHeaderTextColor - } - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let cell = super.tableView(tableView, cellForRowAt: indexPath) - return cell.isHidden ? 0 : UITableView.automaticDimension - } - - override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } - - /// Only use this to hide the header if the entire section can be conditionally hidden. - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - if syncSectionIndex == section && !shouldShowSyncCell { - return CGFloat.leastNonzeroMagnitude - } else if autofillSectionIndex == section && !shouldShowAutofillCell { - return CGFloat.leastNonzeroMagnitude - } else if debugSectionIndex == section && !shouldShowDebugCell { - return CGFloat.leastNonzeroMagnitude - } else if privacyProSectionIndex == section && !shouldShowPrivacyPro { - return CGFloat.leastNonzeroMagnitude - } else { - return super.tableView(tableView, heightForHeaderInSection: section) - } - } - - /// Only use this to hide the footer if the entire section can be conditionally hidden. - override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - if syncSectionIndex == section && !shouldShowSyncCell { - return CGFloat.leastNonzeroMagnitude - } else if autofillSectionIndex == section && !shouldShowAutofillCell { - return CGFloat.leastNonzeroMagnitude - } else if debugSectionIndex == section && !shouldShowDebugCell { - return CGFloat.leastNonzeroMagnitude - } else { - return super.tableView(tableView, heightForFooterInSection: section) - } - } - - /// Only use this if the *last cell* in the section is to be conditionally hidden in order to retain the section rounding. - /// If your cell is not the last you don't need to modify the number of rows. - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let rows = super.tableView(tableView, numberOfRowsInSection: section) - if section == moreFromDDGSectionIndex && !shouldShowNetPCell { - return rows - 1 - } else if section == appearanceSectionIndex && UIDevice.current.userInterfaceIdiom == .pad { - // Both the text size and bottom bar settings are at the end of the section so need to reduce the section size appropriately - return rows - 2 - } else if section == privacyProSectionIndex && !shouldShowPrivacyPro { - return 0 - } else { - return rows - } - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - if section == privacyProSectionIndex && !shouldShowPrivacyPro { - return nil - } - return super.tableView(tableView, titleForHeaderInSection: section) - } - - - @IBAction func onVoiceSearchToggled(_ sender: UISwitch) { - var enableVoiceSearch = sender.isOn - let isFirstTimeAskingForPermission = SpeechRecognizer.recordPermission == .undetermined - - SpeechRecognizer.requestMicAccess { permission in - if !permission { - enableVoiceSearch = false - sender.setOn(false, animated: true) - if !isFirstTimeAskingForPermission { - self.showNoMicrophonePermissionAlert() - } - } - - AppDependencyProvider.shared.voiceSearchHelper.enableVoiceSearch(enableVoiceSearch) - } - } - - @IBAction func onAboutTapped() { - navigationController?.pushViewController(AboutViewController(), animated: true) - } - - private func showNoMicrophonePermissionAlert() { - let alertController = NoMicPermissionAlert.buildAlert() - present(alertController, animated: true, completion: nil) - } - - @IBAction func onAuthenticationToggled(_ sender: UISwitch) { - privacyStore.authenticationEnabled = sender.isOn - } - - @IBAction func onDonePressed(_ sender: Any) { - dismiss(animated: true, completion: nil) - } - - @IBAction func onAutocompleteToggled(_ sender: UISwitch) { - appSettings.autocomplete = sender.isOn - } - - @IBAction func onAllowUniversalLinksToggled(_ sender: UISwitch) { - appSettings.allowUniversalLinks = sender.isOn - } - - @IBAction func onLinkPreviewsToggle(_ sender: UISwitch) { - appSettings.longPressPreviews = sender.isOn - } -} - -extension SettingsViewController: Themable { - - func decorate(with theme: Theme) { - view.backgroundColor = theme.backgroundColor - - decorateNavigationBar(with: theme) - configureThemeCellAccessory() - - for label in labels { - label.textColor = theme.tableCellTextColor - } - - for label in accessoryLabels { - label.textColor = theme.tableCellAccessoryTextColor - } - - versionText.textColor = theme.tableCellTextColor - - autocompleteToggle.onTintColor = theme.buttonTintColor - authenticationToggle.onTintColor = theme.buttonTintColor - openUniversalLinksToggle.onTintColor = theme.buttonTintColor - longPressPreviewsToggle.onTintColor = theme.buttonTintColor - voiceSearchToggle.onTintColor = theme.buttonTintColor - - tableView.backgroundColor = theme.backgroundColor - tableView.separatorColor = theme.tableCellSeparatorColor - - UIView.transition(with: view, - duration: 0.2, - options: .transitionCrossDissolve, animations: { - self.tableView.reloadData() - }, completion: nil) - } -} - -extension SettingsViewController { - static var fontSizeForHeaderView: CGFloat { - let contentSize = UIApplication.shared.preferredContentSizeCategory - switch contentSize { - case .extraSmall: - return 12 - case .small: - return 12 - case .medium: - return 12 - case .large: - return 13 - case .extraLarge: - return 15 - case .extraExtraLarge: - return 17 - case .extraExtraExtraLarge: - return 19 - case .accessibilityMedium: - return 23 - case .accessibilityLarge: - return 27 - case .accessibilityExtraLarge: - return 33 - case .accessibilityExtraExtraLarge: - return 38 - case .accessibilityExtraExtraExtraLarge: - return 44 - default: - return 13 - } - } -} - -// MARK: - AutofillLoginSettingsListViewControllerDelegate - -extension SettingsViewController: AutofillLoginSettingsListViewControllerDelegate { - func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) { - navigationController?.popViewController(animated: true) - } -} -// swiftlint:enable file_length type_body_length From 73a8afc42e68c5508f0ec2f5bce734cc8f142271 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 15:04:21 +0100 Subject: [PATCH 70/99] Updated Profiles --- DuckDuckGo.xcodeproj/project.pbxproj | 409 +-------------------------- 1 file changed, 2 insertions(+), 407 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 96f5356bd4..2ec5f5c8a8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8778,398 +8778,6 @@ }; name = Release; }; - D664C7CF2B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; - }; - name = "Alpha Debug"; - }; - D664C7D02B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; - CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_ASSET_PATHS = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; - INFOPLIST_FILE = DuckDuckGo/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; - SWIFT_VERSION = 5.0; - }; - name = "Alpha Debug"; - }; - D664C7D12B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = ShareExtension/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).ShareExtension"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D664C7D22B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcons; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OpenAction/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).OpenAction2"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D664C7D32B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEAD_CODE_STRIPPING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).Widgets"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D664C7D42B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - 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_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEVELOPMENT_TEAM = HKE973VLUW; - GCC_C_LANGUAGE_STANDARD = gnu11; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = PacketTunnelProvider/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = PacketTunnelProvider; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 DuckDuckGo. All rights reserved."; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - OTHER_CFLAGS = ""; - OTHER_SWIFT_FLAGS = "-D NETWORK_EXTENSION"; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.NetworkExtension; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Alpha Debug"; - }; - D664C7D52B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Core/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = NO; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = "Alpha Debug"; - }; - D664C7D62B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_STYLE = Automatic; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - VERSIONING_SYSTEM = ""; - }; - name = "Alpha Debug"; - }; - D664C7D72B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = IntegrationTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.IntegrationTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = DuckDuckGo; - }; - name = "Alpha Debug"; - }; - D664C7D82B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - INFOPLIST_FILE = DuckDuckGoTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; - }; - name = "Alpha Debug"; - }; - D664C7D92B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = DuckDuckGo; - }; - name = "Alpha Debug"; - }; - D664C7DA2B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = IntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; - }; - name = "Alpha Debug"; - }; - D664C7DB2B289D6C00CBFA76 /* Alpha Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; - }; - name = "Alpha Debug"; - }; EE5A7C462A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; @@ -9239,7 +8847,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; @@ -9251,7 +8859,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha; PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.alpha"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development App - Alpha"; SWIFT_VERSION = 5.0; }; name = Alpha; @@ -9648,7 +9256,6 @@ isa = XCConfigurationList; buildConfigurations = ( 0202566D298818B200E694E7 /* Debug */, - D664C7D42B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4B2A82BBB700387C84 /* Alpha */, 0202566E298818B200E694E7 /* Release */, ); @@ -9659,7 +9266,6 @@ isa = XCConfigurationList; buildConfigurations = ( 025CCFE92582601C001CD5BB /* Debug */, - D664C7D92B289D6C00CBFA76 /* Alpha Debug */, EE5A7C502A82BBB700387C84 /* Alpha */, 025CCFEA2582601C001CD5BB /* Release */, ); @@ -9670,7 +9276,6 @@ isa = XCConfigurationList; buildConfigurations = ( 8390447820BDCE10006461CD /* Debug */, - D664C7D12B289D6C00CBFA76 /* Alpha Debug */, EE5A7C482A82BBB700387C84 /* Alpha */, 8390447920BDCE10006461CD /* Release */, ); @@ -9681,7 +9286,6 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341B81E2F7EFC00BDBA6F /* Debug */, - D664C7CF2B289D6C00CBFA76 /* Alpha Debug */, EE5A7C462A82BBB700387C84 /* Alpha */, 84E341B91E2F7EFC00BDBA6F /* Release */, ); @@ -9692,7 +9296,6 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341BB1E2F7EFC00BDBA6F /* Debug */, - D664C7D02B289D6C00CBFA76 /* Alpha Debug */, EE5A7C472A82BBB700387C84 /* Alpha */, 84E341BC1E2F7EFC00BDBA6F /* Release */, ); @@ -9703,7 +9306,6 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341BE1E2F7EFC00BDBA6F /* Debug */, - D664C7D82B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4F2A82BBB700387C84 /* Alpha */, 84E341BF1E2F7EFC00BDBA6F /* Release */, ); @@ -9714,7 +9316,6 @@ isa = XCConfigurationList; buildConfigurations = ( 8512EA5E24ED30D30073EE19 /* Debug */, - D664C7D32B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4A2A82BBB700387C84 /* Alpha */, 8512EA5F24ED30D30073EE19 /* Release */, ); @@ -9725,7 +9326,6 @@ isa = XCConfigurationList; buildConfigurations = ( 85482D952462DCD100EDEDD1 /* Debug */, - D664C7D22B289D6C00CBFA76 /* Alpha Debug */, EE5A7C492A82BBB700387C84 /* Alpha */, 85482D962462DCD100EDEDD1 /* Release */, ); @@ -9736,7 +9336,6 @@ isa = XCConfigurationList; buildConfigurations = ( 85D33FD325C97B6E002B91A6 /* Debug */, - D664C7DA2B289D6C00CBFA76 /* Alpha Debug */, EE5A7C512A82BBB700387C84 /* Alpha */, 85D33FD425C97B6E002B91A6 /* Release */, ); @@ -9747,7 +9346,6 @@ isa = XCConfigurationList; buildConfigurations = ( 85F21DB4210F5E32002631A6 /* Debug */, - D664C7D72B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4E2A82BBB700387C84 /* Alpha */, 85F21DB5210F5E32002631A6 /* Release */, ); @@ -9758,7 +9356,6 @@ isa = XCConfigurationList; buildConfigurations = ( 9825F9D5293F2DE900F220F2 /* Debug */, - D664C7DB2B289D6C00CBFA76 /* Alpha Debug */, EE5A7C522A82BBB700387C84 /* Alpha */, 9825F9D6293F2DE900F220F2 /* Release */, ); @@ -9769,7 +9366,6 @@ isa = XCConfigurationList; buildConfigurations = ( 98A54A8622AFCB2D00E541F4 /* Debug */, - D664C7D62B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4D2A82BBB700387C84 /* Alpha */, 98A54A8722AFCB2D00E541F4 /* Release */, ); @@ -9780,7 +9376,6 @@ isa = XCConfigurationList; buildConfigurations = ( F143C2EE1E4A4CD400CFDE3A /* Debug */, - D664C7D52B289D6C00CBFA76 /* Alpha Debug */, EE5A7C4C2A82BBB700387C84 /* Alpha */, F143C2EF1E4A4CD400CFDE3A /* Release */, ); From 8599afd9ab6904ecb511b59b85866f83d660b32c Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 15:05:45 +0100 Subject: [PATCH 71/99] Add IAP Capability --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2ec5f5c8a8..0b9ee73128 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -789,6 +789,7 @@ D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */; }; D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B42B289AA000CBFA76 /* TestUserScript.swift */; }; D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; + D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -2424,6 +2425,7 @@ D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; D664C7B42B289AA000CBFA76 /* TestUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUserScript.swift; sourceTree = ""; }; D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; + D664C7DC2B28A02800CBFA76 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -2663,6 +2665,7 @@ 85875B6129912A9900115F05 /* SyncUI in Frameworks */, F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, + D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5261,6 +5264,7 @@ F1AA545F1E48D90700223211 /* Frameworks */ = { isa = PBXGroup; children = ( + D664C7DC2B28A02800CBFA76 /* StoreKit.framework */, F1AA54601E48D90700223211 /* NotificationCenter.framework */, 8512EA4E24ED30D20073EE19 /* WidgetKit.framework */, 8512EA5024ED30D20073EE19 /* SwiftUI.framework */, From 3e789421c89b04c937f50d0aedca4d8be739fff5 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 15:10:47 +0100 Subject: [PATCH 72/99] Create debug build configuration --- DuckDuckGo.xcodeproj/project.pbxproj | 409 +++++++++++++++++- .../xcschemes/DuckDuckGo-Alpha.xcscheme | 2 +- 2 files changed, 408 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0b9ee73128..b5b7ba6852 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8782,6 +8782,398 @@ }; name = Release; }; + D664C7DE2B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + }; + name = "Alpha Debug"; + }; + D664C7DF2B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; + CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + INFOPLIST_FILE = DuckDuckGo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development App - Alpha"; + SWIFT_VERSION = 5.0; + }; + name = "Alpha Debug"; + }; + D664C7E02B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).ShareExtension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7E12B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcons; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OpenAction/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).OpenAction2"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7E22B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Widgets/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID).Widgets"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7E32B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + 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_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PacketTunnelProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PacketTunnelProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 DuckDuckGo. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ""; + OTHER_SWIFT_FLAGS = "-D NETWORK_EXTENSION"; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.NetworkExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + D664C7E42B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Core/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Core; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Alpha Debug"; + }; + D664C7E52B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CODE_SIGN_STYLE = Automatic; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Instruments/Packages"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = ""; + }; + name = "Alpha Debug"; + }; + D664C7E62B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = IntegrationTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = "Alpha Debug"; + }; + D664C7E72B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = DuckDuckGoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; + D664C7E82B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = "Alpha Debug"; + }; + D664C7E92B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = IntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; + D664C7EA2B28A0FD00CBFA76 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "IntegrationTests copy-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.IntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DuckDuckGo.app/DuckDuckGo"; + }; + name = "Alpha Debug"; + }; EE5A7C462A82BBB700387C84 /* Alpha */ = { isa = XCBuildConfiguration; baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; @@ -8851,7 +9243,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; @@ -8863,7 +9255,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha; PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development App - Alpha"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.alpha"; SWIFT_VERSION = 5.0; }; name = Alpha; @@ -9260,6 +9652,7 @@ isa = XCConfigurationList; buildConfigurations = ( 0202566D298818B200E694E7 /* Debug */, + D664C7E32B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C4B2A82BBB700387C84 /* Alpha */, 0202566E298818B200E694E7 /* Release */, ); @@ -9270,6 +9663,7 @@ isa = XCConfigurationList; buildConfigurations = ( 025CCFE92582601C001CD5BB /* Debug */, + D664C7E82B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C502A82BBB700387C84 /* Alpha */, 025CCFEA2582601C001CD5BB /* Release */, ); @@ -9280,6 +9674,7 @@ isa = XCConfigurationList; buildConfigurations = ( 8390447820BDCE10006461CD /* Debug */, + D664C7E02B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C482A82BBB700387C84 /* Alpha */, 8390447920BDCE10006461CD /* Release */, ); @@ -9290,6 +9685,7 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341B81E2F7EFC00BDBA6F /* Debug */, + D664C7DE2B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C462A82BBB700387C84 /* Alpha */, 84E341B91E2F7EFC00BDBA6F /* Release */, ); @@ -9300,6 +9696,7 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341BB1E2F7EFC00BDBA6F /* Debug */, + D664C7DF2B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C472A82BBB700387C84 /* Alpha */, 84E341BC1E2F7EFC00BDBA6F /* Release */, ); @@ -9310,6 +9707,7 @@ isa = XCConfigurationList; buildConfigurations = ( 84E341BE1E2F7EFC00BDBA6F /* Debug */, + D664C7E72B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C4F2A82BBB700387C84 /* Alpha */, 84E341BF1E2F7EFC00BDBA6F /* Release */, ); @@ -9320,6 +9718,7 @@ isa = XCConfigurationList; buildConfigurations = ( 8512EA5E24ED30D30073EE19 /* Debug */, + D664C7E22B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C4A2A82BBB700387C84 /* Alpha */, 8512EA5F24ED30D30073EE19 /* Release */, ); @@ -9330,6 +9729,7 @@ isa = XCConfigurationList; buildConfigurations = ( 85482D952462DCD100EDEDD1 /* Debug */, + D664C7E12B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C492A82BBB700387C84 /* Alpha */, 85482D962462DCD100EDEDD1 /* Release */, ); @@ -9340,6 +9740,7 @@ isa = XCConfigurationList; buildConfigurations = ( 85D33FD325C97B6E002B91A6 /* Debug */, + D664C7E92B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C512A82BBB700387C84 /* Alpha */, 85D33FD425C97B6E002B91A6 /* Release */, ); @@ -9350,6 +9751,7 @@ isa = XCConfigurationList; buildConfigurations = ( 85F21DB4210F5E32002631A6 /* Debug */, + D664C7E62B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C4E2A82BBB700387C84 /* Alpha */, 85F21DB5210F5E32002631A6 /* Release */, ); @@ -9360,6 +9762,7 @@ isa = XCConfigurationList; buildConfigurations = ( 9825F9D5293F2DE900F220F2 /* Debug */, + D664C7EA2B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C522A82BBB700387C84 /* Alpha */, 9825F9D6293F2DE900F220F2 /* Release */, ); @@ -9370,6 +9773,7 @@ isa = XCConfigurationList; buildConfigurations = ( 98A54A8622AFCB2D00E541F4 /* Debug */, + D664C7E52B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C4D2A82BBB700387C84 /* Alpha */, 98A54A8722AFCB2D00E541F4 /* Release */, ); @@ -9380,6 +9784,7 @@ isa = XCConfigurationList; buildConfigurations = ( F143C2EE1E4A4CD400CFDE3A /* Debug */, + D664C7E42B28A0FD00CBFA76 /* Alpha Debug */, EE5A7C4C2A82BBB700387C84 /* Alpha */, F143C2EF1E4A4CD400CFDE3A /* Release */, ); diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme index 4d85e9b636..92ab68d4e4 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme @@ -71,7 +71,7 @@ buildConfiguration = "Alpha Debug"> From 53e09743374f49808ea90ea00060e7290dcfeac7 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:03:32 +0100 Subject: [PATCH 73/99] =?UTF-8?q?Hide=20netP=20subtitle=20if=20=E2=80=9C?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DuckDuckGo/SettingsMoreView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index bbf45fab58..c2ba3543a8 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -49,7 +49,7 @@ struct SettingsMoreView: View { #if NETWORK_PROTECTION if viewModel.shouldShowNetworkProtectionCell { SettingsCellView(label: UserText.netPNavTitle, - subtitle: viewModel.state.netPSubtitle, + subtitle: viewModel.state.netPSubtitle != "" ? viewModel.state.netPSubtitle : nil, action: { viewModel.presentLegacyView(.netP) }, asLink: true, disclosureIndicator: true) From ccc49248609a02f0cdeb1759d5ab8722b994b49f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:24:05 +0100 Subject: [PATCH 74/99] Updated missing string --- DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Settings.strings | 2 +- DuckDuckGo/cs.lproj/Settings.strings | 2 +- DuckDuckGo/da.lproj/Settings.strings | 2 +- DuckDuckGo/de.lproj/Settings.strings | 2 +- DuckDuckGo/el.lproj/Settings.strings | 2 +- DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Settings.strings | 2 +- DuckDuckGo/et.lproj/Settings.strings | 2 +- DuckDuckGo/fi.lproj/Settings.strings | 2 +- DuckDuckGo/fr.lproj/Settings.strings | 2 +- DuckDuckGo/hr.lproj/Settings.strings | 2 +- DuckDuckGo/hu.lproj/Settings.strings | 2 +- DuckDuckGo/it.lproj/Settings.strings | 2 +- DuckDuckGo/lt.lproj/Settings.strings | 2 +- DuckDuckGo/lv.lproj/Settings.strings | 2 +- DuckDuckGo/nb.lproj/Settings.strings | 2 +- DuckDuckGo/nl.lproj/Settings.strings | 2 +- DuckDuckGo/pl.lproj/Settings.strings | 2 +- DuckDuckGo/pt.lproj/Settings.strings | 2 +- DuckDuckGo/ro.lproj/Settings.strings | 2 +- DuckDuckGo/ru.lproj/Settings.strings | 2 +- DuckDuckGo/sk.lproj/Settings.strings | 2 +- DuckDuckGo/sl.lproj/Settings.strings | 2 +- DuckDuckGo/sv.lproj/Settings.strings | 2 +- DuckDuckGo/tr.lproj/Settings.strings | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 990dc359fa..99e8cdcaa9 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -942,7 +942,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") - public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "Automatically Clear Data", comment: "Section footer Autolock description") + public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening", comment: "Section footer Autolock description") public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/bg.lproj/Settings.strings b/DuckDuckGo/bg.lproj/Settings.strings index 6887796705..0ccc530fbb 100644 --- a/DuckDuckGo/bg.lproj/Settings.strings +++ b/DuckDuckGo/bg.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Външен Вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; +"settings.autolock.description" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Поверителност"; diff --git a/DuckDuckGo/cs.lproj/Settings.strings b/DuckDuckGo/cs.lproj/Settings.strings index 965b4ff82e..f6b22fdad8 100644 --- a/DuckDuckGo/cs.lproj/Settings.strings +++ b/DuckDuckGo/cs.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; +"settings.autolock.description" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Soukromí"; diff --git a/DuckDuckGo/da.lproj/Settings.strings b/DuckDuckGo/da.lproj/Settings.strings index 631f1f1c56..61b25ae4cc 100644 --- a/DuckDuckGo/da.lproj/Settings.strings +++ b/DuckDuckGo/da.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "udseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; +"settings.autolock.description" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatliv"; diff --git a/DuckDuckGo/de.lproj/Settings.strings b/DuckDuckGo/de.lproj/Settings.strings index 49d0dd41f7..5c4b85e2ea 100644 --- a/DuckDuckGo/de.lproj/Settings.strings +++ b/DuckDuckGo/de.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aussehen"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; +"settings.autolock.description" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatsphäre"; diff --git a/DuckDuckGo/el.lproj/Settings.strings b/DuckDuckGo/el.lproj/Settings.strings index 38a6b1ac4b..ef51181cdf 100644 --- a/DuckDuckGo/el.lproj/Settings.strings +++ b/DuckDuckGo/el.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Εμφάνιση"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; +"settings.autolock.description" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Ιδιωτικότητα"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 1bd0fb0559..5d1e355ea8 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1763,7 +1763,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.autolock" = "Application Lock"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatically Clear Data"; +"settings.autolock.description" = "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatically Clear Data"; diff --git a/DuckDuckGo/es.lproj/Settings.strings b/DuckDuckGo/es.lproj/Settings.strings index 90ce5a3ace..f84076cd7e 100644 --- a/DuckDuckGo/es.lproj/Settings.strings +++ b/DuckDuckGo/es.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apariencia"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; +"settings.autolock.description" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidad"; diff --git a/DuckDuckGo/et.lproj/Settings.strings b/DuckDuckGo/et.lproj/Settings.strings index 14d97bb0ae..250e2c0178 100644 --- a/DuckDuckGo/et.lproj/Settings.strings +++ b/DuckDuckGo/et.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Välimus"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; +"settings.autolock.description" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privaatsus"; diff --git a/DuckDuckGo/fi.lproj/Settings.strings b/DuckDuckGo/fi.lproj/Settings.strings index f684826ce3..b04b23d8fb 100644 --- a/DuckDuckGo/fi.lproj/Settings.strings +++ b/DuckDuckGo/fi.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Ulkoasu"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; +"settings.autolock.description" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Tietosuoja"; diff --git a/DuckDuckGo/fr.lproj/Settings.strings b/DuckDuckGo/fr.lproj/Settings.strings index 2fc2c6d4d7..8c5ee103de 100644 --- a/DuckDuckGo/fr.lproj/Settings.strings +++ b/DuckDuckGo/fr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apparence"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; +"settings.autolock.description" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidentialité"; diff --git a/DuckDuckGo/hr.lproj/Settings.strings b/DuckDuckGo/hr.lproj/Settings.strings index 5b4efa5072..5d7e7c3037 100644 --- a/DuckDuckGo/hr.lproj/Settings.strings +++ b/DuckDuckGo/hr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; +"settings.autolock.description" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zaštita privatnosti"; diff --git a/DuckDuckGo/hu.lproj/Settings.strings b/DuckDuckGo/hu.lproj/Settings.strings index b6f3f5cc23..3a8fa933e3 100644 --- a/DuckDuckGo/hu.lproj/Settings.strings +++ b/DuckDuckGo/hu.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Megjelenés"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; +"settings.autolock.description" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Adatvédelem"; diff --git a/DuckDuckGo/it.lproj/Settings.strings b/DuckDuckGo/it.lproj/Settings.strings index ca6f845e18..df08a1e935 100644 --- a/DuckDuckGo/it.lproj/Settings.strings +++ b/DuckDuckGo/it.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspetto"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; +"settings.autolock.description" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/lt.lproj/Settings.strings b/DuckDuckGo/lt.lproj/Settings.strings index 4f67084ae3..f62e73317c 100644 --- a/DuckDuckGo/lt.lproj/Settings.strings +++ b/DuckDuckGo/lt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Išvaizda"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; +"settings.autolock.description" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatumas"; diff --git a/DuckDuckGo/lv.lproj/Settings.strings b/DuckDuckGo/lv.lproj/Settings.strings index 23f21ecfb1..2d6e25a5f8 100644 --- a/DuckDuckGo/lv.lproj/Settings.strings +++ b/DuckDuckGo/lv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Parādas"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; +"settings.autolock.description" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privātums"; diff --git a/DuckDuckGo/nb.lproj/Settings.strings b/DuckDuckGo/nb.lproj/Settings.strings index 8c3750d5fb..805271b7ab 100644 --- a/DuckDuckGo/nb.lproj/Settings.strings +++ b/DuckDuckGo/nb.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; +"settings.autolock.description" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Personvern"; diff --git a/DuckDuckGo/nl.lproj/Settings.strings b/DuckDuckGo/nl.lproj/Settings.strings index 99c32eca3e..6f30f20ce9 100644 --- a/DuckDuckGo/nl.lproj/Settings.strings +++ b/DuckDuckGo/nl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Uiterlijk"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; +"settings.autolock.description" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/pl.lproj/Settings.strings b/DuckDuckGo/pl.lproj/Settings.strings index 4afbe423ff..0dcec42f97 100644 --- a/DuckDuckGo/pl.lproj/Settings.strings +++ b/DuckDuckGo/pl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Wygląd"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; +"settings.autolock.description" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Prywatność"; diff --git a/DuckDuckGo/pt.lproj/Settings.strings b/DuckDuckGo/pt.lproj/Settings.strings index f136fee8f4..5486c8ac1f 100644 --- a/DuckDuckGo/pt.lproj/Settings.strings +++ b/DuckDuckGo/pt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aparência"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; +"settings.autolock.description" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidade"; diff --git a/DuckDuckGo/ro.lproj/Settings.strings b/DuckDuckGo/ro.lproj/Settings.strings index 4404ec5c0d..63a21baa33 100644 --- a/DuckDuckGo/ro.lproj/Settings.strings +++ b/DuckDuckGo/ro.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspect"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; +"settings.autolock.description" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidențialitate"; diff --git a/DuckDuckGo/ru.lproj/Settings.strings b/DuckDuckGo/ru.lproj/Settings.strings index 6b54a2d6c0..dd14621132 100644 --- a/DuckDuckGo/ru.lproj/Settings.strings +++ b/DuckDuckGo/ru.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Внешний вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; +"settings.autolock.description" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Настройки конфиденциальности"; diff --git a/DuckDuckGo/sk.lproj/Settings.strings b/DuckDuckGo/sk.lproj/Settings.strings index 5aba47196d..4fa93be816 100644 --- a/DuckDuckGo/sk.lproj/Settings.strings +++ b/DuckDuckGo/sk.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhľad"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; +"settings.autolock.description" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Súkromie"; diff --git a/DuckDuckGo/sl.lproj/Settings.strings b/DuckDuckGo/sl.lproj/Settings.strings index ec4d2b7bf2..4cf888ab95 100644 --- a/DuckDuckGo/sl.lproj/Settings.strings +++ b/DuckDuckGo/sl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; +"settings.autolock.description" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zasebnost"; diff --git a/DuckDuckGo/sv.lproj/Settings.strings b/DuckDuckGo/sv.lproj/Settings.strings index 5a1b5101ff..d31cada261 100644 --- a/DuckDuckGo/sv.lproj/Settings.strings +++ b/DuckDuckGo/sv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; +"settings.autolock.description" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Sekretess"; diff --git a/DuckDuckGo/tr.lproj/Settings.strings b/DuckDuckGo/tr.lproj/Settings.strings index 81c5b0fc5d..0d7a481c9c 100644 --- a/DuckDuckGo/tr.lproj/Settings.strings +++ b/DuckDuckGo/tr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Görünüm"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; +"settings.autolock.description" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Gizlilik"; From ccb0e3d42b2996aa784f9cdbf6cb32d59fe352a8 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:30:47 +0100 Subject: [PATCH 75/99] Revert Localizable change --- DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Localizable.strings | 2 +- DuckDuckGo/bg.lproj/Settings.strings | 2 +- DuckDuckGo/cs.lproj/Localizable.strings | 2 +- DuckDuckGo/cs.lproj/Settings.strings | 2 +- DuckDuckGo/da.lproj/Localizable.strings | 2 +- DuckDuckGo/da.lproj/Settings.strings | 2 +- DuckDuckGo/de.lproj/Localizable.strings | 2 +- DuckDuckGo/de.lproj/Settings.strings | 2 +- DuckDuckGo/el.lproj/Localizable.strings | 2 +- DuckDuckGo/el.lproj/Settings.strings | 2 +- DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Settings.strings | 2 +- DuckDuckGo/et.lproj/Localizable.strings | 2 +- DuckDuckGo/et.lproj/Settings.strings | 2 +- DuckDuckGo/fi.lproj/Localizable.strings | 2 +- DuckDuckGo/fi.lproj/Settings.strings | 2 +- DuckDuckGo/fr.lproj/Localizable.strings | 2 +- DuckDuckGo/fr.lproj/Settings.strings | 2 +- DuckDuckGo/hr.lproj/Localizable.strings | 2 +- DuckDuckGo/hr.lproj/Settings.strings | 2 +- DuckDuckGo/hu.lproj/Localizable.strings | 2 +- DuckDuckGo/hu.lproj/Settings.strings | 2 +- DuckDuckGo/it.lproj/Localizable.strings | 2 +- DuckDuckGo/it.lproj/Settings.strings | 2 +- DuckDuckGo/lt.lproj/Localizable.strings | 2 +- DuckDuckGo/lt.lproj/Settings.strings | 2 +- DuckDuckGo/lv.lproj/Localizable.strings | 2 +- DuckDuckGo/lv.lproj/Settings.strings | 2 +- DuckDuckGo/nb.lproj/Localizable.strings | 2 +- DuckDuckGo/nb.lproj/Settings.strings | 2 +- DuckDuckGo/nl.lproj/Localizable.strings | 2 +- DuckDuckGo/nl.lproj/Settings.strings | 2 +- DuckDuckGo/pl.lproj/Localizable.strings | 2 +- DuckDuckGo/pl.lproj/Settings.strings | 2 +- DuckDuckGo/pt.lproj/Localizable.strings | 2 +- DuckDuckGo/pt.lproj/Settings.strings | 2 +- DuckDuckGo/ro.lproj/Localizable.strings | 2 +- DuckDuckGo/ro.lproj/Settings.strings | 2 +- DuckDuckGo/ru.lproj/Localizable.strings | 2 +- DuckDuckGo/ru.lproj/Settings.strings | 2 +- DuckDuckGo/sk.lproj/Localizable.strings | 2 +- DuckDuckGo/sk.lproj/Settings.strings | 2 +- DuckDuckGo/sl.lproj/Localizable.strings | 2 +- DuckDuckGo/sl.lproj/Settings.strings | 2 +- DuckDuckGo/sv.lproj/Localizable.strings | 2 +- DuckDuckGo/sv.lproj/Settings.strings | 2 +- DuckDuckGo/tr.lproj/Localizable.strings | 2 +- DuckDuckGo/tr.lproj/Settings.strings | 2 +- 50 files changed, 50 insertions(+), 50 deletions(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 99e8cdcaa9..eda0e35309 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -942,7 +942,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") - public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening", comment: "Section footer Autolock description") + public static let settingsAutoLockDescription = NSLocalizedString("dOj-jn-mSN.footerTitle", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index cda2a56879..d1d2f54891 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Заключване на приложение"; /* Section footer Autolock description */ -"settings.autolock.description" = "Автоматично изчистване на данните"; +"dOj-jn-mSN.footerTitle" = "Автоматично изчистване на данните"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоматично изчистване на данните"; diff --git a/DuckDuckGo/bg.lproj/Settings.strings b/DuckDuckGo/bg.lproj/Settings.strings index 0ccc530fbb..6887796705 100644 --- a/DuckDuckGo/bg.lproj/Settings.strings +++ b/DuckDuckGo/bg.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Външен Вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; +"dOj-jn-mSN.footerTitle" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Поверителност"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 93a782a622..0647af6194 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -1691,7 +1691,7 @@ "settings.autolock" = "Zámek aplikace"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automaticky vymazat data"; +"dOj-jn-mSN.footerTitle" = "Automaticky vymazat data"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazat data"; diff --git a/DuckDuckGo/cs.lproj/Settings.strings b/DuckDuckGo/cs.lproj/Settings.strings index f6b22fdad8..965b4ff82e 100644 --- a/DuckDuckGo/cs.lproj/Settings.strings +++ b/DuckDuckGo/cs.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; +"dOj-jn-mSN.footerTitle" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Soukromí"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index fa5de6c141..6a82f66bba 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Applikationslås"; /* Section footer Autolock description */ -"settings.autolock.description" = "Ryd data automatisk"; +"dOj-jn-mSN.footerTitle" = "Ryd data automatisk"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Ryd data automatisk"; diff --git a/DuckDuckGo/da.lproj/Settings.strings b/DuckDuckGo/da.lproj/Settings.strings index 61b25ae4cc..631f1f1c56 100644 --- a/DuckDuckGo/da.lproj/Settings.strings +++ b/DuckDuckGo/da.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "udseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; +"dOj-jn-mSN.footerTitle" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatliv"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 64b930603b..f76b1b9aba 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Anwendungssperre"; /* Section footer Autolock description */ -"settings.autolock.description" = "Daten automatisch löschen"; +"dOj-jn-mSN.footerTitle" = "Daten automatisch löschen"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Daten automatisch löschen"; diff --git a/DuckDuckGo/de.lproj/Settings.strings b/DuckDuckGo/de.lproj/Settings.strings index 5c4b85e2ea..49d0dd41f7 100644 --- a/DuckDuckGo/de.lproj/Settings.strings +++ b/DuckDuckGo/de.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aussehen"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; +"dOj-jn-mSN.footerTitle" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatsphäre"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 27a65200ca..087a1fd7f7 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Κλείδωμα εφαρμογής"; /* Section footer Autolock description */ -"settings.autolock.description" = "Αυτόματη απαλοιφή δεδομένων"; +"dOj-jn-mSN.footerTitle" = "Αυτόματη απαλοιφή δεδομένων"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Αυτόματη απαλοιφή δεδομένων"; diff --git a/DuckDuckGo/el.lproj/Settings.strings b/DuckDuckGo/el.lproj/Settings.strings index ef51181cdf..38a6b1ac4b 100644 --- a/DuckDuckGo/el.lproj/Settings.strings +++ b/DuckDuckGo/el.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Εμφάνιση"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; +"dOj-jn-mSN.footerTitle" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Ιδιωτικότητα"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 5d1e355ea8..74942f9d3c 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1763,7 +1763,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.autolock" = "Application Lock"; /* Section footer Autolock description */ -"settings.autolock.description" = "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening"; +"dOj-jn-mSN.footerTitle" = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatically Clear Data"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index c974029da3..82b0fb12af 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Bloqueo de aplicación"; /* Section footer Autolock description */ -"settings.autolock.description" = "Borrar datos automáticamente"; +"dOj-jn-mSN.footerTitle" = "Borrar datos automáticamente"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Borrar datos automáticamente"; diff --git a/DuckDuckGo/es.lproj/Settings.strings b/DuckDuckGo/es.lproj/Settings.strings index f84076cd7e..90ce5a3ace 100644 --- a/DuckDuckGo/es.lproj/Settings.strings +++ b/DuckDuckGo/es.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apariencia"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; +"dOj-jn-mSN.footerTitle" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidad"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index d5feec758b..65f2831d02 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Rakenduse lukk"; /* Section footer Autolock description */ -"settings.autolock.description" = "Kustuta andmed automaatselt"; +"dOj-jn-mSN.footerTitle" = "Kustuta andmed automaatselt"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Kustuta andmed automaatselt"; diff --git a/DuckDuckGo/et.lproj/Settings.strings b/DuckDuckGo/et.lproj/Settings.strings index 250e2c0178..14d97bb0ae 100644 --- a/DuckDuckGo/et.lproj/Settings.strings +++ b/DuckDuckGo/et.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Välimus"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; +"dOj-jn-mSN.footerTitle" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privaatsus"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index a20894d098..aa6d072b26 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Sovelluksen lukitus"; /* Section footer Autolock description */ -"settings.autolock.description" = "Tyhjennä tiedot automaattisesti"; +"dOj-jn-mSN.footerTitle" = "Tyhjennä tiedot automaattisesti"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Tyhjennä tiedot automaattisesti"; diff --git a/DuckDuckGo/fi.lproj/Settings.strings b/DuckDuckGo/fi.lproj/Settings.strings index b04b23d8fb..f684826ce3 100644 --- a/DuckDuckGo/fi.lproj/Settings.strings +++ b/DuckDuckGo/fi.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Ulkoasu"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; +"dOj-jn-mSN.footerTitle" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Tietosuoja"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 7ce5b93429..1672a095e1 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Verrouillage de l'application"; /* Section footer Autolock description */ -"settings.autolock.description" = "Effacer automatiquement les données"; +"dOj-jn-mSN.footerTitle" = "Effacer automatiquement les données"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Effacer automatiquement les données"; diff --git a/DuckDuckGo/fr.lproj/Settings.strings b/DuckDuckGo/fr.lproj/Settings.strings index 8c5ee103de..2fc2c6d4d7 100644 --- a/DuckDuckGo/fr.lproj/Settings.strings +++ b/DuckDuckGo/fr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apparence"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; +"dOj-jn-mSN.footerTitle" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidentialité"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index fd0584e941..120056b994 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Zaključavanje aplikacije"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatsko brisanje podataka"; +"dOj-jn-mSN.footerTitle" = "Automatsko brisanje podataka"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatsko brisanje podataka"; diff --git a/DuckDuckGo/hr.lproj/Settings.strings b/DuckDuckGo/hr.lproj/Settings.strings index 5d7e7c3037..5b4efa5072 100644 --- a/DuckDuckGo/hr.lproj/Settings.strings +++ b/DuckDuckGo/hr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; +"dOj-jn-mSN.footerTitle" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zaštita privatnosti"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 289a0808d9..b1d6ff0980 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Alkalmazás zárolás"; /* Section footer Autolock description */ -"settings.autolock.description" = "Adatok automatikus törlése"; +"dOj-jn-mSN.footerTitle" = "Adatok automatikus törlése"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Adatok automatikus törlése"; diff --git a/DuckDuckGo/hu.lproj/Settings.strings b/DuckDuckGo/hu.lproj/Settings.strings index 3a8fa933e3..b6f3f5cc23 100644 --- a/DuckDuckGo/hu.lproj/Settings.strings +++ b/DuckDuckGo/hu.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Megjelenés"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; +"dOj-jn-mSN.footerTitle" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Adatvédelem"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index 81eb7f6111..b8ef30c37b 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Blocco applicazione"; /* Section footer Autolock description */ -"settings.autolock.description" = "Cancellazione automatica dei dati"; +"dOj-jn-mSN.footerTitle" = "Cancellazione automatica dei dati"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Cancellazione automatica dei dati"; diff --git a/DuckDuckGo/it.lproj/Settings.strings b/DuckDuckGo/it.lproj/Settings.strings index df08a1e935..ca6f845e18 100644 --- a/DuckDuckGo/it.lproj/Settings.strings +++ b/DuckDuckGo/it.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspetto"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; +"dOj-jn-mSN.footerTitle" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 87425c6d70..3d2814b6a2 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Programos užraktas"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatiškai valyti duomenis"; +"dOj-jn-mSN.footerTitle" = "Automatiškai valyti duomenis"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatiškai valyti duomenis"; diff --git a/DuckDuckGo/lt.lproj/Settings.strings b/DuckDuckGo/lt.lproj/Settings.strings index f62e73317c..4f67084ae3 100644 --- a/DuckDuckGo/lt.lproj/Settings.strings +++ b/DuckDuckGo/lt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Išvaizda"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; +"dOj-jn-mSN.footerTitle" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatumas"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 3a1c3c0137..52d30b5ce4 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Lietojumprogrammas bloķēšana"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automātiski notīrīt datus"; +"dOj-jn-mSN.footerTitle" = "Automātiski notīrīt datus"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automātiski notīrīt datus"; diff --git a/DuckDuckGo/lv.lproj/Settings.strings b/DuckDuckGo/lv.lproj/Settings.strings index 2d6e25a5f8..23f21ecfb1 100644 --- a/DuckDuckGo/lv.lproj/Settings.strings +++ b/DuckDuckGo/lv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Parādas"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; +"dOj-jn-mSN.footerTitle" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privātums"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 407e416b85..1a254fb913 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -1781,7 +1781,7 @@ "settings.autolock" = "Applås"; /* Section footer Autolock description */ -"settings.autolock.description" = "Slett data automatisk"; +"dOj-jn-mSN.footerTitle" = "Slett data automatisk"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Slett data automatisk"; diff --git a/DuckDuckGo/nb.lproj/Settings.strings b/DuckDuckGo/nb.lproj/Settings.strings index 805271b7ab..8c3750d5fb 100644 --- a/DuckDuckGo/nb.lproj/Settings.strings +++ b/DuckDuckGo/nb.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; +"dOj-jn-mSN.footerTitle" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Personvern"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 8bdb8046e0..16a4df7468 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "App-vergrendeling"; /* Section footer Autolock description */ -"settings.autolock.description" = "Gegevens automatisch wissen"; +"dOj-jn-mSN.footerTitle" = "Gegevens automatisch wissen"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Gegevens automatisch wissen"; diff --git a/DuckDuckGo/nl.lproj/Settings.strings b/DuckDuckGo/nl.lproj/Settings.strings index 6f30f20ce9..99c32eca3e 100644 --- a/DuckDuckGo/nl.lproj/Settings.strings +++ b/DuckDuckGo/nl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Uiterlijk"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; +"dOj-jn-mSN.footerTitle" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 4d771ab66d..9c59af675f 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Blokada aplikacji"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatyczne czyszczenie danych"; +"dOj-jn-mSN.footerTitle" = "Automatyczne czyszczenie danych"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatyczne czyszczenie danych"; diff --git a/DuckDuckGo/pl.lproj/Settings.strings b/DuckDuckGo/pl.lproj/Settings.strings index 0dcec42f97..4afbe423ff 100644 --- a/DuckDuckGo/pl.lproj/Settings.strings +++ b/DuckDuckGo/pl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Wygląd"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; +"dOj-jn-mSN.footerTitle" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Prywatność"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index e25e0f3044..07f1fe4c1a 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Bloqueio de aplicações"; /* Section footer Autolock description */ -"settings.autolock.description" = "Limpar os dados automaticamente"; +"dOj-jn-mSN.footerTitle" = "Limpar os dados automaticamente"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Limpar os dados automaticamente"; diff --git a/DuckDuckGo/pt.lproj/Settings.strings b/DuckDuckGo/pt.lproj/Settings.strings index 5486c8ac1f..f136fee8f4 100644 --- a/DuckDuckGo/pt.lproj/Settings.strings +++ b/DuckDuckGo/pt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aparência"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; +"dOj-jn-mSN.footerTitle" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidade"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index bfb0bbf1df..3a135ca1bf 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Blocarea aplicației"; /* Section footer Autolock description */ -"settings.autolock.description" = "Șterge automat datele"; +"dOj-jn-mSN.footerTitle" = "Șterge automat datele"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Șterge automat datele"; diff --git a/DuckDuckGo/ro.lproj/Settings.strings b/DuckDuckGo/ro.lproj/Settings.strings index 63a21baa33..4404ec5c0d 100644 --- a/DuckDuckGo/ro.lproj/Settings.strings +++ b/DuckDuckGo/ro.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspect"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; +"dOj-jn-mSN.footerTitle" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidențialitate"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 43b718f1c2..6cc266c4ef 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Блокировка"; /* Section footer Autolock description */ -"settings.autolock.description" = "Автоудаление данных"; +"dOj-jn-mSN.footerTitle" = "Автоудаление данных"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоудаление данных"; diff --git a/DuckDuckGo/ru.lproj/Settings.strings b/DuckDuckGo/ru.lproj/Settings.strings index dd14621132..6b54a2d6c0 100644 --- a/DuckDuckGo/ru.lproj/Settings.strings +++ b/DuckDuckGo/ru.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Внешний вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; +"dOj-jn-mSN.footerTitle" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Настройки конфиденциальности"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 25e901ed93..6fdccf112b 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Zámok aplikácie"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automaticky vymazať údaje"; +"dOj-jn-mSN.footerTitle" = "Automaticky vymazať údaje"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazať údaje"; diff --git a/DuckDuckGo/sk.lproj/Settings.strings b/DuckDuckGo/sk.lproj/Settings.strings index 4fa93be816..5aba47196d 100644 --- a/DuckDuckGo/sk.lproj/Settings.strings +++ b/DuckDuckGo/sk.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhľad"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; +"dOj-jn-mSN.footerTitle" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Súkromie"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 8f3e9e7d01..0c65a91575 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Zaklepanje aplikacije"; /* Section footer Autolock description */ -"settings.autolock.description" = "Samodejno počisti podatke"; +"dOj-jn-mSN.footerTitle" = "Samodejno počisti podatke"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Samodejno počisti podatke"; diff --git a/DuckDuckGo/sl.lproj/Settings.strings b/DuckDuckGo/sl.lproj/Settings.strings index 4cf888ab95..ec4d2b7bf2 100644 --- a/DuckDuckGo/sl.lproj/Settings.strings +++ b/DuckDuckGo/sl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; +"dOj-jn-mSN.footerTitle" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zasebnost"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 603e62d7d0..089e6b318b 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "App-lås"; /* Section footer Autolock description */ -"settings.autolock.description" = "Rensa data automatiskt"; +"dOj-jn-mSN.footerTitle" = "Rensa data automatiskt"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Rensa data automatiskt"; diff --git a/DuckDuckGo/sv.lproj/Settings.strings b/DuckDuckGo/sv.lproj/Settings.strings index d31cada261..5a1b5101ff 100644 --- a/DuckDuckGo/sv.lproj/Settings.strings +++ b/DuckDuckGo/sv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; +"dOj-jn-mSN.footerTitle" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Sekretess"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 8507885afb..07cadb7e71 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Uygulama Kilidi"; /* Section footer Autolock description */ -"settings.autolock.description" = "Verileri Otomatik Olarak Temizle"; +"dOj-jn-mSN.footerTitle" = "Verileri Otomatik Olarak Temizle"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Verileri Otomatik Olarak Temizle"; diff --git a/DuckDuckGo/tr.lproj/Settings.strings b/DuckDuckGo/tr.lproj/Settings.strings index 0d7a481c9c..81c5b0fc5d 100644 --- a/DuckDuckGo/tr.lproj/Settings.strings +++ b/DuckDuckGo/tr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Görünüm"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; +"dOj-jn-mSN.footerTitle" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Gizlilik"; From 9634cffc31dd4ba9d6c12d6a8aa0a69c46ac6095 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:58:55 +0100 Subject: [PATCH 76/99] Updated translations from Smartling --- DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/cs.lproj/Localizable.strings | 103 ++++++++++++++-- DuckDuckGo/da.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/de.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/el.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/et.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/fi.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/fr.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/hr.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/hu.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/it.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/lt.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/lv.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/nb.lproj/Localizable.strings | 5 +- DuckDuckGo/nb.lproj/Settings.strings | 156 ------------------------ DuckDuckGo/nl.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/pl.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/pt.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/ro.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/ru.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/sk.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/sl.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/sv.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/tr.lproj/Localizable.strings | 129 +++++++++++++++++--- 27 files changed, 2540 insertions(+), 566 deletions(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index eda0e35309..fbcf8e7daa 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -942,7 +942,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") - public static let settingsAutoLockDescription = NSLocalizedString("dOj-jn-mSN.footerTitle", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") + public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index d1d2f54891..4a6a8c2cd0 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Редактиране"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Интернет може да бъде и доста опасно място.\n\nНе се притеснявайте! Поверителното търсене и сърфиране е много по-лесно, отколкото си мислите."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Деактивирано"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Политика за поверителност"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Имам код за покана"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Първи стъпки"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Включени сте в списъка с чакащи!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Отворете поканата"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Включени сте в списъка с чакащи!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Включване в списък с чакащи за личен адрес"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Известия за VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Автоматично възстановяване на VPN връзка след прекъсване."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Винаги включено"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Известия за VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Мрежовата защита предотвратява изтичането на DNS към Вашия доставчик на интернет услуги, като насочва DNS заявките през VPN тунела към нашия собствен резолвер."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Сигурен DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Настройки на VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Заключване на приложение"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Автоматично изчистване на данните"; +"settings.autolock.description" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоматично изчистване на данните"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 0647af6194..b9645433ca 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Upravit"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet může být trochu strašidelný.\n\nNebojte se! Anonymní vyhledávání a procházení je jednodušší, než si myslíte."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Zakázáno"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Zásady ochrany osobních údajů"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mám zvací kód"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Začínáme"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Jsi v pořadníku!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otevřít pozvánku"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Jsi v pořadníku!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Připojte se k soukromému pořadníku"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1691,7 +1778,7 @@ "settings.autolock" = "Zámek aplikace"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automaticky vymazat data"; +"settings.autolock.description" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazat data"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 6a82f66bba..9ceaca8ac2 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Rediger"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internettet kan være ret uhyggeligt.\n\nBare rolig! Det er lettere at søge og browse privat, end du tror."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Deaktiveret"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privatlivspolitik"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Jeg har en invitationskode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Kom igang"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du er på listen!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Åbn din invitation"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du er på listen!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Slut dig til den private venteliste"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-advarsler"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Gendan automatisk en VPN-forbindelse efter afbrydelse."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Altid aktiv"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-meddelelser"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection forhindrer DNS-lækager til din internetudbyder ved at dirigere DNS-forespørgsler gennem VPN-tunnelen til vores egen resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Sikker DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-indstillinger"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Applikationslås"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Ryd data automatisk"; +"settings.autolock.description" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Ryd data automatisk"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index f76b1b9aba..816de684f4 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Bearbeiten"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Das Internet kann ein bisschen gruselig sein.\n\nKeine Sorge! Privat zu suchen und zu browsen ist einfacher als du denkst."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Deaktiviert"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Datenschutzrichtlinie"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Ich habe einen Einladungscode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Los geht's!"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du stehst auf der Liste!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Öffne deine Einladung"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du stehst auf der Liste!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Auf die private Warteliste setzen"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-Benachrichtigungen"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Stelle eine VPN-Verbindung nach einer Unterbrechung automatisch wieder her."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Immer aktiviert"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-Benachrichtigungen"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection verhindert DNS-Lecks zu deinem Internetanbieter, indem DNS-Anfragen über den VPN-Tunnel an unseren eigenen Resolver weitergeleitet werden."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Sicheres DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-Einstellungen"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Anwendungssperre"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Daten automatisch löschen"; +"settings.autolock.description" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Daten automatisch löschen"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 087a1fd7f7..5500609257 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Επεξεργασία"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Το Διαδίκτυο μπορεί να είναι κάπως ανατριχιαστικό.\n\nΜην ανησυχείτε! Το να πραγματοποιείτε αναζήτηση και περιήγηση ιδιωτικά είναι πιο εύκολο απ' όσο νομίζετε."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Απενεργοποιήθηκε"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Πολιτική ιδιωτικού απορρήτου"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Διαθέτω Κωδικό πρόσκλησης"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Ξεκινήστε"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Βρίσκεστε στη λίστα!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Ανοίξτε την πρόσκλησή σας"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Βρίσκεστε στη λίστα!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Εγγραφείτε στην Ιδιωτική λίστα αναμονής"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Ειδοποιήσεις VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Αυτόματη αποκατάσταση μιας σύνδεσης VPN έπειτα από διακοπή."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Πάντα σε λειτουργία"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Ειδοποιήσεις VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Η Προστασία δικτύου αποτρέπει διαρροές DNS προς τον πάροχο υπηρεσιών διαδικτύου σας, δρομολογώντας ερωτήματα DNS μέσω της σήραγγας VPN στο δικό μας πρόγραμμα επίλυσης."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Ασφαλές DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Ρυθμίσεις VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Κλείδωμα εφαρμογής"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Αυτόματη απαλοιφή δεδομένων"; +"settings.autolock.description" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Αυτόματη απαλοιφή δεδομένων"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 74942f9d3c..dcb69ae89f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1763,7 +1763,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.autolock" = "Application Lock"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; +"settings.autolock.description" = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatically Clear Data"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 82b0fb12af..aaae0c5bf1 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Editar"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet puede ser un lugar horrible.\n\nNo te preocupes. Buscar y navegar de forma privada es más fácil de lo que piensas."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Desactivado"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Política de privacidad"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Tengo un código de invitación"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Empezar"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "¡Estás en la lista!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Abrir tu invitación"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "¡Estás en la lista!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Unirse a la lista de espera privada"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alertas de VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaura automáticamente una conexión VPN después de una interrupción."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Siempre activado"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notificaciones de VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "La protección de red evita las filtraciones DNS a tu proveedor de servicios de internet redirigiendo las consultas de DNS a través del túnel VPN a nuestro propio resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS seguro"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Configuración de VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Bloqueo de aplicación"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Borrar datos automáticamente"; +"settings.autolock.description" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Borrar datos automáticamente"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index 65f2831d02..e7b47e1c12 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Redigeeri"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet võib olla üsna jube.\n\nÄra muretse! Privaatselt otsimine ja sirvimine on lihtsam kui arvad."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Keelatud"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privaatsuspoliitika"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mul on kutse kood"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Alustage"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Oled ootenimekirjas!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Ava oma kutse"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Oled ootenimekirjas!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Liituge privaatse ootenimekirjaga"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-i hoiatused"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "VPN-ühendus taastatakse pärast katkestust automaatselt."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Alati sisse lülitatud"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-i teavitused"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection takistab DNS-i lekkeid sinu internetiteenuse pakkujale, suunates DNS-päringud läbi VPN-tunneli meie enda aadressiteisendusteenusesse."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Turvaline DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-i seaded"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Rakenduse lukk"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Kustuta andmed automaatselt"; +"settings.autolock.description" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Kustuta andmed automaatselt"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index aa6d072b26..672d8a244d 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Muokkaa"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet voi olla pelottava paikka.\n\nMutta ei hätää! Yksityinen haku ja selaaminen on helpompaa kuin luulet."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Pois käytöstä"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Tietosuojakäytäntö"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Minulla on kutsukoodi"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Aloita"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Olet odotuslistalla!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Avaa kutsusi"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Olet odotuslistalla!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Liity yksityiselle odotuslistalle"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-ilmoitukset"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Palauta VPN-yhteys automaattisesti keskeytyksen jälkeen."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Aina käytössä"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-ilmoitukset"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection estää DNS-vuodot internetpalveluntarjoajallesi ohjaamalla DNS-pyynnöt VPN-verkon kautta omalle välittäjällemme."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Suojattu DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-asetukset"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Sovelluksen lukitus"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Tyhjennä tiedot automaattisesti"; +"settings.autolock.description" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Tyhjennä tiedot automaattisesti"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 1672a095e1..0c20412874 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Modifier"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet peut être intrusif.\n\nMais ne vous inquiétez pas ! Rechercher et naviguer de manière confidentielle est plus facile que vous ne le pensez."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Désactivé"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Politique de confidentialité"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "J'ai un code d'invitation"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Commencer"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Vous êtes sur liste d'attente !"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Ouvrez votre invitation"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Vous êtes sur liste d'attente !"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Rejoindre la liste d'attente privée"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alertes VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaurez automatiquement une connexion VPN après une interruption."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Toujours activé"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notifications VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection empêche les fuites DNS vers votre fournisseur de services Internet en acheminant les requêtes DNS via le tunnel VPN vers notre propre résolveur."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS sécurisé"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Paramètres VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Verrouillage de l'application"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Effacer automatiquement les données"; +"settings.autolock.description" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Effacer automatiquement les données"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 120056b994..33a5f3c036 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Uredi"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet može biti pomalo neugodan.\n\nNe brini! Privatno pretraživanje i pregledavanje lakše je nego što misliš."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Onemogućeno"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Pravila o zaštiti privatnosti"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Imam pozivnu šifru"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Započnite"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Na popisu ste!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otvori svoju pozivnicu"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Na popisu ste!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pridružite se privatnoj listi čekanja"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN upozorenja"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatski obnovi VPN vezu nakon prekida."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Uvijek uključeno"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN obavijesti"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Mrežna zaštita sprječava curenje DNS-a tvom davatelju internetskih usluga usmjeravanjem DNS upita kroz VPN tunel na naš vlastiti rješavač."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Siguran DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN postavke"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Zaključavanje aplikacije"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automatsko brisanje podataka"; +"settings.autolock.description" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatsko brisanje podataka"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index b1d6ff0980..e955b5d849 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Szerkesztés"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Az internet meglehetősen undok hely lehet.\n\nNe aggódj! A bizalmas keresés és böngészés egyszerűbb, mint hinnéd."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Letiltva"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Adatvédelmi szabályzat"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Van meghívókódom"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Első lépések"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Felkerültél a listára!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Meghívó megnyitása"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Felkerültél a listára!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Csatlakozz a privát várólistához"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-figyelmeztetések"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "A VPN-kapcsolat automatikus helyreállítása megszakítás után."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Mindig be van kapcsolva"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-értesítések"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "A hálózatvédelem megakadályozza a DNS-szivárgást az internetszolgáltatód felé azáltal, hogy a DNS-lekérdezéseket a VPN-alagúton keresztül a saját feloldónkhoz irányítja."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Biztonságos DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-beállítások"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Alkalmazás zárolás"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Adatok automatikus törlése"; +"settings.autolock.description" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Adatok automatikus törlése"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index b8ef30c37b..3e04c792df 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Modifica"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet può essere un po' inquietante.\n\nNon preoccuparti! Effettuare ricerche e navigare in modo privato è più facile di quanto pensi."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Disattivato"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privacy policy"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Ho un codice invito"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Iniziamo"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Sei in lista d'attesa."; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Apri il tuo invito"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Sei in lista d'attesa."; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Iscriviti alla lista d'attesa privata"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Avvisi VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Ripristina automaticamente una connessione VPN dopo un'interruzione."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Sempre attiva"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notifiche VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection previene le fughe di DNS verso il tuo Internet Service Provider instradando le query DNS tramite tunneling VPN verso il nostro resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS sicuro"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Impostazioni VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Blocco applicazione"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Cancellazione automatica dei dati"; +"settings.autolock.description" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Cancellazione automatica dei dati"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 3d2814b6a2..85973a6334 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Redaguoti"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internetas gali būti bauginantis.\n\nNesijaudink! Ieškoti ir naršyti privačiai yra lengviau, nei manai."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Išjungta"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privatumo Politika"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Turiu kvietimo kodą"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Pradėti"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Esate sąraše!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Atidaryti pakvietimą"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Esate sąraše!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Prisijungti prie privataus laukimo sąrašo"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN įspėjimai"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatiškai atkurti VPN ryšį po nutrūkimo."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Visada įjungta"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN pranešimai"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Tinklo apsauga apsaugo nuo DNS nutekėjimo interneto paslaugų teikėjui, nukreipdama DNS užklausas per VPN tunelį į mūsų trūkumo šalinimo įrankį."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Saugi DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN nustatymai"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Programos užraktas"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automatiškai valyti duomenis"; +"settings.autolock.description" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatiškai valyti duomenis"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 52d30b5ce4..668740e6b4 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Rediģēt"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internets var būt diezgan “mežonīgs”.\n\nBet neuztraucies! Meklēt un pārlūkot privātā režīmā ir vieglāk, nekā tu domā."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Atspējota"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privātuma politika"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Man ir uzaicinājuma kods"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Sākt darbu"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Tu esi sarakstā!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Atver savu ielūgumu"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Tu esi sarakstā!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pievienojies privātajam nogaides sarakstam"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN brīdinājumi"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automātiski atjaunot VPN savienojumu pēc pārtraukuma."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vienmēr ieslēgts"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN paziņojumi"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Tīkla aizsardzība novērš DNS noplūdi uz tavu interneta pakalpojumu sniedzēju, novirzot DNS vaicājumus caur VPN tuneli uz mūsu pašu atrisinātāju."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Drošs DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN iestatījumi"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Lietojumprogrammas bloķēšana"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automātiski notīrīt datus"; +"settings.autolock.description" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automātiski notīrīt datus"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 1a254fb913..6e49d83cc6 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Rediger"; @@ -1781,7 +1778,7 @@ "settings.autolock" = "Applås"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Slett data automatisk"; +"settings.autolock.description" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Slett data automatisk"; diff --git a/DuckDuckGo/nb.lproj/Settings.strings b/DuckDuckGo/nb.lproj/Settings.strings index 8c3750d5fb..95cb40ddef 100644 --- a/DuckDuckGo/nb.lproj/Settings.strings +++ b/DuckDuckGo/nb.lproj/Settings.strings @@ -1,15 +1,3 @@ -/* Class = "UILabel"; text = "Fireproof Sites"; ObjectID = "0DQ-yq-UuT"; */ -"0DQ-yq-UuT.text" = "Brannsikre nettsteder"; - -/* Class = "UITableViewSection"; footerTitle = "Disable to prevent links from automatically opening in other installed apps."; ObjectID = "0gU-rZ-pRc"; */ -"0gU-rZ-pRc.footerTitle" = "Deaktiver for å forhindre at lenker automatisk åpnes i andre installerte apper."; - -/* Class = "UITableViewSection"; headerTitle = "Customize"; ObjectID = "0gU-rZ-pRc"; */ -"0gU-rZ-pRc.headerTitle" = "Tilpass"; - -/* Class = "UILabel"; text = "Version"; ObjectID = "2ky-s9-1aZ"; */ -"2ky-s9-1aZ.text" = "Versjon"; - /* Class = "UINavigationItem"; title = "Keyboard"; ObjectID = "2pp-PM-6rW"; */ "2pp-PM-6rW.title" = "Tastatur"; @@ -31,30 +19,9 @@ /* Class = "UILabel"; text = "Clear Tabs and Data"; ObjectID = "9fc-9r-4aA"; */ "9fc-9r-4aA.text" = "Lukk faner og slett data"; -/* Class = "UILabel"; text = "Text Size"; ObjectID = "9Ko-0g-T3h"; */ -"9Ko-0g-T3h.text" = "Tekststørrelse"; - -/* Class = "UILabel"; text = "Title"; ObjectID = "9kt-6R-XiZ"; */ -"9kt-6R-XiZ.text" = "Tittel"; - /* Class = "UILabel"; text = "App Launch"; ObjectID = "13n-KI-KLq"; */ "13n-KI-KLq.text" = "Appoversikt"; -/* Class = "UILabel"; text = "Open Links in Associated Apps"; ObjectID = "a1T-ui-4Nw"; */ -"a1T-ui-4Nw.text" = "Åpne lenker i tilknyttede apper"; - -/* Class = "UILabel"; text = "Debug Menu"; ObjectID = "A9G-5I-RSn"; */ -"A9G-5I-RSn.text" = "Feilsøkingsmeny"; - -/* Class = "UILabel"; text = "Logins"; ObjectID = "And-cQ-SEu"; */ -"And-cQ-SEu.text" = "Pålogginger"; - -/* Class = "UILabel"; text = "Animation"; ObjectID = "AtR-nS-Gun"; */ -"AtR-nS-Gun.text" = "Animasjon"; - -/* Class = "UILabel"; text = "Email Protection"; ObjectID = "azf-Nc-kvW"; */ -"azf-Nc-kvW.text" = "E-postbeskyttelse"; - /* Class = "UITableViewSection"; footerTitle = "Data and/or tabs will be cleared upon restart of the app."; ObjectID = "BGs-JL-4ib"; */ "BGs-JL-4ib.footerTitle" = "Data og/eller faner slettes/lukkes ved omstart av appen."; @@ -64,114 +31,42 @@ /* Class = "UINavigationItem"; title = "Manage Cookie Pop-ups"; ObjectID = "btj-ri-kRr"; */ "btj-ri-kRr.title" = "Administrer vinduer om informasjonskapsler"; -/* Class = "UILabel"; text = "App Icon"; ObjectID = "cKo-er-HNj"; */ -"cKo-er-HNj.text" = "Appikon"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "CR5-Al-WIW"; */ -"CR5-Al-WIW.text" = "Etikett"; - /* Class = "UILabel"; text = "Global Privacy Control (GPC)"; ObjectID = "cW7-OM-2oW"; */ "cW7-OM-2oW.text" = "Global Privacy Control (GPC)"; /* Class = "UIBarButtonItem"; title = "Add"; ObjectID = "CxT-QK-iVn"; */ "CxT-QK-iVn.title" = "Legg til"; -/* Class = "UILabel"; text = "Hide your location and conceal your online activity"; ObjectID = "Cyw-ir-cSK"; */ -"Cyw-ir-cSK.text" = "Hide your location and conceal your online activity"; - /* Class = "UILabel"; text = "App Exit, Inactive for 15 Minutes"; ObjectID = "D0R-jp-UY8"; */ "D0R-jp-UY8.text" = "når appen lukkes eller har vært inaktiv i 15 minutter"; -/* Class = "UILabel"; text = "10.0.1 (Build 10005)"; ObjectID = "d5n-vG-8kF"; */ -"d5n-vG-8kF.text" = "10.0.1 (Build 10005)"; - -/* Class = "UILabel"; text = "Application Lock"; ObjectID = "dBZ-yq-FYj"; */ -"dBZ-yq-FYj.text" = "Applås"; - -/* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "dj9-vh-Rig"; */ -"dj9-vh-Rig.headerTitle" = "Utseende"; - -/* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; - -/* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.headerTitle" = "Personvern"; - /* Class = "UILabel"; text = "Label"; ObjectID = "dud-qo-Ces"; */ "dud-qo-Ces.text" = "Etikett"; -/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "Dyd-bm-goj"; */ -"Dyd-bm-goj.title" = "Innstillinger"; - -/* Class = "UILabel"; text = "100%"; ObjectID = "EB8-09-gt2"; */ -"EB8-09-gt2.text" = "100 %"; - /* Class = "UILabel"; text = "Label"; ObjectID = "EIq-Ev-nfj"; */ "EIq-Ev-nfj.text" = "Etikett"; -/* Class = "UILabel"; text = "Automatically Clear Data"; ObjectID = "ekF-SJ-PAQ"; */ -"ekF-SJ-PAQ.text" = "Slett data automatisk"; - /* Class = "UILabel"; text = "App Exit, Inactive for 5 Minutes"; ObjectID = "ElX-yE-4PX"; */ "ElX-yE-4PX.text" = "når appen lukkes eller har vært inaktiv i 5 minutter"; /* Class = "UINavigationItem"; title = "App Icon"; ObjectID = "eMH-uv-Kms"; */ "eMH-uv-Kms.title" = "Appikon"; -/* Class = "UILabel"; text = "Theme"; ObjectID = "f1O-6u-LFY"; */ -"f1O-6u-LFY.text" = "Utseende"; - /* Class = "UILabel"; text = "App Exit Only"; ObjectID = "Fal-1Y-o2S"; */ "Fal-1Y-o2S.text" = "kun når appen lukkes"; /* Class = "UITableViewController"; title = "Automatically Clear Data"; ObjectID = "fdJ-b1-Des"; */ "fdJ-b1-Des.title" = "Slett data automatisk"; -/* Class = "UILabel"; text = "Unprotected Sites"; ObjectID = "FHC-1z-Z3v"; */ -"FHC-1z-Z3v.text" = "Ubeskyttede nettsteder"; - -/* Class = "UITableViewSection"; headerTitle = "About"; ObjectID = "FpT-1C-xtx"; */ -"FpT-1C-xtx.headerTitle" = "Om"; - /* Class = "UITableViewController"; title = "Global Privacy Control (GPC)"; ObjectID = "fV3-86-QQj"; */ "fV3-86-QQj.title" = "Global Privacy Control (GPC)"; -/* Class = "UILabel"; text = "Add Widget to Home Screen"; ObjectID = "Fxu-zn-51Z"; */ -"Fxu-zn-51Z.text" = "Legg til widgeten på startskjermen"; - -/* Class = "UILabel"; text = "Fire Button Animation"; ObjectID = "gBo-Cu-e2k"; Note = "Fire button animation settings item"; */ -"gBo-Cu-e2k.text" = "Animasjon for brannknappen"; - -/* Class = "UILabel"; text = "Default"; ObjectID = "Gbx-kl-uOO"; */ -"Gbx-kl-uOO.text" = "Standard"; - -/* Class = "UILabel"; text = "Manage Cookie Pop-ups"; ObjectID = "GRv-M2-Kx1"; */ -"GRv-M2-Kx1.text" = "Administrer vinduer om informasjonskapsler"; - -/* Class = "UINavigationItem"; title = "Theme"; ObjectID = "gS2-mg-l7R"; */ -"gS2-mg-l7R.title" = "Utseende"; - -/* Class = "UILabel"; text = "Long-Press Previews"; ObjectID = "HLr-R8-xxF"; */ -"HLr-R8-xxF.text" = "Forhåndsvisning ved å trykke og holde"; - -/* Class = "UILabel"; text = "Browse privately with our app for Windows"; ObjectID = "hoT-Nu-KXP"; */ -"hoT-Nu-KXP.text" = "Surf privat med vår app for Windows"; - /* Class = "UILabel"; text = "Privacy Protection enabled for all sites"; ObjectID = "Hu1-5i-vjL"; */ "Hu1-5i-vjL.text" = "Personvernbeskyttelse aktivert for alle nettsteder"; /* Class = "UICollectionViewController"; title = "Icon"; ObjectID = "jbD-Oy-Cmw"; */ "jbD-Oy-Cmw.title" = "Ikon"; -/* Class = "UILabel"; text = "Share Feedback"; ObjectID = "m23-t6-9cb"; */ -"m23-t6-9cb.text" = "Del tilbakemelding"; - -/* Class = "UINavigationItem"; title = "Address Bar Position"; ObjectID = "mLn-1x-Fl5"; Note = "Fire button animation setting page title"; */ -"mLn-1x-Fl5.title" = "Plassering av adressefelt"; - -/* Class = "UILabel"; text = "Address Bar Position"; ObjectID = "mqV-pf-NZ1"; Note = "Fire button animation settings item"; */ -"mqV-pf-NZ1.text" = "Plassering av adressefelt"; - /* Class = "UILabel"; text = "Let DuckDuckGo manage cookie consent pop-ups"; ObjectID = "nX1-F1-Frd"; */ "nX1-F1-Frd.text" = "La DuckDuckGo administrere popup-vinduer om samtykke til informasjonskapsler"; @@ -181,36 +76,9 @@ /* Class = "UINavigationItem"; title = "Unprotected Sites"; ObjectID = "OHV-qC-tL9"; */ "OHV-qC-tL9.title" = "Ubeskyttede nettsteder"; -/* Class = "UILabel"; text = "About DuckDuckGo"; ObjectID = "oM7-1o-9oY"; */ -"oM7-1o-9oY.text" = "Om DuckDuckGo"; - -/* Class = "UILabel"; text = "Top"; ObjectID = "opn-JO-idF"; */ -"opn-JO-idF.text" = "Topp"; - -/* Class = "UITableViewSection"; headerTitle = "More From DuckDuckGo"; ObjectID = "OxE-MQ-uJk"; */ -"OxE-MQ-uJk.headerTitle" = "Mer fra DuckDuckGo"; - -/* Class = "UILabel"; text = "Sync & Back Up"; ObjectID = "oXN-ez-gct"; */ -"oXN-ez-gct.text" = "Synkronisering og sikkerhetskopiering"; - -/* Class = "UILabel"; text = "Browse privately with our app for Mac "; ObjectID = "P0F-ts-ekd"; */ -"P0F-ts-ekd.text" = "Surf privat med vår app for Mac "; - -/* Class = "UILabel"; text = "Network Protection"; ObjectID = "qah-gb-udB"; */ -"qah-gb-udB.text" = "Network Protection"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "qeN-SV-zy7"; */ -"qeN-SV-zy7.text" = "Etikett"; - -/* Class = "UILabel"; text = "DuckDuckGo Windows App"; ObjectID = "RQ8-H1-Ez1"; */ -"RQ8-H1-Ez1.text" = "DuckDuckGo Windows-app"; - /* Class = "UINavigationItem"; title = "Text Size"; ObjectID = "ssa-zd-L3T"; */ "ssa-zd-L3T.title" = "Tekststørrelse"; -/* Class = "UILabel"; text = "Private Voice Search"; ObjectID = "Swa-O7-n8W"; */ -"Swa-O7-n8W.text" = "Privat talesøk"; - /* Class = "UITableViewSection"; headerTitle = "Show keyboard on"; ObjectID = "tGh-di-rfq"; */ "tGh-di-rfq.headerTitle" = "Vis tastatur i"; @@ -220,42 +88,18 @@ /* Class = "UITableViewSection"; headerTitle = "Action"; ObjectID = "U2M-6p-6nl"; */ "U2M-6p-6nl.headerTitle" = "Handling"; -/* Class = "UILabel"; text = "Autocomplete Suggestions"; ObjectID = "U8i-cQ-5WW"; */ -"U8i-cQ-5WW.text" = "Vis forslag fra autofullføring"; - -/* Class = "UINavigationItem"; title = "Fire Button Animation"; ObjectID = "uns-8w-IwL"; Note = "Fire button animation setting page title"; */ -"uns-8w-IwL.title" = "Animasjon for brannknappen"; - /* Class = "UILabel"; text = "Remove All Fireproof Sites"; ObjectID = "UZx-52-aer"; */ "UZx-52-aer.text" = "Fjern alle brannsikre nettsteder"; -/* Class = "UILabel"; text = "Global Privacy Control (GPC)"; ObjectID = "vPz-uO-6gB"; */ -"vPz-uO-6gB.text" = "Global Privacy Control (GPC)"; - /* Class = "UITableViewSection"; footerTitle = "App exit is defined by swiping the app to close it while inactivity is when the app is in the background."; ObjectID = "vSJ-YJ-bBs"; Note = "Timing setting for automatic data clearing function"; */ "vSJ-YJ-bBs.footerTitle" = "Apper lukkes når de avsluttes ved sveiping, og regnes som inaktive når de ligger i bakgrunnen."; /* Class = "UITableViewSection"; headerTitle = "Desired timing"; ObjectID = "vSJ-YJ-bBs"; Note = "Timing setting for automatic data clearing function"; */ "vSJ-YJ-bBs.headerTitle" = "Utføres"; -/* Class = "UILabel"; text = "Set as Default Browser"; ObjectID = "xof-5k-PkI"; */ -"xof-5k-PkI.text" = "Gjør til standardnettleser"; - /* Class = "UINavigationItem"; title = "Fireproof Sites"; ObjectID = "xUX-nF-HOl"; */ "xUX-nF-HOl.title" = "Brannsikre nettsteder"; -/* Class = "UILabel"; text = "Block email trackers and hide your address"; ObjectID = "Y6Y-wA-n6Z"; */ -"Y6Y-wA-n6Z.text" = "Blokker e-postsporere og skjul adressen din"; - -/* Class = "UILabel"; text = "Keyboard"; ObjectID = "yoZ-jw-Cu3"; */ -"yoZ-jw-Cu3.text" = "Tastatur"; - -/* Class = "UILabel"; text = "Add App to Your Dock"; ObjectID = "yvj-LL-MiR"; */ -"yvj-LL-MiR.text" = "Legg til appen i docken"; - -/* Class = "UILabel"; text = "DuckDuckGo Mac App"; ObjectID = "Yz9-17-qnn"; */ -"Yz9-17-qnn.text" = "DuckDuckGo Mac-app"; - /* Class = "UILabel"; text = "New Tab"; ObjectID = "Zpg-h0-rYv"; */ "Zpg-h0-rYv.text" = "Ny fane"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 16a4df7468..1283674abd 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Bewerken"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet kan best eng zijn.\n\nMaak je geen zorgen! Privé zoeken en browsen is eenvoudiger dan je denkt."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Uitgeschakeld"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privacybeleid"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Ik heb een uitnodigingscode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Aan de slag"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Je staat op de lijst!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Open je uitnodiging"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Je staat op de lijst!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Schrijf je in voor de privéwachtlijst"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-meldingen"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Herstel een VPN-verbinding automatisch na onderbreking."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Altijd aan"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-meldingen"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Netwerkbeveiliging voorkomt DNS-lekken naar je internetprovider door DNS-aanvragen via de VPN-tunnel naar onze eigen resolver te routeren."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Veilige DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-instellingen"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "App-vergrendeling"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Gegevens automatisch wissen"; +"settings.autolock.description" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Gegevens automatisch wissen"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 9c59af675f..657547f480 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Edytuj"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet może być trochę nieprzyjemny.\n\nBez obaw! Prywatne wyszukiwanie i przeglądanie jest łatwiejsze niż myślisz."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Wyłączone"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Polityka prywatności"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mam kod zaproszenia"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Rozpocznij"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Jesteś na liście!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otwórz zaproszenie"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Jesteś na liście!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Dołącz do prywatnej listy oczekujących"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alerty VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatycznie przywracaj połączenie VPN po jego przerwaniu."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Zawsze włączone"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Powiadomienia VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ochrona sieci zapobiega wyciekom DNS do dostawcy usług internetowych poprzez kierowanie zapytań DNS przez tunel VPN do naszego mechanizmu rozpoznawania nazw."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Bezpieczny DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Ustawienia VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Blokada aplikacji"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automatyczne czyszczenie danych"; +"settings.autolock.description" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatyczne czyszczenie danych"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 07f1fe4c1a..484df25045 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Editar"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "A Internet pode ser um pouco assustadora.\n\nMas não se preocupe! Pesquisar e navegar em privado é mais fácil do que pensa."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Desativado"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Política de Privacidade"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Tenho um Código de Convite"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Comece"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Está na lista!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Abra o seu convite"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Está na lista!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Adira à Lista de Espera Privada"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alertas de VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaurar automaticamente uma ligação VPN após a interrupção."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Sempre ligada"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notificações da VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "A Network Protection impede fugas de DNS para o teu fornecedor de serviços de internet ao encaminhar as consultas de DNS através do túnel da VPN para o nosso resolvedor."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS seguro"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Definições da VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Bloqueio de aplicações"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Limpar os dados automaticamente"; +"settings.autolock.description" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Limpar os dados automaticamente"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 3a135ca1bf..586833df57 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Editați"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internetul poate fi cam terifiant.\n\nNu te îngrijora! Căutarea și navigarea în mod confidențial sunt mai ușoare decât crezi."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Dezactivat"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Politica de confidențialitate"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Am un cod de invitație"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Să începem"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Ești pe listă!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Deschide invitația ta"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Ești pe listă!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Alătură-te listei de așteptare private"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alerte VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaurează automat o conexiune VPN după întrerupere."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Întotdeauna activat"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notificări VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection previne scurgerile DNS către furnizorul tău de servicii de internet prin direcționarea interogărilor DNS prin tunelul VPN către propriul nostru resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS securizat"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Setări VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Blocarea aplicației"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Șterge automat datele"; +"settings.autolock.description" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Șterge automat datele"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 6cc266c4ef..eefdff8811 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Редактировать"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Интернет набит трекерами.\n\nНо выход есть! Пользоваться сетью без слежки проще, чем вы думали."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Отключено"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Политика конфиденциальности"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "У меня есть пригласительный код"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Приступим!"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Вы в списке!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Откройте приглашение"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Вы в списке!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Приглашаем встать в очередь"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Оповещения о VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Автоматически восстанавливать VPN-соединение в случае его прерывания."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Всегда включено"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Уведомления о VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Защита сети предотвращает утечку данных через DNS вашему интернет-провайдеру, направляя DNS-запросы через VPN-туннель к нашему собственному сопоставителю."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Безопасный DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Настройки VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Блокировка"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Автоудаление данных"; +"settings.autolock.description" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоудаление данных"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 6fdccf112b..710f2607f3 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Upraviť"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet môže byť dosť nevyspytateľný.\n\nNemajte žiadne obavy! Súkromné vyhľadávanie a prehliadanie je jednoduchšie, ako si myslíte."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Zakázané"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Zásady ochrany súkromia"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mám pozývací kód"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Začnite"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Ste na zozname čakateľov!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otvorte svoju pozvánku"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Ste na zozname čakateľov!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pridajte sa do súkromného zoznamu čakateľov"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Upozornenia týkajúce sa VPN siete"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatické obnovenie pripojenia VPN po prerušení."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vždy zapnuté"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Oznámenia VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ochrana siete zabraňuje úniku informácií DNS k poskytovateľovi internetových služieb tým, že presmeruje DNS požiadavky cez VPN tunel na náš vlastný, tzv. „DNS resolver“."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Zabezpečený systém DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Nastavenia siete VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Zámok aplikácie"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automaticky vymazať údaje"; +"settings.autolock.description" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazať údaje"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 0c65a91575..48f5fb635e 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Uredi"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet je lahko grozljiv.\n\nNe skrbi! Iskanje in brskanje na zaseben način je lažje, kot si misliš."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Onemogočeno"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Pravilnik o zasebnosti"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Imam kodo povabila"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Začni"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Na seznamu ste!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Odprite svoje povabilo"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Na seznamu ste!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pridruži se zasebnemu čakalnemu seznamu"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Opozorila VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Samodejno obnovite povezavo VPN po prekinitvi."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vedno vklopljeno"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Obvestila VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Zaščita omrežja preprečuje uhajanje podatkov DNS k ponudniku internetnih storitev, saj poizvedbe DNS usmerja prek tunela VPN k lastnemu razreševalniku."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Varen DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Nastavitve VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Zaklepanje aplikacije"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Samodejno počisti podatke"; +"settings.autolock.description" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Samodejno počisti podatke"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 089e6b318b..e397a80ab6 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Redigera"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet kan vara lite läskigt.\n\nOroa dig inte! Att söka och surfa privat är lättare än du tror."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Inaktiverad"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Integritetspolicy"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Jag har en inbjudningskod"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Kom igång"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du står på listan!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Öppna din inbjudan"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du står på listan!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Ställ dig i den privata väntelistan"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-varningar"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Återställ automatiskt en VPN-anslutning efter avbrott."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Alltid på"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-aviseringar"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection förhindrar DNS-läckor till din internetleverantör genom att dirigera DNS-frågor genom VPN-tunneln till vår egen resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Säker DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-inställningar"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "App-lås"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Rensa data automatiskt"; +"settings.autolock.description" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Rensa data automatiskt"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 07cadb7e71..05cba409c8 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Düzenle"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "İnternet bazen ürkütücü olabilir.\n\nEndişelenmeyin! İnternette kimsenin göremeyeceği şekilde arama yapmak ve gezinmek sandığınızdan çok daha kolay."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Devre Dışı"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Gizlilik Politikası"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Davet Kodum var"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Başlayın"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Listedesiniz!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Davetiyenizi açın"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Listedesiniz!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Özel Bekleme Listesine Katılın"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN Uyarıları"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Kesintiden sonra VPN bağlantısını otomatik olarak geri yükleyin."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Her Zaman Açık"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN Bildirimleri"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ağ Koruması, DNS sorgularını VPN tüneli üzerinden kendi çözümleyicimize yönlendirerek İnternet Servis Sağlayıcınıza DNS sızıntılarını önler."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Güvenli DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN Ayarları"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Uygulama Kilidi"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Verileri Otomatik Olarak Temizle"; +"settings.autolock.description" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Verileri Otomatik Olarak Temizle"; From 45bb8211d7e1a98e4569237933bd9207795161d3 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:24:05 +0100 Subject: [PATCH 77/99] Updated missing string --- DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Settings.strings | 2 +- DuckDuckGo/cs.lproj/Settings.strings | 2 +- DuckDuckGo/da.lproj/Settings.strings | 2 +- DuckDuckGo/de.lproj/Settings.strings | 2 +- DuckDuckGo/el.lproj/Settings.strings | 2 +- DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Settings.strings | 2 +- DuckDuckGo/et.lproj/Settings.strings | 2 +- DuckDuckGo/fi.lproj/Settings.strings | 2 +- DuckDuckGo/fr.lproj/Settings.strings | 2 +- DuckDuckGo/hr.lproj/Settings.strings | 2 +- DuckDuckGo/hu.lproj/Settings.strings | 2 +- DuckDuckGo/it.lproj/Settings.strings | 2 +- DuckDuckGo/lt.lproj/Settings.strings | 2 +- DuckDuckGo/lv.lproj/Settings.strings | 2 +- DuckDuckGo/nb.lproj/Settings.strings | 2 +- DuckDuckGo/nl.lproj/Settings.strings | 2 +- DuckDuckGo/pl.lproj/Settings.strings | 2 +- DuckDuckGo/pt.lproj/Settings.strings | 2 +- DuckDuckGo/ro.lproj/Settings.strings | 2 +- DuckDuckGo/ru.lproj/Settings.strings | 2 +- DuckDuckGo/sk.lproj/Settings.strings | 2 +- DuckDuckGo/sl.lproj/Settings.strings | 2 +- DuckDuckGo/sv.lproj/Settings.strings | 2 +- DuckDuckGo/tr.lproj/Settings.strings | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 990dc359fa..99e8cdcaa9 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -942,7 +942,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") - public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "Automatically Clear Data", comment: "Section footer Autolock description") + public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening", comment: "Section footer Autolock description") public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/bg.lproj/Settings.strings b/DuckDuckGo/bg.lproj/Settings.strings index 6887796705..0ccc530fbb 100644 --- a/DuckDuckGo/bg.lproj/Settings.strings +++ b/DuckDuckGo/bg.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Външен Вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; +"settings.autolock.description" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Поверителност"; diff --git a/DuckDuckGo/cs.lproj/Settings.strings b/DuckDuckGo/cs.lproj/Settings.strings index 965b4ff82e..f6b22fdad8 100644 --- a/DuckDuckGo/cs.lproj/Settings.strings +++ b/DuckDuckGo/cs.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; +"settings.autolock.description" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Soukromí"; diff --git a/DuckDuckGo/da.lproj/Settings.strings b/DuckDuckGo/da.lproj/Settings.strings index 631f1f1c56..61b25ae4cc 100644 --- a/DuckDuckGo/da.lproj/Settings.strings +++ b/DuckDuckGo/da.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "udseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; +"settings.autolock.description" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatliv"; diff --git a/DuckDuckGo/de.lproj/Settings.strings b/DuckDuckGo/de.lproj/Settings.strings index 49d0dd41f7..5c4b85e2ea 100644 --- a/DuckDuckGo/de.lproj/Settings.strings +++ b/DuckDuckGo/de.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aussehen"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; +"settings.autolock.description" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatsphäre"; diff --git a/DuckDuckGo/el.lproj/Settings.strings b/DuckDuckGo/el.lproj/Settings.strings index 38a6b1ac4b..ef51181cdf 100644 --- a/DuckDuckGo/el.lproj/Settings.strings +++ b/DuckDuckGo/el.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Εμφάνιση"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; +"settings.autolock.description" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Ιδιωτικότητα"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index c6a2c88e3f..60ba10bdbd 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1766,7 +1766,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.autolock" = "Application Lock"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatically Clear Data"; +"settings.autolock.description" = "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatically Clear Data"; diff --git a/DuckDuckGo/es.lproj/Settings.strings b/DuckDuckGo/es.lproj/Settings.strings index 90ce5a3ace..f84076cd7e 100644 --- a/DuckDuckGo/es.lproj/Settings.strings +++ b/DuckDuckGo/es.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apariencia"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; +"settings.autolock.description" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidad"; diff --git a/DuckDuckGo/et.lproj/Settings.strings b/DuckDuckGo/et.lproj/Settings.strings index 14d97bb0ae..250e2c0178 100644 --- a/DuckDuckGo/et.lproj/Settings.strings +++ b/DuckDuckGo/et.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Välimus"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; +"settings.autolock.description" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privaatsus"; diff --git a/DuckDuckGo/fi.lproj/Settings.strings b/DuckDuckGo/fi.lproj/Settings.strings index f684826ce3..b04b23d8fb 100644 --- a/DuckDuckGo/fi.lproj/Settings.strings +++ b/DuckDuckGo/fi.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Ulkoasu"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; +"settings.autolock.description" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Tietosuoja"; diff --git a/DuckDuckGo/fr.lproj/Settings.strings b/DuckDuckGo/fr.lproj/Settings.strings index 2fc2c6d4d7..8c5ee103de 100644 --- a/DuckDuckGo/fr.lproj/Settings.strings +++ b/DuckDuckGo/fr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apparence"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; +"settings.autolock.description" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidentialité"; diff --git a/DuckDuckGo/hr.lproj/Settings.strings b/DuckDuckGo/hr.lproj/Settings.strings index 5b4efa5072..5d7e7c3037 100644 --- a/DuckDuckGo/hr.lproj/Settings.strings +++ b/DuckDuckGo/hr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; +"settings.autolock.description" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zaštita privatnosti"; diff --git a/DuckDuckGo/hu.lproj/Settings.strings b/DuckDuckGo/hu.lproj/Settings.strings index b6f3f5cc23..3a8fa933e3 100644 --- a/DuckDuckGo/hu.lproj/Settings.strings +++ b/DuckDuckGo/hu.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Megjelenés"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; +"settings.autolock.description" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Adatvédelem"; diff --git a/DuckDuckGo/it.lproj/Settings.strings b/DuckDuckGo/it.lproj/Settings.strings index ca6f845e18..df08a1e935 100644 --- a/DuckDuckGo/it.lproj/Settings.strings +++ b/DuckDuckGo/it.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspetto"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; +"settings.autolock.description" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/lt.lproj/Settings.strings b/DuckDuckGo/lt.lproj/Settings.strings index 4f67084ae3..f62e73317c 100644 --- a/DuckDuckGo/lt.lproj/Settings.strings +++ b/DuckDuckGo/lt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Išvaizda"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; +"settings.autolock.description" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatumas"; diff --git a/DuckDuckGo/lv.lproj/Settings.strings b/DuckDuckGo/lv.lproj/Settings.strings index 23f21ecfb1..2d6e25a5f8 100644 --- a/DuckDuckGo/lv.lproj/Settings.strings +++ b/DuckDuckGo/lv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Parādas"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; +"settings.autolock.description" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privātums"; diff --git a/DuckDuckGo/nb.lproj/Settings.strings b/DuckDuckGo/nb.lproj/Settings.strings index 8c3750d5fb..805271b7ab 100644 --- a/DuckDuckGo/nb.lproj/Settings.strings +++ b/DuckDuckGo/nb.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; +"settings.autolock.description" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Personvern"; diff --git a/DuckDuckGo/nl.lproj/Settings.strings b/DuckDuckGo/nl.lproj/Settings.strings index 99c32eca3e..6f30f20ce9 100644 --- a/DuckDuckGo/nl.lproj/Settings.strings +++ b/DuckDuckGo/nl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Uiterlijk"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; +"settings.autolock.description" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/pl.lproj/Settings.strings b/DuckDuckGo/pl.lproj/Settings.strings index 4afbe423ff..0dcec42f97 100644 --- a/DuckDuckGo/pl.lproj/Settings.strings +++ b/DuckDuckGo/pl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Wygląd"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; +"settings.autolock.description" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Prywatność"; diff --git a/DuckDuckGo/pt.lproj/Settings.strings b/DuckDuckGo/pt.lproj/Settings.strings index f136fee8f4..5486c8ac1f 100644 --- a/DuckDuckGo/pt.lproj/Settings.strings +++ b/DuckDuckGo/pt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aparência"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; +"settings.autolock.description" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidade"; diff --git a/DuckDuckGo/ro.lproj/Settings.strings b/DuckDuckGo/ro.lproj/Settings.strings index 4404ec5c0d..63a21baa33 100644 --- a/DuckDuckGo/ro.lproj/Settings.strings +++ b/DuckDuckGo/ro.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspect"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; +"settings.autolock.description" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidențialitate"; diff --git a/DuckDuckGo/ru.lproj/Settings.strings b/DuckDuckGo/ru.lproj/Settings.strings index 6b54a2d6c0..dd14621132 100644 --- a/DuckDuckGo/ru.lproj/Settings.strings +++ b/DuckDuckGo/ru.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Внешний вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; +"settings.autolock.description" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Настройки конфиденциальности"; diff --git a/DuckDuckGo/sk.lproj/Settings.strings b/DuckDuckGo/sk.lproj/Settings.strings index 5aba47196d..4fa93be816 100644 --- a/DuckDuckGo/sk.lproj/Settings.strings +++ b/DuckDuckGo/sk.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhľad"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; +"settings.autolock.description" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Súkromie"; diff --git a/DuckDuckGo/sl.lproj/Settings.strings b/DuckDuckGo/sl.lproj/Settings.strings index ec4d2b7bf2..4cf888ab95 100644 --- a/DuckDuckGo/sl.lproj/Settings.strings +++ b/DuckDuckGo/sl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; +"settings.autolock.description" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zasebnost"; diff --git a/DuckDuckGo/sv.lproj/Settings.strings b/DuckDuckGo/sv.lproj/Settings.strings index 5a1b5101ff..d31cada261 100644 --- a/DuckDuckGo/sv.lproj/Settings.strings +++ b/DuckDuckGo/sv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; +"settings.autolock.description" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Sekretess"; diff --git a/DuckDuckGo/tr.lproj/Settings.strings b/DuckDuckGo/tr.lproj/Settings.strings index 81c5b0fc5d..0d7a481c9c 100644 --- a/DuckDuckGo/tr.lproj/Settings.strings +++ b/DuckDuckGo/tr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Görünüm"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; +"settings.autolock.description" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Gizlilik"; From 620920e58c89db75121b4ee424103cc713a2f24e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:30:47 +0100 Subject: [PATCH 78/99] Revert Localizable change --- DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Localizable.strings | 2 +- DuckDuckGo/bg.lproj/Settings.strings | 2 +- DuckDuckGo/cs.lproj/Localizable.strings | 2 +- DuckDuckGo/cs.lproj/Settings.strings | 2 +- DuckDuckGo/da.lproj/Localizable.strings | 2 +- DuckDuckGo/da.lproj/Settings.strings | 2 +- DuckDuckGo/de.lproj/Localizable.strings | 2 +- DuckDuckGo/de.lproj/Settings.strings | 2 +- DuckDuckGo/el.lproj/Localizable.strings | 2 +- DuckDuckGo/el.lproj/Settings.strings | 2 +- DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Settings.strings | 2 +- DuckDuckGo/et.lproj/Localizable.strings | 2 +- DuckDuckGo/et.lproj/Settings.strings | 2 +- DuckDuckGo/fi.lproj/Localizable.strings | 2 +- DuckDuckGo/fi.lproj/Settings.strings | 2 +- DuckDuckGo/fr.lproj/Localizable.strings | 2 +- DuckDuckGo/fr.lproj/Settings.strings | 2 +- DuckDuckGo/hr.lproj/Localizable.strings | 2 +- DuckDuckGo/hr.lproj/Settings.strings | 2 +- DuckDuckGo/hu.lproj/Localizable.strings | 2 +- DuckDuckGo/hu.lproj/Settings.strings | 2 +- DuckDuckGo/it.lproj/Localizable.strings | 2 +- DuckDuckGo/it.lproj/Settings.strings | 2 +- DuckDuckGo/lt.lproj/Localizable.strings | 2 +- DuckDuckGo/lt.lproj/Settings.strings | 2 +- DuckDuckGo/lv.lproj/Localizable.strings | 2 +- DuckDuckGo/lv.lproj/Settings.strings | 2 +- DuckDuckGo/nb.lproj/Localizable.strings | 2 +- DuckDuckGo/nb.lproj/Settings.strings | 2 +- DuckDuckGo/nl.lproj/Localizable.strings | 2 +- DuckDuckGo/nl.lproj/Settings.strings | 2 +- DuckDuckGo/pl.lproj/Localizable.strings | 2 +- DuckDuckGo/pl.lproj/Settings.strings | 2 +- DuckDuckGo/pt.lproj/Localizable.strings | 2 +- DuckDuckGo/pt.lproj/Settings.strings | 2 +- DuckDuckGo/ro.lproj/Localizable.strings | 2 +- DuckDuckGo/ro.lproj/Settings.strings | 2 +- DuckDuckGo/ru.lproj/Localizable.strings | 2 +- DuckDuckGo/ru.lproj/Settings.strings | 2 +- DuckDuckGo/sk.lproj/Localizable.strings | 2 +- DuckDuckGo/sk.lproj/Settings.strings | 2 +- DuckDuckGo/sl.lproj/Localizable.strings | 2 +- DuckDuckGo/sl.lproj/Settings.strings | 2 +- DuckDuckGo/sv.lproj/Localizable.strings | 2 +- DuckDuckGo/sv.lproj/Settings.strings | 2 +- DuckDuckGo/tr.lproj/Localizable.strings | 2 +- DuckDuckGo/tr.lproj/Settings.strings | 2 +- 50 files changed, 50 insertions(+), 50 deletions(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 99e8cdcaa9..eda0e35309 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -942,7 +942,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") - public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening", comment: "Section footer Autolock description") + public static let settingsAutoLockDescription = NSLocalizedString("dOj-jn-mSN.footerTitle", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index cda2a56879..d1d2f54891 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Заключване на приложение"; /* Section footer Autolock description */ -"settings.autolock.description" = "Автоматично изчистване на данните"; +"dOj-jn-mSN.footerTitle" = "Автоматично изчистване на данните"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоматично изчистване на данните"; diff --git a/DuckDuckGo/bg.lproj/Settings.strings b/DuckDuckGo/bg.lproj/Settings.strings index 0ccc530fbb..6887796705 100644 --- a/DuckDuckGo/bg.lproj/Settings.strings +++ b/DuckDuckGo/bg.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Външен Вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; +"dOj-jn-mSN.footerTitle" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Поверителност"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 93a782a622..0647af6194 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -1691,7 +1691,7 @@ "settings.autolock" = "Zámek aplikace"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automaticky vymazat data"; +"dOj-jn-mSN.footerTitle" = "Automaticky vymazat data"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazat data"; diff --git a/DuckDuckGo/cs.lproj/Settings.strings b/DuckDuckGo/cs.lproj/Settings.strings index f6b22fdad8..965b4ff82e 100644 --- a/DuckDuckGo/cs.lproj/Settings.strings +++ b/DuckDuckGo/cs.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; +"dOj-jn-mSN.footerTitle" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Soukromí"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index fa5de6c141..6a82f66bba 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Applikationslås"; /* Section footer Autolock description */ -"settings.autolock.description" = "Ryd data automatisk"; +"dOj-jn-mSN.footerTitle" = "Ryd data automatisk"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Ryd data automatisk"; diff --git a/DuckDuckGo/da.lproj/Settings.strings b/DuckDuckGo/da.lproj/Settings.strings index 61b25ae4cc..631f1f1c56 100644 --- a/DuckDuckGo/da.lproj/Settings.strings +++ b/DuckDuckGo/da.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "udseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; +"dOj-jn-mSN.footerTitle" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatliv"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 64b930603b..f76b1b9aba 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Anwendungssperre"; /* Section footer Autolock description */ -"settings.autolock.description" = "Daten automatisch löschen"; +"dOj-jn-mSN.footerTitle" = "Daten automatisch löschen"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Daten automatisch löschen"; diff --git a/DuckDuckGo/de.lproj/Settings.strings b/DuckDuckGo/de.lproj/Settings.strings index 5c4b85e2ea..49d0dd41f7 100644 --- a/DuckDuckGo/de.lproj/Settings.strings +++ b/DuckDuckGo/de.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aussehen"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; +"dOj-jn-mSN.footerTitle" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatsphäre"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 27a65200ca..087a1fd7f7 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Κλείδωμα εφαρμογής"; /* Section footer Autolock description */ -"settings.autolock.description" = "Αυτόματη απαλοιφή δεδομένων"; +"dOj-jn-mSN.footerTitle" = "Αυτόματη απαλοιφή δεδομένων"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Αυτόματη απαλοιφή δεδομένων"; diff --git a/DuckDuckGo/el.lproj/Settings.strings b/DuckDuckGo/el.lproj/Settings.strings index ef51181cdf..38a6b1ac4b 100644 --- a/DuckDuckGo/el.lproj/Settings.strings +++ b/DuckDuckGo/el.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Εμφάνιση"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; +"dOj-jn-mSN.footerTitle" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Ιδιωτικότητα"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 60ba10bdbd..d730430b74 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1766,7 +1766,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.autolock" = "Application Lock"; /* Section footer Autolock description */ -"settings.autolock.description" = "If Touch ID, Face ID or a system passcode is set, you'll be requested to unlock the app when opening"; +"dOj-jn-mSN.footerTitle" = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatically Clear Data"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index c974029da3..82b0fb12af 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Bloqueo de aplicación"; /* Section footer Autolock description */ -"settings.autolock.description" = "Borrar datos automáticamente"; +"dOj-jn-mSN.footerTitle" = "Borrar datos automáticamente"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Borrar datos automáticamente"; diff --git a/DuckDuckGo/es.lproj/Settings.strings b/DuckDuckGo/es.lproj/Settings.strings index f84076cd7e..90ce5a3ace 100644 --- a/DuckDuckGo/es.lproj/Settings.strings +++ b/DuckDuckGo/es.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apariencia"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; +"dOj-jn-mSN.footerTitle" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidad"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index d5feec758b..65f2831d02 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Rakenduse lukk"; /* Section footer Autolock description */ -"settings.autolock.description" = "Kustuta andmed automaatselt"; +"dOj-jn-mSN.footerTitle" = "Kustuta andmed automaatselt"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Kustuta andmed automaatselt"; diff --git a/DuckDuckGo/et.lproj/Settings.strings b/DuckDuckGo/et.lproj/Settings.strings index 250e2c0178..14d97bb0ae 100644 --- a/DuckDuckGo/et.lproj/Settings.strings +++ b/DuckDuckGo/et.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Välimus"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; +"dOj-jn-mSN.footerTitle" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privaatsus"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index a20894d098..aa6d072b26 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Sovelluksen lukitus"; /* Section footer Autolock description */ -"settings.autolock.description" = "Tyhjennä tiedot automaattisesti"; +"dOj-jn-mSN.footerTitle" = "Tyhjennä tiedot automaattisesti"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Tyhjennä tiedot automaattisesti"; diff --git a/DuckDuckGo/fi.lproj/Settings.strings b/DuckDuckGo/fi.lproj/Settings.strings index b04b23d8fb..f684826ce3 100644 --- a/DuckDuckGo/fi.lproj/Settings.strings +++ b/DuckDuckGo/fi.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Ulkoasu"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; +"dOj-jn-mSN.footerTitle" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Tietosuoja"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 7ce5b93429..1672a095e1 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Verrouillage de l'application"; /* Section footer Autolock description */ -"settings.autolock.description" = "Effacer automatiquement les données"; +"dOj-jn-mSN.footerTitle" = "Effacer automatiquement les données"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Effacer automatiquement les données"; diff --git a/DuckDuckGo/fr.lproj/Settings.strings b/DuckDuckGo/fr.lproj/Settings.strings index 8c5ee103de..2fc2c6d4d7 100644 --- a/DuckDuckGo/fr.lproj/Settings.strings +++ b/DuckDuckGo/fr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Apparence"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; +"dOj-jn-mSN.footerTitle" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidentialité"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index fd0584e941..120056b994 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Zaključavanje aplikacije"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatsko brisanje podataka"; +"dOj-jn-mSN.footerTitle" = "Automatsko brisanje podataka"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatsko brisanje podataka"; diff --git a/DuckDuckGo/hr.lproj/Settings.strings b/DuckDuckGo/hr.lproj/Settings.strings index 5d7e7c3037..5b4efa5072 100644 --- a/DuckDuckGo/hr.lproj/Settings.strings +++ b/DuckDuckGo/hr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; +"dOj-jn-mSN.footerTitle" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zaštita privatnosti"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 289a0808d9..b1d6ff0980 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Alkalmazás zárolás"; /* Section footer Autolock description */ -"settings.autolock.description" = "Adatok automatikus törlése"; +"dOj-jn-mSN.footerTitle" = "Adatok automatikus törlése"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Adatok automatikus törlése"; diff --git a/DuckDuckGo/hu.lproj/Settings.strings b/DuckDuckGo/hu.lproj/Settings.strings index 3a8fa933e3..b6f3f5cc23 100644 --- a/DuckDuckGo/hu.lproj/Settings.strings +++ b/DuckDuckGo/hu.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Megjelenés"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; +"dOj-jn-mSN.footerTitle" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Adatvédelem"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index 81eb7f6111..b8ef30c37b 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Blocco applicazione"; /* Section footer Autolock description */ -"settings.autolock.description" = "Cancellazione automatica dei dati"; +"dOj-jn-mSN.footerTitle" = "Cancellazione automatica dei dati"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Cancellazione automatica dei dati"; diff --git a/DuckDuckGo/it.lproj/Settings.strings b/DuckDuckGo/it.lproj/Settings.strings index df08a1e935..ca6f845e18 100644 --- a/DuckDuckGo/it.lproj/Settings.strings +++ b/DuckDuckGo/it.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspetto"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; +"dOj-jn-mSN.footerTitle" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 87425c6d70..3d2814b6a2 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Programos užraktas"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatiškai valyti duomenis"; +"dOj-jn-mSN.footerTitle" = "Automatiškai valyti duomenis"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatiškai valyti duomenis"; diff --git a/DuckDuckGo/lt.lproj/Settings.strings b/DuckDuckGo/lt.lproj/Settings.strings index f62e73317c..4f67084ae3 100644 --- a/DuckDuckGo/lt.lproj/Settings.strings +++ b/DuckDuckGo/lt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Išvaizda"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; +"dOj-jn-mSN.footerTitle" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privatumas"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 3a1c3c0137..52d30b5ce4 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Lietojumprogrammas bloķēšana"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automātiski notīrīt datus"; +"dOj-jn-mSN.footerTitle" = "Automātiski notīrīt datus"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automātiski notīrīt datus"; diff --git a/DuckDuckGo/lv.lproj/Settings.strings b/DuckDuckGo/lv.lproj/Settings.strings index 2d6e25a5f8..23f21ecfb1 100644 --- a/DuckDuckGo/lv.lproj/Settings.strings +++ b/DuckDuckGo/lv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Parādas"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; +"dOj-jn-mSN.footerTitle" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privātums"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 407e416b85..1a254fb913 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -1781,7 +1781,7 @@ "settings.autolock" = "Applås"; /* Section footer Autolock description */ -"settings.autolock.description" = "Slett data automatisk"; +"dOj-jn-mSN.footerTitle" = "Slett data automatisk"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Slett data automatisk"; diff --git a/DuckDuckGo/nb.lproj/Settings.strings b/DuckDuckGo/nb.lproj/Settings.strings index 805271b7ab..8c3750d5fb 100644 --- a/DuckDuckGo/nb.lproj/Settings.strings +++ b/DuckDuckGo/nb.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; +"dOj-jn-mSN.footerTitle" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Personvern"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 8bdb8046e0..16a4df7468 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "App-vergrendeling"; /* Section footer Autolock description */ -"settings.autolock.description" = "Gegevens automatisch wissen"; +"dOj-jn-mSN.footerTitle" = "Gegevens automatisch wissen"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Gegevens automatisch wissen"; diff --git a/DuckDuckGo/nl.lproj/Settings.strings b/DuckDuckGo/nl.lproj/Settings.strings index 6f30f20ce9..99c32eca3e 100644 --- a/DuckDuckGo/nl.lproj/Settings.strings +++ b/DuckDuckGo/nl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Uiterlijk"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; +"dOj-jn-mSN.footerTitle" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacy"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 4d771ab66d..9c59af675f 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Blokada aplikacji"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automatyczne czyszczenie danych"; +"dOj-jn-mSN.footerTitle" = "Automatyczne czyszczenie danych"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatyczne czyszczenie danych"; diff --git a/DuckDuckGo/pl.lproj/Settings.strings b/DuckDuckGo/pl.lproj/Settings.strings index 0dcec42f97..4afbe423ff 100644 --- a/DuckDuckGo/pl.lproj/Settings.strings +++ b/DuckDuckGo/pl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Wygląd"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; +"dOj-jn-mSN.footerTitle" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Prywatność"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index e25e0f3044..07f1fe4c1a 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Bloqueio de aplicações"; /* Section footer Autolock description */ -"settings.autolock.description" = "Limpar os dados automaticamente"; +"dOj-jn-mSN.footerTitle" = "Limpar os dados automaticamente"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Limpar os dados automaticamente"; diff --git a/DuckDuckGo/pt.lproj/Settings.strings b/DuckDuckGo/pt.lproj/Settings.strings index 5486c8ac1f..f136fee8f4 100644 --- a/DuckDuckGo/pt.lproj/Settings.strings +++ b/DuckDuckGo/pt.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aparência"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; +"dOj-jn-mSN.footerTitle" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Privacidade"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index bfb0bbf1df..3a135ca1bf 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Blocarea aplicației"; /* Section footer Autolock description */ -"settings.autolock.description" = "Șterge automat datele"; +"dOj-jn-mSN.footerTitle" = "Șterge automat datele"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Șterge automat datele"; diff --git a/DuckDuckGo/ro.lproj/Settings.strings b/DuckDuckGo/ro.lproj/Settings.strings index 63a21baa33..4404ec5c0d 100644 --- a/DuckDuckGo/ro.lproj/Settings.strings +++ b/DuckDuckGo/ro.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Aspect"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; +"dOj-jn-mSN.footerTitle" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Confidențialitate"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 43b718f1c2..6cc266c4ef 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Блокировка"; /* Section footer Autolock description */ -"settings.autolock.description" = "Автоудаление данных"; +"dOj-jn-mSN.footerTitle" = "Автоудаление данных"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоудаление данных"; diff --git a/DuckDuckGo/ru.lproj/Settings.strings b/DuckDuckGo/ru.lproj/Settings.strings index dd14621132..6b54a2d6c0 100644 --- a/DuckDuckGo/ru.lproj/Settings.strings +++ b/DuckDuckGo/ru.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Внешний вид"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; +"dOj-jn-mSN.footerTitle" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Настройки конфиденциальности"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 25e901ed93..6fdccf112b 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Zámok aplikácie"; /* Section footer Autolock description */ -"settings.autolock.description" = "Automaticky vymazať údaje"; +"dOj-jn-mSN.footerTitle" = "Automaticky vymazať údaje"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazať údaje"; diff --git a/DuckDuckGo/sk.lproj/Settings.strings b/DuckDuckGo/sk.lproj/Settings.strings index 4fa93be816..5aba47196d 100644 --- a/DuckDuckGo/sk.lproj/Settings.strings +++ b/DuckDuckGo/sk.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Vzhľad"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; +"dOj-jn-mSN.footerTitle" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Súkromie"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 8f3e9e7d01..0c65a91575 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Zaklepanje aplikacije"; /* Section footer Autolock description */ -"settings.autolock.description" = "Samodejno počisti podatke"; +"dOj-jn-mSN.footerTitle" = "Samodejno počisti podatke"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Samodejno počisti podatke"; diff --git a/DuckDuckGo/sl.lproj/Settings.strings b/DuckDuckGo/sl.lproj/Settings.strings index 4cf888ab95..ec4d2b7bf2 100644 --- a/DuckDuckGo/sl.lproj/Settings.strings +++ b/DuckDuckGo/sl.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Izgled"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; +"dOj-jn-mSN.footerTitle" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Zasebnost"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 603e62d7d0..089e6b318b 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "App-lås"; /* Section footer Autolock description */ -"settings.autolock.description" = "Rensa data automatiskt"; +"dOj-jn-mSN.footerTitle" = "Rensa data automatiskt"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Rensa data automatiskt"; diff --git a/DuckDuckGo/sv.lproj/Settings.strings b/DuckDuckGo/sv.lproj/Settings.strings index d31cada261..5a1b5101ff 100644 --- a/DuckDuckGo/sv.lproj/Settings.strings +++ b/DuckDuckGo/sv.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Utseende"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; +"dOj-jn-mSN.footerTitle" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Sekretess"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 8507885afb..07cadb7e71 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -1685,7 +1685,7 @@ "settings.autolock" = "Uygulama Kilidi"; /* Section footer Autolock description */ -"settings.autolock.description" = "Verileri Otomatik Olarak Temizle"; +"dOj-jn-mSN.footerTitle" = "Verileri Otomatik Olarak Temizle"; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Verileri Otomatik Olarak Temizle"; diff --git a/DuckDuckGo/tr.lproj/Settings.strings b/DuckDuckGo/tr.lproj/Settings.strings index 0d7a481c9c..81c5b0fc5d 100644 --- a/DuckDuckGo/tr.lproj/Settings.strings +++ b/DuckDuckGo/tr.lproj/Settings.strings @@ -92,7 +92,7 @@ "dj9-vh-Rig.headerTitle" = "Görünüm"; /* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"settings.autolock.description" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; +"dOj-jn-mSN.footerTitle" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; /* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ "dOj-jn-mSN.headerTitle" = "Gizlilik"; From 673d94a92b6d21f9ad46b4d76488a0e4d518ed29 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 17:58:55 +0100 Subject: [PATCH 79/99] Updated translations from Smartling --- DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/cs.lproj/Localizable.strings | 103 ++++++++++++++-- DuckDuckGo/da.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/de.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/el.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGo/es.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/et.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/fi.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/fr.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/hr.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/hu.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/it.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/lt.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/lv.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/nb.lproj/Localizable.strings | 5 +- DuckDuckGo/nb.lproj/Settings.strings | 156 ------------------------ DuckDuckGo/nl.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/pl.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/pt.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/ro.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/ru.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/sk.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/sl.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/sv.lproj/Localizable.strings | 129 +++++++++++++++++--- DuckDuckGo/tr.lproj/Localizable.strings | 129 +++++++++++++++++--- 27 files changed, 2540 insertions(+), 566 deletions(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index eda0e35309..fbcf8e7daa 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -942,7 +942,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsFireproofSites = NSLocalizedString("settings.fireproof.sites", value: "Fireproof Sites", comment: "Settings screen cell text for Fireproof Sites") public static let settingsClearData = NSLocalizedString("settings.clear.data", value: "Automatically Clear Data", comment: "Settings screen cell text for Automatically Clearing Data") public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") - public static let settingsAutoLockDescription = NSLocalizedString("dOj-jn-mSN.footerTitle", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") + public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index d1d2f54891..4a6a8c2cd0 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Редактиране"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Интернет може да бъде и доста опасно място.\n\nНе се притеснявайте! Поверителното търсене и сърфиране е много по-лесно, отколкото си мислите."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Деактивирано"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Политика за поверителност"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Имам код за покана"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Първи стъпки"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Включени сте в списъка с чакащи!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Отворете поканата"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Включени сте в списъка с чакащи!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Включване в списък с чакащи за личен адрес"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Известия за VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Автоматично възстановяване на VPN връзка след прекъсване."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Винаги включено"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Известия за VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Мрежовата защита предотвратява изтичането на DNS към Вашия доставчик на интернет услуги, като насочва DNS заявките през VPN тунела към нашия собствен резолвер."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Сигурен DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Настройки на VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Заключване на приложение"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Автоматично изчистване на данните"; +"settings.autolock.description" = "Ако сте задали пръстов отпечатък, лицево разпознаване или системна парола, ще бъдете приканени да отключите приложението при отваряне."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоматично изчистване на данните"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 0647af6194..b9645433ca 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Upravit"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet může být trochu strašidelný.\n\nNebojte se! Anonymní vyhledávání a procházení je jednodušší, než si myslíte."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Zakázáno"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Zásady ochrany osobních údajů"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mám zvací kód"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Začínáme"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Jsi v pořadníku!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otevřít pozvánku"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Jsi v pořadníku!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Připojte se k soukromému pořadníku"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1691,7 +1778,7 @@ "settings.autolock" = "Zámek aplikace"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automaticky vymazat data"; +"settings.autolock.description" = "Pokud je nastaveno Touch ID, Face ID nebo přístupový kód k systému, budete při otevírání požádáni o odemknutí aplikace."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazat data"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 6a82f66bba..9ceaca8ac2 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Rediger"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internettet kan være ret uhyggeligt.\n\nBare rolig! Det er lettere at søge og browse privat, end du tror."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Deaktiveret"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privatlivspolitik"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Jeg har en invitationskode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Kom igang"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du er på listen!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Åbn din invitation"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du er på listen!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Slut dig til den private venteliste"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-advarsler"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Gendan automatisk en VPN-forbindelse efter afbrydelse."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Altid aktiv"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-meddelelser"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection forhindrer DNS-lækager til din internetudbyder ved at dirigere DNS-forespørgsler gennem VPN-tunnelen til vores egen resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Sikker DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-indstillinger"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Applikationslås"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Ryd data automatisk"; +"settings.autolock.description" = "Hvis Touch ID, Face ID eller en systemadgangskode er indstillet, bliver du bedt om at låse appen op, når du åbner."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Ryd data automatisk"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index f76b1b9aba..816de684f4 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Bearbeiten"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Das Internet kann ein bisschen gruselig sein.\n\nKeine Sorge! Privat zu suchen und zu browsen ist einfacher als du denkst."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Deaktiviert"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Datenschutzrichtlinie"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Ich habe einen Einladungscode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Los geht's!"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du stehst auf der Liste!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Öffne deine Einladung"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du stehst auf der Liste!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Auf die private Warteliste setzen"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-Benachrichtigungen"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Stelle eine VPN-Verbindung nach einer Unterbrechung automatisch wieder her."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Immer aktiviert"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-Benachrichtigungen"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection verhindert DNS-Lecks zu deinem Internetanbieter, indem DNS-Anfragen über den VPN-Tunnel an unseren eigenen Resolver weitergeleitet werden."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Sicheres DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-Einstellungen"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Anwendungssperre"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Daten automatisch löschen"; +"settings.autolock.description" = "Wenn Touch ID, Face ID oder ein Systempasswort eingestellt sind, wirst du aufgefordert, die App beim Öffnen zu entsperren."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Daten automatisch löschen"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 087a1fd7f7..5500609257 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Επεξεργασία"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Το Διαδίκτυο μπορεί να είναι κάπως ανατριχιαστικό.\n\nΜην ανησυχείτε! Το να πραγματοποιείτε αναζήτηση και περιήγηση ιδιωτικά είναι πιο εύκολο απ' όσο νομίζετε."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Απενεργοποιήθηκε"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Πολιτική ιδιωτικού απορρήτου"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Διαθέτω Κωδικό πρόσκλησης"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Ξεκινήστε"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Βρίσκεστε στη λίστα!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Ανοίξτε την πρόσκλησή σας"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Βρίσκεστε στη λίστα!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Εγγραφείτε στην Ιδιωτική λίστα αναμονής"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Ειδοποιήσεις VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Αυτόματη αποκατάσταση μιας σύνδεσης VPN έπειτα από διακοπή."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Πάντα σε λειτουργία"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Ειδοποιήσεις VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Η Προστασία δικτύου αποτρέπει διαρροές DNS προς τον πάροχο υπηρεσιών διαδικτύου σας, δρομολογώντας ερωτήματα DNS μέσω της σήραγγας VPN στο δικό μας πρόγραμμα επίλυσης."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Ασφαλές DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Ρυθμίσεις VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Κλείδωμα εφαρμογής"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Αυτόματη απαλοιφή δεδομένων"; +"settings.autolock.description" = "Εάν έχει οριστεί Touch ID, Face ID ή κωδικός πρόσβασης συστήματος, θα σας ζητηθεί να ξεκλειδώσετε την εφαρμογή κατά το άνοιγμά της."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Αυτόματη απαλοιφή δεδομένων"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index d730430b74..c1c24c6b54 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1766,7 +1766,7 @@ But if you *do* want a peek under the hood, you can find more information about "settings.autolock" = "Application Lock"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; +"settings.autolock.description" = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatically Clear Data"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 82b0fb12af..aaae0c5bf1 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Editar"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet puede ser un lugar horrible.\n\nNo te preocupes. Buscar y navegar de forma privada es más fácil de lo que piensas."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Desactivado"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Política de privacidad"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Tengo un código de invitación"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Empezar"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "¡Estás en la lista!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Abrir tu invitación"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "¡Estás en la lista!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Unirse a la lista de espera privada"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alertas de VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaura automáticamente una conexión VPN después de una interrupción."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Siempre activado"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notificaciones de VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "La protección de red evita las filtraciones DNS a tu proveedor de servicios de internet redirigiendo las consultas de DNS a través del túnel VPN a nuestro propio resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS seguro"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Configuración de VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Bloqueo de aplicación"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Borrar datos automáticamente"; +"settings.autolock.description" = "Si se establece Touch ID, Face ID o una contraseña del sistema, deberás desbloquear la aplicación al abrirla."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Borrar datos automáticamente"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index 65f2831d02..e7b47e1c12 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Redigeeri"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet võib olla üsna jube.\n\nÄra muretse! Privaatselt otsimine ja sirvimine on lihtsam kui arvad."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Keelatud"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privaatsuspoliitika"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mul on kutse kood"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Alustage"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Oled ootenimekirjas!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Ava oma kutse"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Oled ootenimekirjas!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Liituge privaatse ootenimekirjaga"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-i hoiatused"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "VPN-ühendus taastatakse pärast katkestust automaatselt."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Alati sisse lülitatud"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-i teavitused"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection takistab DNS-i lekkeid sinu internetiteenuse pakkujale, suunates DNS-päringud läbi VPN-tunneli meie enda aadressiteisendusteenusesse."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Turvaline DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-i seaded"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Rakenduse lukk"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Kustuta andmed automaatselt"; +"settings.autolock.description" = "Kui on määratud Touch ID, Face ID või süsteemi pääsukood, palutakse avamisel rakendus avada."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Kustuta andmed automaatselt"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index aa6d072b26..672d8a244d 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Muokkaa"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet voi olla pelottava paikka.\n\nMutta ei hätää! Yksityinen haku ja selaaminen on helpompaa kuin luulet."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Pois käytöstä"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Tietosuojakäytäntö"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Minulla on kutsukoodi"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Aloita"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Olet odotuslistalla!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Avaa kutsusi"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Olet odotuslistalla!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Liity yksityiselle odotuslistalle"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-ilmoitukset"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Palauta VPN-yhteys automaattisesti keskeytyksen jälkeen."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Aina käytössä"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-ilmoitukset"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection estää DNS-vuodot internetpalveluntarjoajallesi ohjaamalla DNS-pyynnöt VPN-verkon kautta omalle välittäjällemme."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Suojattu DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-asetukset"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Sovelluksen lukitus"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Tyhjennä tiedot automaattisesti"; +"settings.autolock.description" = "Jos käytössä on Touch ID, Face ID tai järjestelmän salasana, sinua pyydetään poistamaan lukitus, kun avaat sovelluksen."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Tyhjennä tiedot automaattisesti"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 1672a095e1..0c20412874 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Modifier"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet peut être intrusif.\n\nMais ne vous inquiétez pas ! Rechercher et naviguer de manière confidentielle est plus facile que vous ne le pensez."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Désactivé"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Politique de confidentialité"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "J'ai un code d'invitation"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Commencer"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Vous êtes sur liste d'attente !"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Ouvrez votre invitation"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Vous êtes sur liste d'attente !"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Rejoindre la liste d'attente privée"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alertes VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaurez automatiquement une connexion VPN après une interruption."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Toujours activé"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notifications VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection empêche les fuites DNS vers votre fournisseur de services Internet en acheminant les requêtes DNS via le tunnel VPN vers notre propre résolveur."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS sécurisé"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Paramètres VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Verrouillage de l'application"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Effacer automatiquement les données"; +"settings.autolock.description" = "Si Touch ID, Face ID ou un code d'accès au système est mis en place, il vous sera demandé de déverrouiller l'application lors de l'ouverture."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Effacer automatiquement les données"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 120056b994..33a5f3c036 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Uredi"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet može biti pomalo neugodan.\n\nNe brini! Privatno pretraživanje i pregledavanje lakše je nego što misliš."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Onemogućeno"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Pravila o zaštiti privatnosti"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Imam pozivnu šifru"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Započnite"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Na popisu ste!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otvori svoju pozivnicu"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Na popisu ste!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pridružite se privatnoj listi čekanja"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN upozorenja"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatski obnovi VPN vezu nakon prekida."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Uvijek uključeno"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN obavijesti"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Mrežna zaštita sprječava curenje DNS-a tvom davatelju internetskih usluga usmjeravanjem DNS upita kroz VPN tunel na naš vlastiti rješavač."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Siguran DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN postavke"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Zaključavanje aplikacije"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automatsko brisanje podataka"; +"settings.autolock.description" = "Ako su postavljeni Touch ID, Face ID ili pristupni kôd sustava, od tebe će se tražiti da otključaš aplikaciju prilikom otvaranja."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatsko brisanje podataka"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index b1d6ff0980..e955b5d849 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Szerkesztés"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Az internet meglehetősen undok hely lehet.\n\nNe aggódj! A bizalmas keresés és böngészés egyszerűbb, mint hinnéd."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Letiltva"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Adatvédelmi szabályzat"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Van meghívókódom"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Első lépések"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Felkerültél a listára!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Meghívó megnyitása"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Felkerültél a listára!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Csatlakozz a privát várólistához"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-figyelmeztetések"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "A VPN-kapcsolat automatikus helyreállítása megszakítás után."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Mindig be van kapcsolva"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-értesítések"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "A hálózatvédelem megakadályozza a DNS-szivárgást az internetszolgáltatód felé azáltal, hogy a DNS-lekérdezéseket a VPN-alagúton keresztül a saját feloldónkhoz irányítja."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Biztonságos DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-beállítások"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Alkalmazás zárolás"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Adatok automatikus törlése"; +"settings.autolock.description" = "Ha be van állítva ujjlenyomat- vagy arcfelismerés, illetve rendszerjelszó, megnyitásakor fel kell oldanod az alkalmazást."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Adatok automatikus törlése"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index b8ef30c37b..3e04c792df 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Modifica"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet può essere un po' inquietante.\n\nNon preoccuparti! Effettuare ricerche e navigare in modo privato è più facile di quanto pensi."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Disattivato"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privacy policy"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Ho un codice invito"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Iniziamo"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Sei in lista d'attesa."; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Apri il tuo invito"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Sei in lista d'attesa."; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Iscriviti alla lista d'attesa privata"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Avvisi VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Ripristina automaticamente una connessione VPN dopo un'interruzione."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Sempre attiva"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notifiche VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection previene le fughe di DNS verso il tuo Internet Service Provider instradando le query DNS tramite tunneling VPN verso il nostro resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS sicuro"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Impostazioni VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Blocco applicazione"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Cancellazione automatica dei dati"; +"settings.autolock.description" = "Se hai impostato Touch ID, Face ID o un codice di accesso al sistema, ti verrà richiesto di sbloccare l'app all'apertura."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Cancellazione automatica dei dati"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 3d2814b6a2..85973a6334 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Redaguoti"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internetas gali būti bauginantis.\n\nNesijaudink! Ieškoti ir naršyti privačiai yra lengviau, nei manai."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Išjungta"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privatumo Politika"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Turiu kvietimo kodą"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Pradėti"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Esate sąraše!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Atidaryti pakvietimą"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Esate sąraše!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Prisijungti prie privataus laukimo sąrašo"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN įspėjimai"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatiškai atkurti VPN ryšį po nutrūkimo."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Visada įjungta"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN pranešimai"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Tinklo apsauga apsaugo nuo DNS nutekėjimo interneto paslaugų teikėjui, nukreipdama DNS užklausas per VPN tunelį į mūsų trūkumo šalinimo įrankį."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Saugi DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN nustatymai"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Programos užraktas"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automatiškai valyti duomenis"; +"settings.autolock.description" = "Jei nustatytas „Touch ID“, „Face ID“ arba sistemos slaptažodis, prieš atidarydami būsite paprašyti atrakinti programą."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatiškai valyti duomenis"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 52d30b5ce4..668740e6b4 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Rediģēt"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internets var būt diezgan “mežonīgs”.\n\nBet neuztraucies! Meklēt un pārlūkot privātā režīmā ir vieglāk, nekā tu domā."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Atspējota"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privātuma politika"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Man ir uzaicinājuma kods"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Sākt darbu"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Tu esi sarakstā!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Atver savu ielūgumu"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Tu esi sarakstā!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pievienojies privātajam nogaides sarakstam"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN brīdinājumi"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automātiski atjaunot VPN savienojumu pēc pārtraukuma."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vienmēr ieslēgts"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN paziņojumi"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Tīkla aizsardzība novērš DNS noplūdi uz tavu interneta pakalpojumu sniedzēju, novirzot DNS vaicājumus caur VPN tuneli uz mūsu pašu atrisinātāju."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Drošs DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN iestatījumi"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Lietojumprogrammas bloķēšana"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automātiski notīrīt datus"; +"settings.autolock.description" = "Ja ir iestatīts Touch ID, Face ID vai sistēmas piekļuves kods, atverot lietotni, tev tā būs jāatbloķē."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automātiski notīrīt datus"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 1a254fb913..6e49d83cc6 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Rediger"; @@ -1781,7 +1778,7 @@ "settings.autolock" = "Applås"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Slett data automatisk"; +"settings.autolock.description" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Slett data automatisk"; diff --git a/DuckDuckGo/nb.lproj/Settings.strings b/DuckDuckGo/nb.lproj/Settings.strings index 8c3750d5fb..95cb40ddef 100644 --- a/DuckDuckGo/nb.lproj/Settings.strings +++ b/DuckDuckGo/nb.lproj/Settings.strings @@ -1,15 +1,3 @@ -/* Class = "UILabel"; text = "Fireproof Sites"; ObjectID = "0DQ-yq-UuT"; */ -"0DQ-yq-UuT.text" = "Brannsikre nettsteder"; - -/* Class = "UITableViewSection"; footerTitle = "Disable to prevent links from automatically opening in other installed apps."; ObjectID = "0gU-rZ-pRc"; */ -"0gU-rZ-pRc.footerTitle" = "Deaktiver for å forhindre at lenker automatisk åpnes i andre installerte apper."; - -/* Class = "UITableViewSection"; headerTitle = "Customize"; ObjectID = "0gU-rZ-pRc"; */ -"0gU-rZ-pRc.headerTitle" = "Tilpass"; - -/* Class = "UILabel"; text = "Version"; ObjectID = "2ky-s9-1aZ"; */ -"2ky-s9-1aZ.text" = "Versjon"; - /* Class = "UINavigationItem"; title = "Keyboard"; ObjectID = "2pp-PM-6rW"; */ "2pp-PM-6rW.title" = "Tastatur"; @@ -31,30 +19,9 @@ /* Class = "UILabel"; text = "Clear Tabs and Data"; ObjectID = "9fc-9r-4aA"; */ "9fc-9r-4aA.text" = "Lukk faner og slett data"; -/* Class = "UILabel"; text = "Text Size"; ObjectID = "9Ko-0g-T3h"; */ -"9Ko-0g-T3h.text" = "Tekststørrelse"; - -/* Class = "UILabel"; text = "Title"; ObjectID = "9kt-6R-XiZ"; */ -"9kt-6R-XiZ.text" = "Tittel"; - /* Class = "UILabel"; text = "App Launch"; ObjectID = "13n-KI-KLq"; */ "13n-KI-KLq.text" = "Appoversikt"; -/* Class = "UILabel"; text = "Open Links in Associated Apps"; ObjectID = "a1T-ui-4Nw"; */ -"a1T-ui-4Nw.text" = "Åpne lenker i tilknyttede apper"; - -/* Class = "UILabel"; text = "Debug Menu"; ObjectID = "A9G-5I-RSn"; */ -"A9G-5I-RSn.text" = "Feilsøkingsmeny"; - -/* Class = "UILabel"; text = "Logins"; ObjectID = "And-cQ-SEu"; */ -"And-cQ-SEu.text" = "Pålogginger"; - -/* Class = "UILabel"; text = "Animation"; ObjectID = "AtR-nS-Gun"; */ -"AtR-nS-Gun.text" = "Animasjon"; - -/* Class = "UILabel"; text = "Email Protection"; ObjectID = "azf-Nc-kvW"; */ -"azf-Nc-kvW.text" = "E-postbeskyttelse"; - /* Class = "UITableViewSection"; footerTitle = "Data and/or tabs will be cleared upon restart of the app."; ObjectID = "BGs-JL-4ib"; */ "BGs-JL-4ib.footerTitle" = "Data og/eller faner slettes/lukkes ved omstart av appen."; @@ -64,114 +31,42 @@ /* Class = "UINavigationItem"; title = "Manage Cookie Pop-ups"; ObjectID = "btj-ri-kRr"; */ "btj-ri-kRr.title" = "Administrer vinduer om informasjonskapsler"; -/* Class = "UILabel"; text = "App Icon"; ObjectID = "cKo-er-HNj"; */ -"cKo-er-HNj.text" = "Appikon"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "CR5-Al-WIW"; */ -"CR5-Al-WIW.text" = "Etikett"; - /* Class = "UILabel"; text = "Global Privacy Control (GPC)"; ObjectID = "cW7-OM-2oW"; */ "cW7-OM-2oW.text" = "Global Privacy Control (GPC)"; /* Class = "UIBarButtonItem"; title = "Add"; ObjectID = "CxT-QK-iVn"; */ "CxT-QK-iVn.title" = "Legg til"; -/* Class = "UILabel"; text = "Hide your location and conceal your online activity"; ObjectID = "Cyw-ir-cSK"; */ -"Cyw-ir-cSK.text" = "Hide your location and conceal your online activity"; - /* Class = "UILabel"; text = "App Exit, Inactive for 15 Minutes"; ObjectID = "D0R-jp-UY8"; */ "D0R-jp-UY8.text" = "når appen lukkes eller har vært inaktiv i 15 minutter"; -/* Class = "UILabel"; text = "10.0.1 (Build 10005)"; ObjectID = "d5n-vG-8kF"; */ -"d5n-vG-8kF.text" = "10.0.1 (Build 10005)"; - -/* Class = "UILabel"; text = "Application Lock"; ObjectID = "dBZ-yq-FYj"; */ -"dBZ-yq-FYj.text" = "Applås"; - -/* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "dj9-vh-Rig"; */ -"dj9-vh-Rig.headerTitle" = "Utseende"; - -/* Class = "UITableViewSection"; footerTitle = "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening."; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.footerTitle" = "Hvis du har touch-ID, face-ID eller systempassord, blir du bedt om å låse opp appen når du åpner den."; - -/* Class = "UITableViewSection"; headerTitle = "Privacy"; ObjectID = "dOj-jn-mSN"; */ -"dOj-jn-mSN.headerTitle" = "Personvern"; - /* Class = "UILabel"; text = "Label"; ObjectID = "dud-qo-Ces"; */ "dud-qo-Ces.text" = "Etikett"; -/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "Dyd-bm-goj"; */ -"Dyd-bm-goj.title" = "Innstillinger"; - -/* Class = "UILabel"; text = "100%"; ObjectID = "EB8-09-gt2"; */ -"EB8-09-gt2.text" = "100 %"; - /* Class = "UILabel"; text = "Label"; ObjectID = "EIq-Ev-nfj"; */ "EIq-Ev-nfj.text" = "Etikett"; -/* Class = "UILabel"; text = "Automatically Clear Data"; ObjectID = "ekF-SJ-PAQ"; */ -"ekF-SJ-PAQ.text" = "Slett data automatisk"; - /* Class = "UILabel"; text = "App Exit, Inactive for 5 Minutes"; ObjectID = "ElX-yE-4PX"; */ "ElX-yE-4PX.text" = "når appen lukkes eller har vært inaktiv i 5 minutter"; /* Class = "UINavigationItem"; title = "App Icon"; ObjectID = "eMH-uv-Kms"; */ "eMH-uv-Kms.title" = "Appikon"; -/* Class = "UILabel"; text = "Theme"; ObjectID = "f1O-6u-LFY"; */ -"f1O-6u-LFY.text" = "Utseende"; - /* Class = "UILabel"; text = "App Exit Only"; ObjectID = "Fal-1Y-o2S"; */ "Fal-1Y-o2S.text" = "kun når appen lukkes"; /* Class = "UITableViewController"; title = "Automatically Clear Data"; ObjectID = "fdJ-b1-Des"; */ "fdJ-b1-Des.title" = "Slett data automatisk"; -/* Class = "UILabel"; text = "Unprotected Sites"; ObjectID = "FHC-1z-Z3v"; */ -"FHC-1z-Z3v.text" = "Ubeskyttede nettsteder"; - -/* Class = "UITableViewSection"; headerTitle = "About"; ObjectID = "FpT-1C-xtx"; */ -"FpT-1C-xtx.headerTitle" = "Om"; - /* Class = "UITableViewController"; title = "Global Privacy Control (GPC)"; ObjectID = "fV3-86-QQj"; */ "fV3-86-QQj.title" = "Global Privacy Control (GPC)"; -/* Class = "UILabel"; text = "Add Widget to Home Screen"; ObjectID = "Fxu-zn-51Z"; */ -"Fxu-zn-51Z.text" = "Legg til widgeten på startskjermen"; - -/* Class = "UILabel"; text = "Fire Button Animation"; ObjectID = "gBo-Cu-e2k"; Note = "Fire button animation settings item"; */ -"gBo-Cu-e2k.text" = "Animasjon for brannknappen"; - -/* Class = "UILabel"; text = "Default"; ObjectID = "Gbx-kl-uOO"; */ -"Gbx-kl-uOO.text" = "Standard"; - -/* Class = "UILabel"; text = "Manage Cookie Pop-ups"; ObjectID = "GRv-M2-Kx1"; */ -"GRv-M2-Kx1.text" = "Administrer vinduer om informasjonskapsler"; - -/* Class = "UINavigationItem"; title = "Theme"; ObjectID = "gS2-mg-l7R"; */ -"gS2-mg-l7R.title" = "Utseende"; - -/* Class = "UILabel"; text = "Long-Press Previews"; ObjectID = "HLr-R8-xxF"; */ -"HLr-R8-xxF.text" = "Forhåndsvisning ved å trykke og holde"; - -/* Class = "UILabel"; text = "Browse privately with our app for Windows"; ObjectID = "hoT-Nu-KXP"; */ -"hoT-Nu-KXP.text" = "Surf privat med vår app for Windows"; - /* Class = "UILabel"; text = "Privacy Protection enabled for all sites"; ObjectID = "Hu1-5i-vjL"; */ "Hu1-5i-vjL.text" = "Personvernbeskyttelse aktivert for alle nettsteder"; /* Class = "UICollectionViewController"; title = "Icon"; ObjectID = "jbD-Oy-Cmw"; */ "jbD-Oy-Cmw.title" = "Ikon"; -/* Class = "UILabel"; text = "Share Feedback"; ObjectID = "m23-t6-9cb"; */ -"m23-t6-9cb.text" = "Del tilbakemelding"; - -/* Class = "UINavigationItem"; title = "Address Bar Position"; ObjectID = "mLn-1x-Fl5"; Note = "Fire button animation setting page title"; */ -"mLn-1x-Fl5.title" = "Plassering av adressefelt"; - -/* Class = "UILabel"; text = "Address Bar Position"; ObjectID = "mqV-pf-NZ1"; Note = "Fire button animation settings item"; */ -"mqV-pf-NZ1.text" = "Plassering av adressefelt"; - /* Class = "UILabel"; text = "Let DuckDuckGo manage cookie consent pop-ups"; ObjectID = "nX1-F1-Frd"; */ "nX1-F1-Frd.text" = "La DuckDuckGo administrere popup-vinduer om samtykke til informasjonskapsler"; @@ -181,36 +76,9 @@ /* Class = "UINavigationItem"; title = "Unprotected Sites"; ObjectID = "OHV-qC-tL9"; */ "OHV-qC-tL9.title" = "Ubeskyttede nettsteder"; -/* Class = "UILabel"; text = "About DuckDuckGo"; ObjectID = "oM7-1o-9oY"; */ -"oM7-1o-9oY.text" = "Om DuckDuckGo"; - -/* Class = "UILabel"; text = "Top"; ObjectID = "opn-JO-idF"; */ -"opn-JO-idF.text" = "Topp"; - -/* Class = "UITableViewSection"; headerTitle = "More From DuckDuckGo"; ObjectID = "OxE-MQ-uJk"; */ -"OxE-MQ-uJk.headerTitle" = "Mer fra DuckDuckGo"; - -/* Class = "UILabel"; text = "Sync & Back Up"; ObjectID = "oXN-ez-gct"; */ -"oXN-ez-gct.text" = "Synkronisering og sikkerhetskopiering"; - -/* Class = "UILabel"; text = "Browse privately with our app for Mac "; ObjectID = "P0F-ts-ekd"; */ -"P0F-ts-ekd.text" = "Surf privat med vår app for Mac "; - -/* Class = "UILabel"; text = "Network Protection"; ObjectID = "qah-gb-udB"; */ -"qah-gb-udB.text" = "Network Protection"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "qeN-SV-zy7"; */ -"qeN-SV-zy7.text" = "Etikett"; - -/* Class = "UILabel"; text = "DuckDuckGo Windows App"; ObjectID = "RQ8-H1-Ez1"; */ -"RQ8-H1-Ez1.text" = "DuckDuckGo Windows-app"; - /* Class = "UINavigationItem"; title = "Text Size"; ObjectID = "ssa-zd-L3T"; */ "ssa-zd-L3T.title" = "Tekststørrelse"; -/* Class = "UILabel"; text = "Private Voice Search"; ObjectID = "Swa-O7-n8W"; */ -"Swa-O7-n8W.text" = "Privat talesøk"; - /* Class = "UITableViewSection"; headerTitle = "Show keyboard on"; ObjectID = "tGh-di-rfq"; */ "tGh-di-rfq.headerTitle" = "Vis tastatur i"; @@ -220,42 +88,18 @@ /* Class = "UITableViewSection"; headerTitle = "Action"; ObjectID = "U2M-6p-6nl"; */ "U2M-6p-6nl.headerTitle" = "Handling"; -/* Class = "UILabel"; text = "Autocomplete Suggestions"; ObjectID = "U8i-cQ-5WW"; */ -"U8i-cQ-5WW.text" = "Vis forslag fra autofullføring"; - -/* Class = "UINavigationItem"; title = "Fire Button Animation"; ObjectID = "uns-8w-IwL"; Note = "Fire button animation setting page title"; */ -"uns-8w-IwL.title" = "Animasjon for brannknappen"; - /* Class = "UILabel"; text = "Remove All Fireproof Sites"; ObjectID = "UZx-52-aer"; */ "UZx-52-aer.text" = "Fjern alle brannsikre nettsteder"; -/* Class = "UILabel"; text = "Global Privacy Control (GPC)"; ObjectID = "vPz-uO-6gB"; */ -"vPz-uO-6gB.text" = "Global Privacy Control (GPC)"; - /* Class = "UITableViewSection"; footerTitle = "App exit is defined by swiping the app to close it while inactivity is when the app is in the background."; ObjectID = "vSJ-YJ-bBs"; Note = "Timing setting for automatic data clearing function"; */ "vSJ-YJ-bBs.footerTitle" = "Apper lukkes når de avsluttes ved sveiping, og regnes som inaktive når de ligger i bakgrunnen."; /* Class = "UITableViewSection"; headerTitle = "Desired timing"; ObjectID = "vSJ-YJ-bBs"; Note = "Timing setting for automatic data clearing function"; */ "vSJ-YJ-bBs.headerTitle" = "Utføres"; -/* Class = "UILabel"; text = "Set as Default Browser"; ObjectID = "xof-5k-PkI"; */ -"xof-5k-PkI.text" = "Gjør til standardnettleser"; - /* Class = "UINavigationItem"; title = "Fireproof Sites"; ObjectID = "xUX-nF-HOl"; */ "xUX-nF-HOl.title" = "Brannsikre nettsteder"; -/* Class = "UILabel"; text = "Block email trackers and hide your address"; ObjectID = "Y6Y-wA-n6Z"; */ -"Y6Y-wA-n6Z.text" = "Blokker e-postsporere og skjul adressen din"; - -/* Class = "UILabel"; text = "Keyboard"; ObjectID = "yoZ-jw-Cu3"; */ -"yoZ-jw-Cu3.text" = "Tastatur"; - -/* Class = "UILabel"; text = "Add App to Your Dock"; ObjectID = "yvj-LL-MiR"; */ -"yvj-LL-MiR.text" = "Legg til appen i docken"; - -/* Class = "UILabel"; text = "DuckDuckGo Mac App"; ObjectID = "Yz9-17-qnn"; */ -"Yz9-17-qnn.text" = "DuckDuckGo Mac-app"; - /* Class = "UILabel"; text = "New Tab"; ObjectID = "Zpg-h0-rYv"; */ "Zpg-h0-rYv.text" = "Ny fane"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 16a4df7468..1283674abd 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Bewerken"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet kan best eng zijn.\n\nMaak je geen zorgen! Privé zoeken en browsen is eenvoudiger dan je denkt."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Uitgeschakeld"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Privacybeleid"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Ik heb een uitnodigingscode"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Aan de slag"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Je staat op de lijst!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Open je uitnodiging"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Je staat op de lijst!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Schrijf je in voor de privéwachtlijst"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-meldingen"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Herstel een VPN-verbinding automatisch na onderbreking."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Altijd aan"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-meldingen"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Netwerkbeveiliging voorkomt DNS-lekken naar je internetprovider door DNS-aanvragen via de VPN-tunnel naar onze eigen resolver te routeren."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Veilige DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-instellingen"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "App-vergrendeling"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Gegevens automatisch wissen"; +"settings.autolock.description" = "Als je Touch ID, Face ID of een systeemwachtwoord hebt ingesteld, word je gevraagd om de app te ontgrendelen als je deze opent."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Gegevens automatisch wissen"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 9c59af675f..657547f480 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Edytuj"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet może być trochę nieprzyjemny.\n\nBez obaw! Prywatne wyszukiwanie i przeglądanie jest łatwiejsze niż myślisz."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Wyłączone"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Polityka prywatności"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mam kod zaproszenia"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Rozpocznij"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Jesteś na liście!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otwórz zaproszenie"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Jesteś na liście!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Dołącz do prywatnej listy oczekujących"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alerty VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatycznie przywracaj połączenie VPN po jego przerwaniu."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Zawsze włączone"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Powiadomienia VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ochrona sieci zapobiega wyciekom DNS do dostawcy usług internetowych poprzez kierowanie zapytań DNS przez tunel VPN do naszego mechanizmu rozpoznawania nazw."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Bezpieczny DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Ustawienia VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Blokada aplikacji"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automatyczne czyszczenie danych"; +"settings.autolock.description" = "Jeśli ustawiono Touch ID, Face ID lub hasło systemowe, pojawi się prośba o odblokowanie aplikacji podczas otwierania."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automatyczne czyszczenie danych"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 07f1fe4c1a..484df25045 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Editar"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "A Internet pode ser um pouco assustadora.\n\nMas não se preocupe! Pesquisar e navegar em privado é mais fácil do que pensa."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Desativado"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Política de Privacidade"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Tenho um Código de Convite"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Comece"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Está na lista!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Abra o seu convite"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Está na lista!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Adira à Lista de Espera Privada"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alertas de VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaurar automaticamente uma ligação VPN após a interrupção."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Sempre ligada"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notificações da VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "A Network Protection impede fugas de DNS para o teu fornecedor de serviços de internet ao encaminhar as consultas de DNS através do túnel da VPN para o nosso resolvedor."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS seguro"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Definições da VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Bloqueio de aplicações"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Limpar os dados automaticamente"; +"settings.autolock.description" = "Se o Touch ID, Face ID ou um código de acesso estiverem definidos, ser-lhe-á pedido o desbloqueio da aplicação ao abrir."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Limpar os dados automaticamente"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 3a135ca1bf..586833df57 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Editați"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internetul poate fi cam terifiant.\n\nNu te îngrijora! Căutarea și navigarea în mod confidențial sunt mai ușoare decât crezi."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Dezactivat"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Politica de confidențialitate"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Am un cod de invitație"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Să începem"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Ești pe listă!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Deschide invitația ta"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Ești pe listă!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Alătură-te listei de așteptare private"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Alerte VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Restaurează automat o conexiune VPN după întrerupere."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Întotdeauna activat"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Notificări VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection previne scurgerile DNS către furnizorul tău de servicii de internet prin direcționarea interogărilor DNS prin tunelul VPN către propriul nostru resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "DNS securizat"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Setări VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Blocarea aplicației"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Șterge automat datele"; +"settings.autolock.description" = "Dacă este setat Touch ID, Face ID sau o parolă de sistem, ți se va solicita să deblochezi aplicația la deschidere."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Șterge automat datele"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 6cc266c4ef..eefdff8811 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Редактировать"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Интернет набит трекерами.\n\nНо выход есть! Пользоваться сетью без слежки проще, чем вы думали."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Отключено"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Политика конфиденциальности"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "У меня есть пригласительный код"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Приступим!"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Вы в списке!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Откройте приглашение"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Вы в списке!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Приглашаем встать в очередь"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Оповещения о VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Автоматически восстанавливать VPN-соединение в случае его прерывания."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Всегда включено"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Уведомления о VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Защита сети предотвращает утечку данных через DNS вашему интернет-провайдеру, направляя DNS-запросы через VPN-туннель к нашему собственному сопоставителю."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Безопасный DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Настройки VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Блокировка"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Автоудаление данных"; +"settings.autolock.description" = "Если система защищена технологией Touch ID или Face ID либо кодом доступа, при запуске вам придется разблокировать приложение."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Автоудаление данных"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 6fdccf112b..710f2607f3 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Upraviť"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet môže byť dosť nevyspytateľný.\n\nNemajte žiadne obavy! Súkromné vyhľadávanie a prehliadanie je jednoduchšie, ako si myslíte."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Zakázané"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Zásady ochrany súkromia"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Mám pozývací kód"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Začnite"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Ste na zozname čakateľov!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Otvorte svoju pozvánku"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Ste na zozname čakateľov!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pridajte sa do súkromného zoznamu čakateľov"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Upozornenia týkajúce sa VPN siete"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Automatické obnovenie pripojenia VPN po prerušení."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vždy zapnuté"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Oznámenia VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ochrana siete zabraňuje úniku informácií DNS k poskytovateľovi internetových služieb tým, že presmeruje DNS požiadavky cez VPN tunel na náš vlastný, tzv. „DNS resolver“."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Zabezpečený systém DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Nastavenia siete VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Zámok aplikácie"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Automaticky vymazať údaje"; +"settings.autolock.description" = "Ak je nastavená funkcia Touch ID, Face ID alebo systémový prístupový kód, pri otvorení aplikácie sa zobrazí výzva na odomknutie aplikácie."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Automaticky vymazať údaje"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 0c65a91575..48f5fb635e 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Uredi"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet je lahko grozljiv.\n\nNe skrbi! Iskanje in brskanje na zaseben način je lažje, kot si misliš."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Onemogočeno"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Pravilnik o zasebnosti"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Imam kodo povabila"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Začni"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Na seznamu ste!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Odprite svoje povabilo"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Na seznamu ste!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Pridruži se zasebnemu čakalnemu seznamu"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "Opozorila VPN"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Samodejno obnovite povezavo VPN po prekinitvi."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Vedno vklopljeno"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "Obvestila VPN"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Zaščita omrežja preprečuje uhajanje podatkov DNS k ponudniku internetnih storitev, saj poizvedbe DNS usmerja prek tunela VPN k lastnemu razreševalniku."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Varen DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "Nastavitve VPN"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Zaklepanje aplikacije"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Samodejno počisti podatke"; +"settings.autolock.description" = "Če nastavite prepoznavanje z dotikom, prepoznavanje z obrazom ali sistemsko geslo, boste ob odpiranju morali odkleniti aplikacijo."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Samodejno počisti podatke"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 089e6b318b..e397a80ab6 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Redigera"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "Internet kan vara lite läskigt.\n\nOroa dig inte! Att söka och surfa privat är lättare än du tror."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Inaktiverad"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Integritetspolicy"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Jag har en inbjudningskod"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Kom igång"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Du står på listan!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Öppna din inbjudan"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Du står på listan!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Ställ dig i den privata väntelistan"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN-varningar"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Återställ automatiskt en VPN-anslutning efter avbrott."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Alltid på"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN-aviseringar"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Network Protection förhindrar DNS-läckor till din internetleverantör genom att dirigera DNS-frågor genom VPN-tunneln till vår egen resolver."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Säker DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN-inställningar"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "App-lås"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Rensa data automatiskt"; +"settings.autolock.description" = "Om Touch ID, Face ID eller ett systemlösenord har konfigurerats ombes du låsa upp appen när du öppnar."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Rensa data automatiskt"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 07cadb7e71..05cba409c8 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -4,9 +4,6 @@ /* No comment provided by engineer. */ "%@ [%@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)" = "%1$@ [%2$@](https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945)"; -/* No comment provided by engineer. */ -"⚠️ FEATURE IS WORK IN PROGRESS ⚠️" = "⚠️ FEATURE IS WORK IN PROGRESS ⚠️"; - /* Buton label for Edit action */ "action.generic.edit" = "Düzenle"; @@ -874,6 +871,9 @@ /* No comment provided by engineer. */ "dax.onboarding.message" = "İnternet bazen ürkütücü olabilir.\n\nEndişelenmeyin! İnternette kimsenin göremeyeceği şekilde arama yapmak ve gezinmek sandığınızdan çok daha kolay."; +/* No comment provided by engineer. */ +"Debug" = "Debug"; + /* GPC Setting state */ "donotsell.disabled" = "Devre Dışı"; @@ -1414,18 +1414,105 @@ /* Title for the Network Protection feature */ "netP.title" = "Network Protection"; +/* Privacy Policy title for Network Protection */ +"network-protection.privacy-policy.title" = "Gizlilik Politikası"; + +/* Title text for the Network Protection terms and conditions accept button */ +"network-protection.waitlist.agree-and-continue" = "Agree and Continue"; + +/* Availability disclaimer for Network Protection join waitlist screen */ +"network-protection.waitlist.availability-disclaimer" = "Network Protection is free to use during early access."; + +/* Agree and Continue button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.agree-and-continue" = "Agree and Continue"; + +/* Enable Notifications button for Network Protection joined waitlist screen */ +"network-protection.waitlist.button.enable-notifications" = "Enable Notifications"; + +/* Button title for users who already have an invite code */ +"network-protection.waitlist.button.existing-invite-code" = "Davet Kodum var"; + +/* Join Waitlist button for Network Protection join waitlist screen */ +"network-protection.waitlist.button.join-waitlist" = "Join the Waitlist"; + +/* Button title text for the Network Protection waitlist confirmation prompt */ +"network-protection.waitlist.get-started" = "Başlayın"; + +/* Subtitle for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.subtitle" = "Encrypt online traffic across your browsers and apps."; + +/* Title for section 1 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-1.title" = "Full-device coverage"; + +/* Subtitle for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.subtitle" = "No need for a separate app. Connect in one click and see your connection status at a glance."; + +/* Title for section 2 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-2.title" = "Fast, reliable, and easy to use"; + +/* Subtitle for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.subtitle" = "We do not log or save any data that can connect you to your online activity."; + +/* Title for section 3 of the Network Protection invited screen */ +"network-protection.waitlist.invited.section-3.title" = "Strict no-logging policy"; + +/* Subtitle for Network Protection invited screen */ +"network-protection.waitlist.invited.subtitle" = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit."; + +/* Title for Network Protection invited screen */ +"network-protection.waitlist.invited.title" = "You’re invited to try\nNetwork Protection early access!"; + +/* First subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.1" = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo."; + +/* Second subtitle for Network Protection join waitlist screen */ +"network-protection.waitlist.join.subtitle.2" = "Join the waitlist, and we’ll notify you when it’s your turn."; + +/* Title for Network Protection join waitlist screen */ +"network-protection.waitlist.join.title" = "Network Protection Early Access"; + +/* Title for Network Protection joined waitlist screen */ +"network-protection.waitlist.joined.title" = "Listedesiniz!"; + +/* Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.1" = "New invites are sent every few days, on a first come, first served basis."; + +/* Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled */ +"network-protection.waitlist.joined.with-notifications.subtitle.2" = "We’ll notify you when your invite is ready."; + +/* Body text for the alert to enable notifications */ +"network-protection.waitlist.notification-alert.description" = "We’ll send you a notification when your invite to test Network Protection is ready."; + +/* Subtitle for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-description" = "Get a notification when your copy of Network Protection early access is ready."; + +/* Title for the alert to confirm enabling notifications */ +"network-protection.waitlist.notification-prompt-title" = "Know the instant you're invited"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.text" = "Davetiyenizi açın"; + +/* Title for Network Protection waitlist notification */ +"network-protection.waitlist.notification.title" = "Network Protection is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-and-invited" = "Your invite is ready!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.joined-but-not-invited" = "Listedesiniz!"; + +/* Subtitle text for the Network Protection settings row */ +"network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Özel Bekleme Listesine Katılın"; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; /* Title for the network protection invite screen */ -"network.protection.invite.dialog.title" = "You're invited to try Network Protection"; +"network.protection.invite.dialog.title" = "You’re invited to try Network Protection"; /* Prompt for the network protection invite code text field */ "network.protection.invite.field.prompt" = "Invite Code"; -/* Message explaining that netP is invite only */ -"network.protection.invite.only.message" = "DuckDuckGo Network Protection is currently invite-only."; - /* Message for the network protection invite success view */ "network.protection.invite.success.message" = "Hide your location from websites and conceal your online activity from Internet providers and others on your network."; @@ -1456,7 +1543,7 @@ /* Header title label text for the status view when netP is connected */ "network.protection.status.header.title.on" = "Network Protection is On"; -/* The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text */ +/* The status view 'Share Feedback' button which is shown inline on the status view after the temporary free use footer text */ "network.protection.status.menu.share.feedback" = "Share Feedback"; /* Connection details label shown in NetworkProtection's status view. */ @@ -1492,24 +1579,33 @@ /* Title for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.title" = "VPN Uyarıları"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.footer" = "Kesintiden sonra VPN bağlantısını otomatik olarak geri yükleyin."; +/* Footer text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.always.on.setting.title" = "Her Zaman Açık"; +/* Title for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; /* Title for the VPN Location screen's All Countries section. */ "network.protection.vpn.location.all.countries.section.title" = "All Countries"; +/* Subtitle of countries item when there are multiple cities, example : */ +"network.protection.vpn.location.country.item.formatted.cities.count" = "%d cities"; + /* Title for the VPN Location screen's Nearest Available selection item. */ "network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; /* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ -"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find."; /* Title for the VPN Location screen's Recommended section. */ "network.protection.vpn.location.recommended.section.title" = "Recommended"; +/* Subtitle for the preferred location item that formats a city and country. E.g Chicago, United States */ +"network.protection.vpn.location.subtitle.formatted.city.and.country" = "%1$@, %2$@"; + +/* Title for the VPN Location screen. */ +"network.protection.vpn.location.title" = "VPN Location"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN Bildirimleri"; @@ -1520,10 +1616,7 @@ "network.protection.vpn.preferred.location.title" = "Preferred Location"; /* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Ağ Koruması, DNS sorgularını VPN tüneli üzerinden kendi çözümleyicimize yönlendirerek İnternet Servis Sağlayıcınıza DNS sızıntılarını önler."; - -/* Title for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.title" = "Güvenli DNS"; +"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; /* Title for the VPN Settings screen. */ "network.protection.vpn.settings.title" = "VPN Ayarları"; @@ -1685,7 +1778,7 @@ "settings.autolock" = "Uygulama Kilidi"; /* Section footer Autolock description */ -"dOj-jn-mSN.footerTitle" = "Verileri Otomatik Olarak Temizle"; +"settings.autolock.description" = "Touch ID, Face ID veya sistem parolası belirlenmişse uygulamayı açarken kilidini açmanız istenir."; /* Settings screen cell text for Automatically Clearing Data */ "settings.clear.data" = "Verileri Otomatik Olarak Temizle"; From 379b6c943d9a02dd274cc095d7288ce85bcad103 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 18:42:11 +0100 Subject: [PATCH 80/99] Base Privacy Pro Cell in Settings --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +++ DuckDuckGo/SettingsCell.swift | 2 +- DuckDuckGo/SettingsMoreView.swift | 4 +-- DuckDuckGo/SettingsPrivacyProView.swift | 40 +++++++++++++++++++++++++ DuckDuckGo/SettingsView.swift | 5 ++++ DuckDuckGo/SettingsViewModel.swift | 1 + DuckDuckGo/UserText.swift | 20 +++++++++++++ DuckDuckGo/en.lproj/Localizable.strings | 16 ++++++++++ 8 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 DuckDuckGo/SettingsPrivacyProView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b5b7ba6852..cd97935e95 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -790,6 +790,7 @@ D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B42B289AA000CBFA76 /* TestUserScript.swift */; }; D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; }; + D69FBF762B28BE3600B505F1 /* SettingsPrivacyProView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -2426,6 +2427,7 @@ D664C7B42B289AA000CBFA76 /* TestUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUserScript.swift; sourceTree = ""; }; D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; D664C7DC2B28A02800CBFA76 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyProView.swift; sourceTree = ""; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -4615,6 +4617,7 @@ D6E83C3C2B1F2C03006C8AFB /* SettingsLoginsView.swift */, D6E83C402B1FC285006C8AFB /* SettingsAppeareanceView.swift */, D6E83C592B2213ED006C8AFB /* SettingsPrivacyView.swift */, + D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */, D6E83C5D2B224676006C8AFB /* SettingsCustomizeView.swift */, D6E83C612B23298B006C8AFB /* SettingsMoreView.swift */, D6E83C632B238432006C8AFB /* SettingsAboutView.swift */, @@ -6550,6 +6553,7 @@ 1E908BF329827C480008C8F3 /* AutoconsentManagement.swift in Sources */, CB9B8739278C8E72001F4906 /* WidgetEducationViewController.swift in Sources */, F4D9C4FA25117A0F00814B71 /* HomeMessageStorage.swift in Sources */, + D69FBF762B28BE3600B505F1 /* SettingsPrivacyProView.swift in Sources */, D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */, AA3D854523D9942200788410 /* AppIconSettingsViewController.swift in Sources */, 85C297042476C1FD0063A335 /* DaxDialogsSettings.swift in Sources */, diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index b8c46a1bdb..f409d8f7c9 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -119,7 +119,7 @@ struct SettingsCellView: View, Identifiable { .foregroundColor(Color(designSystemColor: .textPrimary)) if let subtitleText = subtitle { Text(subtitleText) - .daxCaption() + .daxFootnoteRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) } }.fixedSize(horizontal: false, vertical: true) diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index bbf45fab58..39294c1e50 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -49,7 +49,7 @@ struct SettingsMoreView: View { #if NETWORK_PROTECTION if viewModel.shouldShowNetworkProtectionCell { SettingsCellView(label: UserText.netPNavTitle, - subtitle: viewModel.state.netPSubtitle, + subtitle: viewModel.state.netPSubtitle != "" ? viewModel.state.netPSubtitle : nil, action: { viewModel.presentLegacyView(.netP) }, asLink: true, disclosureIndicator: true) @@ -58,6 +58,4 @@ struct SettingsMoreView: View { } } - - } diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift new file mode 100644 index 0000000000..c5218944b6 --- /dev/null +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -0,0 +1,40 @@ +// +// SettingsPrivacyProView.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 UIKit + +#if SUBSCRIPTION +struct SettingsPrivacyProView: View { + + @EnvironmentObject var viewModel: SettingsViewModel + + var body: some View { + Section(header: Text(UserText.settingsPrivacySection)) { + SettingsCellView(label: UserText.settingsPProSubscribe, subtitle: UserText.settingsPProDescription) + NavigationLink(destination: SubscriptionFlowView()) { + SettingsCellView(label: UserText.settingsPProLearnMore) + } + } + + + } + +} +#endif diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 9066fffcac..3a1bfce97b 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -33,6 +33,11 @@ struct SettingsView: View { SettingsLoginsView() SettingsAppeareanceView() SettingsPrivacyView() +#if SUBSCRIPTION + if viewModel.shouldShowPrivacyProCell { + SettingsPrivacyProView() + } +#endif SettingsCustomizeView() SettingsMoreView() SettingsAboutView() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index be6a7e2344..6d9e037855 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -80,6 +80,7 @@ final class SettingsViewModel: ObservableObject { var shouldShowSpeechRecognitionCell: Bool { AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } var shouldShowNoMicrophonePermissionAlert: Bool = false var shouldShowDebugCell: Bool { return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild } + var shouldShowPrivacyProCell: Bool { return featureFlagger.isFeatureOn(.privacyPro) } var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index fbcf8e7daa..34886fc99e 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -922,12 +922,15 @@ But if you *do* want a peek under the hood, you can find more information about // MARK: Settings Screeen public static let settingsTitle = NSLocalizedString("settings.title", value: "Settings", comment: "Title for the Settings View") + + // General Section public static let settingsSetDefault = NSLocalizedString("settings.default.browser", value: "Set as Default Browser", comment: "Settings screen cell text for setting the app as default browser") public static let settingsAddToDock = NSLocalizedString("settings.add.to.dock", value: "Add App to Your Dock", comment: "Settings screen cell text for adding the app to the dock") public static let settingsAddWidget = NSLocalizedString("settings.add.widget", value: "Add Widget to Home Screen", comment: "Settings screen cell text for add widget to the home screen") public static let settingsSync = NSLocalizedString("settings.sync", value: "Sync & Backup", comment: "Settings screen cell text for sync and backup") public static let settingsLogins = NSLocalizedString("settings.logins", value: "Logins", comment: "Settings screen cell text for logins") + // Appeareance Section public static let settingsAppearanceSection = NSLocalizedString("settings.appearance", value: "Appearance", comment: "Settings screen appearance section title") public static let settingsTheme = NSLocalizedString("settings.theme", value: "Theme", comment: "Settings screen cell text for theme") public static let settingsIcon = NSLocalizedString("settings.icon", value: "App Icon", comment: "Settings screen cell text for app icon selection") @@ -935,6 +938,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsText = NSLocalizedString("settings.text.size", value: "Text Size", comment: "Settings screen cell text for text size") public static let settingsAddressBar = NSLocalizedString("settings.address.bar", value: "Address Bar Position", comment: "Settings screen cell text for addess bar position") + // Privacy Section public static let settingsPrivacySection = NSLocalizedString("settings.privacy", value: "Privacy", comment: "Settings title for the privacy section") public static let settingsGPC = NSLocalizedString("settings.gpc", value: "Global Privacy Control (GPC)", comment: "Settings screen cell text for GPC") public static let settingsCookiePopups = NSLocalizedString("settings.cookie.popups", value: "Manage Cookie Pop-ups", comment: "Settings screen cell text for Cookie popups") @@ -944,6 +948,21 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsAutolock = NSLocalizedString("settings.autolock", value: "Application Lock", comment: "Settings screen cell text for Application Lock") public static let settingsAutoLockDescription = NSLocalizedString("settings.autolock.description", value: "If Touch ID, Face ID or a system passcode is set, you’ll be requested to unlock the app when opening.", comment: "Section footer Autolock description") + // Privacy Pro Section + public static let settingsPProSection = NSLocalizedString("settings.ppro", value: "Privacy Pro", comment: "Product name for the subscription bundle") + public static let settingsPProSubscribe = NSLocalizedString("settings.ppro.subscribe", value: "Subscribe to Privacy Pro", comment: "Call to action title for Privacy Pro") + public static let settingsPProDescription = NSLocalizedString("settings.ppro.description", value: + """ + More seamless privacy with three new protections, including: + + • VPN (Virtual Private Network) + • Personal Information Removal + • Identity Theft Restoration + """, comment: "about page") + + public static let settingsPProLearnMore = NSLocalizedString("settings.ppro.learn.more", value: "Learn More", comment: "Learn more button text for privacy pro") + + // Customize Section public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") public static let settingsPreviews = NSLocalizedString("settings.previews", value: "Long-Press Previews", comment: "Settings screen cell for long press previews") @@ -952,6 +971,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsAssociatedApps = NSLocalizedString("settings.associated.apps", value: "Open Links in Associated Apps", comment: "Settings screen cell for opening links in associated apps") public static let settingsAssociatedAppsDescription = NSLocalizedString("settings.associated.apps.description", value: "Disable to prevent links from automatically opening in other installed apps.", comment: "Description for associated apps description") + // More Section public static let settingsMoreSction = NSLocalizedString("settings.more", value: "More from DuckDuckGo", comment: "Settings title for the 'More' section") public static let settingsEmailProtection = NSLocalizedString("settings.emailProtection", value: "Email Protection", comment: "Settings cell for Email Protection") public static let settingsEmailProtectionDescription = NSLocalizedString("settings.emailProtection.description", value: "Block email trackers and hide your address", comment: "Settings cell for Email Protection") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index c1c24c6b54..628f156c8b 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1810,6 +1810,22 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings title for the 'More' section */ "settings.more" = "More from DuckDuckGo"; +/* Product name for the subscription bundle */ +"settings.ppro" = "Privacy Pro"; + +/* about page */ +"settings.ppro.description" = "More seamless privacy with three new protections, including: + + • VPN (Virtual Private Network) + • Personal Information Removal + • Identity Theft Restoration"; + +/* Learn more button text for privacy pro */ +"settings.ppro.learn.more" = "Learn More"; + +/* Call to action title for Privacy Pro */ +"settings.ppro.subscribe" = "Subscribe to Privacy Pro"; + /* Settings screen cell for long press previews */ "settings.previews" = "Long-Press Previews"; From ea0abd69227c03c0f87f6f00cf3603bf78348998 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 18:52:58 +0100 Subject: [PATCH 81/99] Display Privacy Pro Cell when available --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +++--- DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift | 1 + DuckDuckGo/SettingsPrivacyProView.swift | 6 ++---- DuckDuckGo/SettingsView.swift | 4 +++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cd97935e95..f30a2718ff 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8311,7 +8311,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8841,7 +8841,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -9233,7 +9233,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; diff --git a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift index 68a2cb0d6e..f1fbbe38a5 100644 --- a/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/PrivacyPro/Views/SubscriptionFlowView.swift @@ -21,6 +21,7 @@ import SwiftUI import Foundation +@available(iOS 15.0, *) struct SubscriptionFlowView: View { @ObservedObject var viewModel: SubscriptionFlowViewModel diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift index c5218944b6..bbedc8982c 100644 --- a/DuckDuckGo/SettingsPrivacyProView.swift +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -21,6 +21,7 @@ import SwiftUI import UIKit #if SUBSCRIPTION +@available(iOS 15.0, *) struct SettingsPrivacyProView: View { @EnvironmentObject var viewModel: SettingsViewModel @@ -28,13 +29,10 @@ struct SettingsPrivacyProView: View { var body: some View { Section(header: Text(UserText.settingsPrivacySection)) { SettingsCellView(label: UserText.settingsPProSubscribe, subtitle: UserText.settingsPProDescription) - NavigationLink(destination: SubscriptionFlowView()) { + NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { SettingsCellView(label: UserText.settingsPProLearnMore) } } - - } - } #endif diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 3a1bfce97b..e71ff6d488 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -35,7 +35,9 @@ struct SettingsView: View { SettingsPrivacyView() #if SUBSCRIPTION if viewModel.shouldShowPrivacyProCell { - SettingsPrivacyProView() + if #available(iOS 15, *) { + SettingsPrivacyProView() + } } #endif SettingsCustomizeView() From e4f92324567d0ca73208a523cd7f45fc40300b89 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 18:53:35 +0100 Subject: [PATCH 82/99] Update caption style --- DuckDuckGo/SettingsCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index b8c46a1bdb..f409d8f7c9 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -119,7 +119,7 @@ struct SettingsCellView: View, Identifiable { .foregroundColor(Color(designSystemColor: .textPrimary)) if let subtitleText = subtitle { Text(subtitleText) - .daxCaption() + .daxFootnoteRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) } }.fixedSize(horizontal: false, vertical: true) From b724c3560706cd587c79829cb09b3f600005a153 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 19:01:46 +0100 Subject: [PATCH 83/99] Added Debug flag --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ---- .../UserScripts/SubscriptionOptions.swift | 18 ------------------ .../PrivacyPro/Views/HeadlessWebView.swift | 7 ++++--- 3 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 DuckDuckGo/PrivacyPro/UserScripts/SubscriptionOptions.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f30a2718ff..a5a706579e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -785,7 +785,6 @@ D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */; }; D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */; }; D664C7CA2B289AA200CBFA76 /* SimpleUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */; }; - D664C7CB2B289AA200CBFA76 /* SubscriptionOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B22B289AA000CBFA76 /* SubscriptionOptions.swift */; }; D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */; }; D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B42B289AA000CBFA76 /* TestUserScript.swift */; }; D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; @@ -2422,7 +2421,6 @@ D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleUserScript.swift; sourceTree = ""; }; - D664C7B22B289AA000CBFA76 /* SubscriptionOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionOptions.swift; sourceTree = ""; }; D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; D664C7B42B289AA000CBFA76 /* TestUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUserScript.swift; sourceTree = ""; }; D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; @@ -4601,7 +4599,6 @@ isa = PBXGroup; children = ( D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, - D664C7B22B289AA000CBFA76 /* SubscriptionOptions.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, @@ -6808,7 +6805,6 @@ 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */, 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */, 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, - D664C7CB2B289AA200CBFA76 /* SubscriptionOptions.swift in Sources */, C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionOptions.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionOptions.swift deleted file mode 100644 index 2ebe2a9b79..0000000000 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionOptions.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// SubscriptionOptions.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. -// diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index 0e12056015..2db5693f2a 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -44,13 +44,14 @@ struct HeadlessWebview: UIViewRepresentable { webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)" // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent - // DispatchQueue.main.asyncAfter(deadline: .now() + 10) { - webView.load(URLRequest(url: url)) - // } + webView.load(URLRequest(url: url)) + +#if DEBUG if #available(iOS 16.4, *) { webView.isInspectable = true } +#endif return webView } From 224c68316b169168afbfc9920886c945cd6083c2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 19:16:46 +0100 Subject: [PATCH 84/99] Use a custom view to render Privacy Pro Cells --- DuckDuckGo/SettingsPrivacyProView.swift | 22 ++++++++++++++++++++-- DuckDuckGo/UserText.swift | 7 +++---- DuckDuckGo/en.lproj/Localizable.strings | 7 ++++--- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift index bbedc8982c..a13a726001 100644 --- a/DuckDuckGo/SettingsPrivacyProView.swift +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -26,11 +26,29 @@ struct SettingsPrivacyProView: View { @EnvironmentObject var viewModel: SettingsViewModel + private var privacyProDescriptionView: some View { + VStack(alignment: .leading) { + Text(UserText.settingsPProSubscribe).daxBodyRegular() + Group { + Text(UserText.settingsPProDescription).daxFootnoteRegular().padding(.bottom, 5) + Text(UserText.settingsPProFeatures).daxFootnoteRegular() + }.foregroundColor(Color(designSystemColor: .textSecondary)) + } + } + + private var learnMoreView: some View { + Text(UserText.settingsPProLearnMore) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent)) + } + var body: some View { Section(header: Text(UserText.settingsPrivacySection)) { - SettingsCellView(label: UserText.settingsPProSubscribe, subtitle: UserText.settingsPProDescription) + + SettingsCustomCell(content: { privacyProDescriptionView }) + NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { - SettingsCellView(label: UserText.settingsPProLearnMore) + SettingsCustomCell(content: { learnMoreView }) } } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 34886fc99e..8b8eb83262 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -951,14 +951,13 @@ But if you *do* want a peek under the hood, you can find more information about // Privacy Pro Section public static let settingsPProSection = NSLocalizedString("settings.ppro", value: "Privacy Pro", comment: "Product name for the subscription bundle") public static let settingsPProSubscribe = NSLocalizedString("settings.ppro.subscribe", value: "Subscribe to Privacy Pro", comment: "Call to action title for Privacy Pro") - public static let settingsPProDescription = NSLocalizedString("settings.ppro.description", value: + public static let settingsPProDescription = NSLocalizedString("settings.ppro.description", value:"More seamless privacy with three new protections, including:", comment: "Privacy pro description subtext") + public static let settingsPProFeatures = NSLocalizedString("settings.ppro.features", value: """ - More seamless privacy with three new protections, including: - • VPN (Virtual Private Network) • Personal Information Removal • Identity Theft Restoration - """, comment: "about page") + """, comment: "Privacy pro features list") public static let settingsPProLearnMore = NSLocalizedString("settings.ppro.learn.more", value: "Learn More", comment: "Learn more button text for privacy pro") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 628f156c8b..2771af7e10 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1813,10 +1813,11 @@ But if you *do* want a peek under the hood, you can find more information about /* Product name for the subscription bundle */ "settings.ppro" = "Privacy Pro"; -/* about page */ -"settings.ppro.description" = "More seamless privacy with three new protections, including: +/* Privacy pro description subtext */ +"settings.ppro.description" = "More seamless privacy with three new protections, including:"; - • VPN (Virtual Private Network) +/* Privacy pro features list */ +"settings.ppro.features" = " • VPN (Virtual Private Network) • Personal Information Removal • Identity Theft Restoration"; From 70d03554d9ac36104178dca2eb28da3b8ff8b01d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 19:17:59 +0100 Subject: [PATCH 85/99] Section title is now Privacy Pro --- DuckDuckGo/SettingsPrivacyProView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift index a13a726001..25dcbdd245 100644 --- a/DuckDuckGo/SettingsPrivacyProView.swift +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -43,7 +43,7 @@ struct SettingsPrivacyProView: View { } var body: some View { - Section(header: Text(UserText.settingsPrivacySection)) { + Section(header: Text(UserText.settingsPProSection)) { SettingsCustomCell(content: { privacyProDescriptionView }) From 8ccd30f88ae2112ac6af44d0f73ca42f559c64ff Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 12 Dec 2023 19:23:56 +0100 Subject: [PATCH 86/99] Remove subscription flag from Debug version --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a5a706579e..22485df30c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8307,7 +8307,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; From 1735eb6388540c4e8a90741d04740300e921931a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 13 Dec 2023 14:27:29 +0100 Subject: [PATCH 87/99] Update Subscription Code from macOS --- DuckDuckGo.xcodeproj/project.pbxproj | 195 ++++++++++-------- .../PurchaseFlows/AppStoreRestoreFlow.swift | 70 ------- .../Subscription/AccountManager.swift | 35 ++-- .../AppStoreAccountManagementFlow.swift | 0 .../AppStore}/AppStorePurchaseFlow.swift | 22 +- .../Flows/AppStore/AppStoreRestoreFlow.swift | 90 ++++++++ .../Flows}/PurchaseFlow.swift | 0 .../Flows/Stripe/StripePurchaseFlow.swift | 91 ++++++++ .../Subscription/PurchaseManager.swift | 26 +-- .../Services/SubscriptionService.swift | 35 +++- .../SubscriptionPurchaseEnvironment.swift | 58 ++++++ .../URL+Subscription.swift | 9 +- .../PrivacyPro/SubscriptionUserText.swift | 27 --- ...scriptionPagesUseSubscriptionFeature.swift | 88 +++++--- .../ViewModel/SubscriptionFlowViewModel.swift | 2 +- .../PrivacyPro/Views/HeadlessWebView.swift | 8 +- DuckDuckGo/SettingsPrivacyProView.swift | 13 +- DuckDuckGo/SettingsViewModel.swift | 19 ++ DuckDuckGo/en.lproj/Localizable.strings | 3 - LocalPackages/Subscription/.gitignore | 8 + LocalPackages/Subscription/Package.resolved | 104 ++++++++++ LocalPackages/Subscription/Package.swift | 27 +++ .../SubscriptionTests/SubscriptionTests.swift | 12 ++ 23 files changed, 658 insertions(+), 284 deletions(-) delete mode 100644 DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift rename DuckDuckGo/PrivacyPro/{PurchaseFlows => Subscription/Flows/AppStore}/AppStoreAccountManagementFlow.swift (100%) rename DuckDuckGo/PrivacyPro/{PurchaseFlows => Subscription/Flows/AppStore}/AppStorePurchaseFlow.swift (80%) create mode 100644 DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift rename DuckDuckGo/PrivacyPro/{PurchaseFlows => Subscription/Flows}/PurchaseFlow.swift (100%) create mode 100644 DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift create mode 100644 DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift rename DuckDuckGo/PrivacyPro/{Extensions => Subscription}/URL+Subscription.swift (87%) delete mode 100644 DuckDuckGo/PrivacyPro/SubscriptionUserText.swift create mode 100644 LocalPackages/Subscription/.gitignore create mode 100644 LocalPackages/Subscription/Package.resolved create mode 100644 LocalPackages/Subscription/Package.swift create mode 100644 LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 22485df30c..aee2eb2d72 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -766,21 +766,7 @@ D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; D664C7B62B289AA200CBFA76 /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */; }; D664C7B72B289AA200CBFA76 /* PrivacyPro.storekit in Resources */ = {isa = PBXBuildFile; fileRef = D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */; }; - D664C7B82B289AA200CBFA76 /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7972B289AA000CBFA76 /* URL+Subscription.swift */; }; D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */; }; - D664C7BA2B289AA200CBFA76 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79A2B289AA000CBFA76 /* Logging.swift */; }; - D664C7BB2B289AA200CBFA76 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79B2B289AA000CBFA76 /* AccountManager.swift */; }; - D664C7BC2B289AA200CBFA76 /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */; }; - D664C7BD2B289AA200CBFA76 /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C79E2B289AA000CBFA76 /* AccountStorage.swift */; }; - D664C7BE2B289AA200CBFA76 /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */; }; - D664C7BF2B289AA200CBFA76 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A12B289AA000CBFA76 /* APIService.swift */; }; - D664C7C02B289AA200CBFA76 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A22B289AA000CBFA76 /* AuthService.swift */; }; - D664C7C12B289AA200CBFA76 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */; }; - D664C7C22B289AA200CBFA76 /* SubscriptionUserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */; }; - D664C7C32B289AA200CBFA76 /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */; }; - D664C7C42B289AA200CBFA76 /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */; }; - D664C7C52B289AA200CBFA76 /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */; }; - D664C7C62B289AA200CBFA76 /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */; }; D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */; }; D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */; }; D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */; }; @@ -790,6 +776,21 @@ D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; }; D69FBF762B28BE3600B505F1 /* SettingsPrivacyProView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */; }; + D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8B2B291CA90054390C /* URL+Subscription.swift */; }; + D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */; }; + D6D12CA12B291CA90054390C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8D2B291CA90054390C /* Logging.swift */; }; + D6D12CA22B291CA90054390C /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8E2B291CA90054390C /* AccountManager.swift */; }; + D6D12CA32B291CAA0054390C /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */; }; + D6D12CA42B291CAA0054390C /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C912B291CA90054390C /* AccountStorage.swift */; }; + D6D12CA52B291CAA0054390C /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */; }; + D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */; }; + D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */; }; + D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C972B291CA90054390C /* PurchaseFlow.swift */; }; + D6D12CA92B291CAA0054390C /* StripePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */; }; + D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9B2B291CA90054390C /* SubscriptionService.swift */; }; + D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9C2B291CA90054390C /* APIService.swift */; }; + D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9D2B291CA90054390C /* AuthService.swift */; }; + D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -2402,21 +2403,7 @@ D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PrivacyPro.storekit; sourceTree = ""; }; - D664C7972B289AA000CBFA76 /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; - D664C79A2B289AA000CBFA76 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - D664C79B2B289AA000CBFA76 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; - D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; - D664C79E2B289AA000CBFA76 /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; - D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; - D664C7A12B289AA000CBFA76 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; - D664C7A22B289AA000CBFA76 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; - D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; - D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionUserText.swift; sourceTree = ""; }; - D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; - D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; - D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; - D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; @@ -2426,6 +2413,21 @@ D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; D664C7DC2B28A02800CBFA76 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyProView.swift; sourceTree = ""; }; + D6D12C8B2B291CA90054390C /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; + D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPurchaseEnvironment.swift; sourceTree = ""; }; + D6D12C8D2B291CA90054390C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D6D12C8E2B291CA90054390C /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; + D6D12C912B291CA90054390C /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; + D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; + D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; + D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; + D6D12C972B291CA90054390C /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; + D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StripePurchaseFlow.swift; sourceTree = ""; }; + D6D12C9B2B291CA90054390C /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; + D6D12C9C2B291CA90054390C /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; + D6D12C9D2B291CA90054390C /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -3899,9 +3901,9 @@ 8590CB66268A2E520089F6BF /* RootDebugViewController.swift */, 8590CB68268A4E190089F6BF /* DebugEtagStorage.swift */, 1EDE39D12705D4A100C99C72 /* FileSizeDebugViewController.swift */, + 983D71B02A286E810072E26D /* SyncDebugViewController.swift */, C18ED43B2AB8364400BF3805 /* FileTextPreviewDebugViewController.swift */, F46FEC5627987A5F0061D9DF /* KeychainItemsDebugViewController.swift */, - 983D71B02A286E810072E26D /* SyncDebugViewController.swift */, EE72CA842A862D000043B5B3 /* NetworkProtectionDebugViewController.swift */, ); name = Debug; @@ -4498,14 +4500,10 @@ D664C7922B289AA000CBFA76 /* PrivacyPro */ = { isa = PBXGroup; children = ( - D664C7932B289AA000CBFA76 /* ViewModel */, + D6D12C8A2B291CA90054390C /* Subscription */, D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */, + D664C7932B289AA000CBFA76 /* ViewModel */, D664C7962B289AA000CBFA76 /* Extensions */, - D664C7992B289AA000CBFA76 /* Subscription */, - D664C7A42B289AA000CBFA76 /* Purchase */, - D664C7A52B289AA000CBFA76 /* SubscriptionUserText.swift */, - D664C7A62B289AA000CBFA76 /* Account */, - D664C7A72B289AA000CBFA76 /* PurchaseFlows */, D664C7AC2B289AA000CBFA76 /* Views */, D664C7B02B289AA000CBFA76 /* UserScripts */, ); @@ -4523,87 +4521,92 @@ D664C7962B289AA000CBFA76 /* Extensions */ = { isa = PBXGroup; children = ( - D664C7972B289AA000CBFA76 /* URL+Subscription.swift */, D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, ); path = Extensions; sourceTree = ""; }; - D664C7992B289AA000CBFA76 /* Subscription */ = { + D664C7AC2B289AA000CBFA76 /* Views */ = { isa = PBXGroup; children = ( - D664C79A2B289AA000CBFA76 /* Logging.swift */, - D664C79B2B289AA000CBFA76 /* AccountManager.swift */, - D664C79C2B289AA000CBFA76 /* AccountStorage */, - D664C79F2B289AA000CBFA76 /* Services */, - D664C7A32B289AA000CBFA76 /* PurchaseManager.swift */, + D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, + D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, + D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */, ); - path = Subscription; + path = Views; sourceTree = ""; }; - D664C79C2B289AA000CBFA76 /* AccountStorage */ = { + D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D664C79D2B289AA000CBFA76 /* AccountKeychainStorage.swift */, - D664C79E2B289AA000CBFA76 /* AccountStorage.swift */, + D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, + D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, + D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, ); - path = AccountStorage; + path = UserScripts; sourceTree = ""; }; - D664C79F2B289AA000CBFA76 /* Services */ = { + D6D12C8A2B291CA90054390C /* Subscription */ = { isa = PBXGroup; children = ( - D664C7A02B289AA000CBFA76 /* SubscriptionService.swift */, - D664C7A12B289AA000CBFA76 /* APIService.swift */, - D664C7A22B289AA000CBFA76 /* AuthService.swift */, + D6D12C8B2B291CA90054390C /* URL+Subscription.swift */, + D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */, + D6D12C8D2B291CA90054390C /* Logging.swift */, + D6D12C8E2B291CA90054390C /* AccountManager.swift */, + D6D12C8F2B291CA90054390C /* AccountStorage */, + D6D12C922B291CA90054390C /* Flows */, + D6D12C9A2B291CA90054390C /* Services */, + D6D12C9E2B291CA90054390C /* PurchaseManager.swift */, ); - path = Services; + path = Subscription; sourceTree = ""; }; - D664C7A42B289AA000CBFA76 /* Purchase */ = { + D6D12C8F2B291CA90054390C /* AccountStorage */ = { isa = PBXGroup; children = ( + D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */, + D6D12C912B291CA90054390C /* AccountStorage.swift */, ); - path = Purchase; + path = AccountStorage; sourceTree = ""; }; - D664C7A62B289AA000CBFA76 /* Account */ = { + D6D12C922B291CA90054390C /* Flows */ = { isa = PBXGroup; children = ( + D6D12C932B291CA90054390C /* AppStore */, + D6D12C972B291CA90054390C /* PurchaseFlow.swift */, + D6D12C982B291CA90054390C /* Stripe */, ); - path = Account; + path = Flows; sourceTree = ""; }; - D664C7A72B289AA000CBFA76 /* PurchaseFlows */ = { + D6D12C932B291CA90054390C /* AppStore */ = { isa = PBXGroup; children = ( - D664C7A82B289AA000CBFA76 /* AppStorePurchaseFlow.swift */, - D664C7A92B289AA000CBFA76 /* AppStoreRestoreFlow.swift */, - D664C7AA2B289AA000CBFA76 /* AppStoreAccountManagementFlow.swift */, - D664C7AB2B289AA000CBFA76 /* PurchaseFlow.swift */, + D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */, + D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */, + D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */, ); - path = PurchaseFlows; + path = AppStore; sourceTree = ""; }; - D664C7AC2B289AA000CBFA76 /* Views */ = { + D6D12C982B291CA90054390C /* Stripe */ = { isa = PBXGroup; children = ( - D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, - D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, - D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */, + D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */, ); - path = Views; + path = Stripe; sourceTree = ""; }; - D664C7B02B289AA000CBFA76 /* UserScripts */ = { + D6D12C9A2B291CA90054390C /* Services */ = { isa = PBXGroup; children = ( - D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, - D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, - D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, - D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, + D6D12C9B2B291CA90054390C /* SubscriptionService.swift */, + D6D12C9C2B291CA90054390C /* APIService.swift */, + D6D12C9D2B291CA90054390C /* AuthService.swift */, ); - path = UserScripts; + path = Services; sourceTree = ""; }; D6E83C322B1F1279006C8AFB /* Sections */ = { @@ -6442,6 +6445,7 @@ 1E24295E293F57FA00584836 /* LottieView.swift in Sources */, 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */, 4BB697A42B1D99C4003699B5 /* VPNWaitlistActivationDateStore.swift in Sources */, + D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */, 853C5F5B21BFF0AE001F7A05 /* HomeCollectionView.swift in Sources */, 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, @@ -6453,6 +6457,7 @@ B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, + D6D12CA92B291CAA0054390C /* StripePurchaseFlow.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, @@ -6466,11 +6471,10 @@ F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, + D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */, - D664C7BB2B289AA200CBFA76 /* AccountManager.swift in Sources */, 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */, - D664C7BE2B289AA200CBFA76 /* SubscriptionService.swift in Sources */, 4BBBBA922B03291700D965DA /* VPNWaitlistUserText.swift in Sources */, F4E1936625AF722F001D2666 /* HighlightCutOutView.swift in Sources */, 1E162605296840D80004127F /* Triangle.swift in Sources */, @@ -6480,7 +6484,6 @@ 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */, F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */, 3151F0F02735802800226F58 /* VoiceSearchViewController.swift in Sources */, - D664C7C02B289AA200CBFA76 /* AuthService.swift in Sources */, 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */, F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, @@ -6488,7 +6491,9 @@ D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, + D6D12CA42B291CAA0054390C /* AccountStorage.swift in Sources */, D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, + D6D12CA32B291CAA0054390C /* AccountKeychainStorage.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, @@ -6528,10 +6533,10 @@ 020108A929A7C1CD00644F9D /* AppTrackerImageCache.swift in Sources */, 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */, 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, + D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */, C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, - D664C7C12B289AA200CBFA76 /* PurchaseManager.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, 020108A329A561C300644F9D /* AppTPActivityView.swift in Sources */, @@ -6556,6 +6561,7 @@ 85C297042476C1FD0063A335 /* DaxDialogsSettings.swift in Sources */, 8505836F219F424500ED4EDB /* UIViewExtension.swift in Sources */, 8505836E219F424500ED4EDB /* RoundedRectangleView.swift in Sources */, + D6D12CA12B291CA90054390C /* Logging.swift in Sources */, EE8594992A44791C008A6D06 /* NetworkProtectionTunnelController.swift in Sources */, 1EEF123F2850A68A003DDE57 /* PrivacyInfoContainerView.swift in Sources */, F4B0B796252CB35700830156 /* OnboardingWidgetsDetailsViewController.swift in Sources */, @@ -6594,6 +6600,7 @@ 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, + D6D12CA22B291CA90054390C /* AccountManager.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */, 1EEC460627A9499600E75FCB /* DownloadsList.swift in Sources */, @@ -6601,12 +6608,14 @@ 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, + D6D12CA52B291CAA0054390C /* AppStorePurchaseFlow.swift in Sources */, F4F6DFB426E6B63700ED7E12 /* BookmarkFolderCell.swift in Sources */, 851B12CC22369931004781BC /* AtbAndVariantCleanup.swift in Sources */, 85F2FFCF2211F8E5006BB258 /* TabSwitcherViewController+KeyCommands.swift in Sources */, 3157B43327F497E90042D3D7 /* SaveLoginView.swift in Sources */, F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */, 0290472529E8496A0008FE3C /* AppTPActivityIconView.swift in Sources */, + D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */, D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */, EE458D142ABB652900FC651A /* NetworkProtectionDebugUtilities.swift in Sources */, 8528AE7C212EF4A200D0BD74 /* AppRatingPrompt.swift in Sources */, @@ -6622,7 +6631,6 @@ EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */, EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */, B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */, - D664C7C42B289AA200CBFA76 /* AppStoreRestoreFlow.swift in Sources */, 981FED6E22025151008488D7 /* BlankSnapshotViewController.swift in Sources */, 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */, 851B128822200575004781BC /* Onboarding.swift in Sources */, @@ -6649,6 +6657,7 @@ 4BBBBA8D2B031B4200D965DA /* VPNWaitlistDebugViewController.swift in Sources */, C160544129D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift in Sources */, 02A54A9A2A094A17000C8FED /* AppTPHomeView.swift in Sources */, + D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */, 31C70B5528045E3500FB6AD1 /* SecureVaultErrorReporter.swift in Sources */, F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */, 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, @@ -6666,9 +6675,9 @@ 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, + D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */, 989B337522D7EF2100437824 /* EmptyCollectionReusableView.swift in Sources */, 8524CC94246C5C8900E59D45 /* DaxDialogViewController.swift in Sources */, - D664C7BA2B289AA200CBFA76 /* Logging.swift in Sources */, F42EF9312614BABE00101FB9 /* ActionSheetDaxDialogViewController.swift in Sources */, EEC02C142B0519DE0045CE11 /* NetworkProtectionVPNLocationViewModel.swift in Sources */, F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */, @@ -6686,12 +6695,10 @@ F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */, 85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */, - D664C7BD2B289AA200CBFA76 /* AccountStorage.swift in Sources */, 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, - D664C7C52B289AA200CBFA76 /* AppStoreAccountManagementFlow.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */, @@ -6700,6 +6707,7 @@ 37FCAAB429914C77000E420A /* WindowsWaitlistViewController.swift in Sources */, 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */, 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */, + D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */, 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */, 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, @@ -6752,7 +6760,6 @@ 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */, 980891A32237146B00313A70 /* Feedback.swift in Sources */, F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, - D664C7C22B289AA200CBFA76 /* SubscriptionUserText.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, @@ -6767,8 +6774,8 @@ 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */, 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */, F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */, - D664C7C32B289AA200CBFA76 /* AppStorePurchaseFlow.swift in Sources */, 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */, + D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */, 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, @@ -6791,7 +6798,6 @@ 85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */, F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */, 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, - D664C7C62B289AA200CBFA76 /* PurchaseFlow.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, @@ -6808,7 +6814,6 @@ C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, - D664C7BF2B289AA200CBFA76 /* APIService.swift in Sources */, 314C92B827C3DD660042EC96 /* QuickLookPreviewView.swift in Sources */, F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */, 020108AE29A7F91600644F9D /* AppTPTrackerCell.swift in Sources */, @@ -6857,11 +6862,10 @@ D6E83C622B23298B006C8AFB /* SettingsMoreView.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, - D664C7BC2B289AA200CBFA76 /* AccountKeychainStorage.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, + D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */, D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, - D664C7B82B289AA200CBFA76 /* URL+Subscription.swift in Sources */, C1B7B52428941F2A0098FD6A /* RemoteMessageRequest.swift in Sources */, EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, @@ -8208,6 +8212,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8237,6 +8242,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8384,6 +8390,7 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8391,6 +8398,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; SWIFT_VERSION = 5.0; }; name = Debug; @@ -8406,6 +8414,7 @@ CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8539,6 +8548,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8571,6 +8581,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8856,7 +8867,12 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8881,6 +8897,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8909,6 +8926,7 @@ CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9249,6 +9267,7 @@ DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9275,6 +9294,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9307,6 +9327,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift deleted file mode 100644 index 6996c2a111..0000000000 --- a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreRestoreFlow.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// AppStorePurchaseFlow.swift -// -// 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 StoreKit - -@available(macOS 12.0, iOS 15.0, *) -public final class AppStoreRestoreFlow { - - public typealias Success = (externalID: String, isActive: Bool) - - public enum Error: Swift.Error { - case missingAccountOrTransactions - case pastTransactionAuthenticationFailure - case accessTokenObtainingError -// case subscriptionExpired - case somethingWentWrong - } - - public static func restoreAccountFromPastPurchase() async -> Result { - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } - - // Do the store login to get short-lived token - let authToken: String - - switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { - case .success(let response): - authToken = response.authToken - case .failure: - return .failure(.pastTransactionAuthenticationFailure) - } - - let externalID: String - - switch await AccountManager().exchangeAndStoreTokens(with: authToken) { - case .success(let existingExternalID): - externalID = existingExternalID - case .failure: - return .failure(.accessTokenObtainingError) - } - - let accessToken = AccountManager().accessToken ?? "" - var isActive = false - - switch await SubscriptionService.getSubscriptionInfo(token: accessToken) { - case .success(let response): - isActive = response.status != "Expired" && response.status != "Inactive" - case .failure: - return .failure(.somethingWentWrong) - } - - // TOOD: Fix this by probably splitting/changing result of exchangeAndStoreTokens - return .success((externalID: externalID, isActive: isActive)) - } -} diff --git a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift index fb8261ca46..655742298d 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift @@ -176,44 +176,35 @@ public class AccountManager { } } - @discardableResult - public func exchangeAndStoreTokens(with authToken: String) async -> Result { - // Exchange short-lived auth token to a long-lived access token - let accessToken: String + public func exchangeAuthTokenToAccessToken(_ authToken: String) async -> Result { switch await AuthService.getAccessToken(token: authToken) { case .success(let response): - accessToken = response.accessToken + return .success(response.accessToken) case .failure(let error): os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) return .failure(error) } + } + + public typealias AccountDetails = (email: String?, externalID: String) - // Fetch entitlements and account details and store the data + public func fetchAccountDetails(with accessToken: String) async -> Result { switch await AuthService.validateToken(accessToken: accessToken) { case .success(let response): - self.storeAuthToken(token: authToken) - self.storeAccount(token: accessToken, - email: response.account.email, - externalID: response.account.externalID) - - return .success(response.account.externalID) - + return .success(AccountDetails(email: response.account.email, externalID: response.account.externalID)) case .failure(let error): os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) return .failure(error) } } - public func refreshAccountData() async { - guard let accessToken else { return } + public func checkSubscriptionState() async { + guard let token = accessToken else { return } - switch await AuthService.validateToken(accessToken: accessToken) { - case .success(let response): - self.storeAccount(token: accessToken, - email: response.account.email, - externalID: response.account.externalID) - case .failure: - break + if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { + if !response.isSubscriptionActive { + signOut() + } } } } diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreAccountManagementFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/PurchaseFlows/AppStoreAccountManagementFlow.swift rename to DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift similarity index 80% rename from DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift rename to DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift index 666ff9e094..2d4497cb00 100644 --- a/DuckDuckGo/PrivacyPro/PurchaseFlows/AppStorePurchaseFlow.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift @@ -55,22 +55,30 @@ public final class AppStorePurchaseFlow { } public static func purchaseSubscription(with subscriptionIdentifier: String, emailAccessToken: String?) async -> Result { + let accountManager = AccountManager() let externalID: String // Check for past transactions most recent - switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { - case .success(let success): - guard !success.isActive else { return .failure(.activeSubscriptionAlreadyPresent)} - externalID = success.externalID + case .success: + return .failure(.activeSubscriptionAlreadyPresent) case .failure(let error): switch error { - case .missingAccountOrTransactions, .pastTransactionAuthenticationFailure: + case .subscriptionExpired(let expiredAccountDetails): + externalID = expiredAccountDetails.externalID + accountManager.storeAuthToken(token: expiredAccountDetails.authToken) + accountManager.storeAccount(token: expiredAccountDetails.accessToken, email: expiredAccountDetails.email, externalID: expiredAccountDetails.externalID) + case .missingAccountOrTransactions: // No history, create new account switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { case .success(let response): externalID = response.externalID - await AccountManager().exchangeAndStoreTokens(with: response.authToken) + + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(response.authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: response.authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } case .failure: return .failure(.accountCreationFailed) } @@ -93,7 +101,7 @@ public final class AppStorePurchaseFlow { @discardableResult public static func completeSubscriptionPurchase() async -> Result { - let result = await checkForEntitlements(wait: 2.0, retry: 15) + let result = await checkForEntitlements(wait: 2.0, retry: 10) return result ? .success(PurchaseUpdate(type: "completed")) : .failure(.missingEntitlements) } diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift new file mode 100644 index 0000000000..a23a3255f7 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift @@ -0,0 +1,90 @@ +// +// AppStorePurchaseFlow.swift +// +// 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 StoreKit + +@available(macOS 12.0, iOS 15.0, *) +public final class AppStoreRestoreFlow { + + // swiftlint:disable:next large_tuple + public typealias RestoredAccountDetails = (authToken: String, accessToken: String, externalID: String, email: String?) + + public enum Error: Swift.Error { + case missingAccountOrTransactions + case pastTransactionAuthenticationError + case failedToObtainAccessToken + case failedToFetchAccountDetails + case failedToFetchSubscriptionDetails + case subscriptionExpired(accountDetails: RestoredAccountDetails) + case somethingWentWrong + } + + public static func restoreAccountFromPastPurchase() async -> Result { + guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } + + let accountManager = AccountManager() + + // Do the store login to get short-lived token + let authToken: String + + switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { + case .success(let response): + authToken = response.authToken + case .failure: + return .failure(.pastTransactionAuthenticationError) + } + + let accessToken: String + let email: String? + let externalID: String + + switch await accountManager.exchangeAuthTokenToAccessToken(authToken) { + case .success(let exchangedAccessToken): + accessToken = exchangedAccessToken + case .failure: + return .failure(.failedToObtainAccessToken) + } + + switch await accountManager.fetchAccountDetails(with: accessToken) { + case .success(let accountDetails): + email = accountDetails.email + externalID = accountDetails.externalID + case .failure: + return .failure(.failedToFetchAccountDetails) + } + + var isSubscriptionActive = false + + switch await SubscriptionService.getSubscriptionDetails(token: accessToken) { + case .success(let response): + isSubscriptionActive = response.isSubscriptionActive + case .failure: + return .failure(.somethingWentWrong) + } + + if isSubscriptionActive { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: email, externalID: externalID) + return .success(()) + } else { + let details = RestoredAccountDetails(authToken: authToken, accessToken: accessToken, externalID: externalID, email: email) + return .failure(.subscriptionExpired(accountDetails: details)) + } + } +} diff --git a/DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift similarity index 100% rename from DuckDuckGo/PrivacyPro/PurchaseFlows/PurchaseFlow.swift rename to DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift new file mode 100644 index 0000000000..67070f383b --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift @@ -0,0 +1,91 @@ +// +// StripePurchaseFlow.swift +// +// 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 StoreKit + +public final class StripePurchaseFlow { + + public enum Error: Swift.Error { + case noProductsFound + case accountCreationFailed + } + + public static func subscriptionOptions() async -> Result { + + guard case let .success(products) = await SubscriptionService.getProducts(), !products.isEmpty else { return .failure(.noProductsFound) } + + let currency = products.first?.currency ?? "USD" + + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.locale = Locale(identifier: "en_US@currency=\(currency)") + + let options: [SubscriptionOption] = products.map { + var displayPrice = "\($0.price) \($0.currency)" + + if let price = Float($0.price), let formattedPrice = formatter.string(from: price as NSNumber) { + displayPrice = formattedPrice + } + + let cost = SubscriptionOptionCost(displayPrice: displayPrice, recurrence: $0.billingPeriod.lowercased()) + + return SubscriptionOption(id: $0.productId, + cost: cost) + } + + let features = SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) } + + return .success(SubscriptionOptions(platform: SubscriptionPlatformName.stripe.rawValue, + options: options, + features: features)) + } + + public static func prepareSubscriptionPurchase(emailAccessToken: String?) async -> Result { + + var authToken: String = "" + + switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { + case .success(let response): + authToken = response.authToken + AccountManager().storeAuthToken(token: authToken) + case .failure: + return .failure(.accountCreationFailed) + } + + return .success(PurchaseUpdate(type: "redirect", token: authToken)) + } + + public static func completeSubscriptionPurchase() async { + let accountManager = AccountManager() + + if let authToken = accountManager.authToken { + print("Exchanging token") + + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + } + + if #available(macOS 12.0, iOS 15.0, *) { + await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 5) + } + } +} diff --git a/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift index 3c0cf3fe24..fd4032b38b 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift @@ -40,14 +40,9 @@ enum PurchaseManagerError: Error { @available(macOS 12.0, iOS 15.0, *) public final class PurchaseManager: ObservableObject { - static let productIdentifiers = ["subscription.1week", - "subscription.1month", - "subscription.1year", - "review.subscription.1week", - "review.subscription.1month", - "review.subscription.1year", - "ios.subscription.1month", - "ios.subscription.1year"] + static let productIdentifiers = ["ios.subscription.1month", "ios.subscription.1year", + "subscription.1week", "subscription.1month", "subscription.1year", + "review.subscription.1week", "review.subscription.1month", "review.subscription.1year"] public static let shared = PurchaseManager() @@ -121,21 +116,6 @@ public final class PurchaseManager: ObservableObject { } } - @MainActor - func fetchAvailableProducts() async -> [Product] { - print(" -- [PurchaseManager] fetchAvailableProducts()") - - do { - let availableProducts = try await Product.products(for: Self.productIdentifiers) - print(" -- [PurchaseManager] fetchAvailableProducts(): fetched \(availableProducts.count) products") - - return availableProducts - } catch { - print("Error fetching available products: \(error)") - return [] - } - } - @MainActor public func updatePurchasedProducts() async { print(" -- [PurchaseManager] updatePurchasedProducts()") diff --git a/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift index 877a45f245..6c3ed27db1 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift @@ -30,24 +30,39 @@ public struct SubscriptionService: APIService { // MARK: - - public static func getSubscriptionInfo(token: String) async -> Result { - await executeAPICall(method: "GET", endpoint: "subscription", headers: makeAuthorizationHeader(for: token)) + public static func getSubscriptionDetails(token: String) async -> Result { + let result: Result = await executeAPICall(method: "GET", endpoint: "subscription", headers: makeAuthorizationHeader(for: token)) + + switch result { + case .success(let response): + cachedSubscriptionDetailsResponse = response + case .failure: + cachedSubscriptionDetailsResponse = nil + } + + return result } - public struct GetSubscriptionInfoResponse: Decodable { + public struct GetSubscriptionDetailsResponse: Decodable { public let productId: String public let startedAt: Date public let expiresOrRenewsAt: Date public let platform: String public let status: String + + public var isSubscriptionActive: Bool { + status.lowercased() != "expired" && status.lowercased() != "inactive" + } } + public static var cachedSubscriptionDetailsResponse: GetSubscriptionDetailsResponse? + // MARK: - public static func getProducts() async -> Result { await executeAPICall(method: "GET", endpoint: "products") } - + public typealias GetProductsResponse = [GetProductsItem] public struct GetProductsItem: Decodable { @@ -58,4 +73,16 @@ public struct SubscriptionService: APIService { public let currency: String } + // MARK: - + + public static func getCustomerPortalURL(accessToken: String, externalID: String) async -> Result { + var headers = makeAuthorizationHeader(for: accessToken) + headers["externalAccountId"] = externalID + return await executeAPICall(method: "GET", endpoint: "checkout/portal", headers: headers) + } + + public struct GetCustomerPortalURLResponse: Decodable { + public let customerPortalUrl: String + } + } diff --git a/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift b/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift new file mode 100644 index 0000000000..60ac8c3e53 --- /dev/null +++ b/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift @@ -0,0 +1,58 @@ +// +// SubscriptionPurchaseEnvironment.swift +// +// 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 + +public final class SubscriptionPurchaseEnvironment { + + public enum Environment { + case appStore, stripe + } + + public static var current: Environment = .appStore { + didSet { + canPurchase = false + + switch current { + case .appStore: + setupForAppStore() + case .stripe: + setupForStripe() + } + } + } + + public static var canPurchase: Bool = false + + private static func setupForAppStore() { + if #available(macOS 12.0, iOS 15.0, *) { + Task { + await PurchaseManager.shared.updateAvailableProducts() + canPurchase = !PurchaseManager.shared.availableProducts.isEmpty + } + } + } + + private static func setupForStripe() { + Task { + if case let .success(products) = await SubscriptionService.getProducts() { + canPurchase = !products.isEmpty + } + } + } +} diff --git a/DuckDuckGo/PrivacyPro/Extensions/URL+Subscription.swift b/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift similarity index 87% rename from DuckDuckGo/PrivacyPro/Extensions/URL+Subscription.swift rename to DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift index dbb018b53e..78061d4a93 100644 --- a/DuckDuckGo/PrivacyPro/Extensions/URL+Subscription.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift @@ -19,7 +19,7 @@ import Foundation public extension URL { - + static var purchaseSubscription: URL { URL(string: "https://abrown.duckduckgo.com/subscriptions/welcome")! } @@ -28,7 +28,6 @@ public extension URL { URL(string: "https://duckduckgo.com/about")! } - // MARK: - Subscription Email static var activateSubscriptionViaEmail: URL { URL(string: "https://abrown.duckduckgo.com/subscriptions/activate")! @@ -41,4 +40,10 @@ public extension URL { static var manageSubscriptionEmail: URL { URL(string: "https://abrown.duckduckgo.com/subscriptions/manage")! } + + // MARK: - App Store app manage subscription URL + + static var manageSubscriptionsInAppStoreAppURL: URL { + URL(string: "macappstores://apps.apple.com/account/subscriptions")! + } } diff --git a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift b/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift deleted file mode 100644 index 14e5ba02d3..0000000000 --- a/DuckDuckGo/PrivacyPro/SubscriptionUserText.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// UserText.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 - -public struct SubscriptionUserText { - - public static let navigationTitle = NSLocalizedString("nagivation.title", value: "Privacy Pro", comment: "Navigation Bar Title for Feature") - - -} diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 09aed84e03..09c83c23e5 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -115,25 +115,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { await withTransactionInProgress { - let subscriptionOptions: [SubscriptionOption] - - if #available(iOS 15, *) { - let monthly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.monthly) }) - let yearly = PurchaseManager.shared.availableProducts.first(where: { $0.id.contains(ProductIDs.yearly) }) - - guard let monthly, let yearly else { return nil } - - subscriptionOptions = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: RecurrenceOptions.month)), - SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: RecurrenceOptions.year))] - } else { - return nil + if #available(iOS 15.0, *) { + switch await AppStorePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + return subscriptionOptions + case .failure: + // TODO: handle errors - no products found + return nil + } } - - let message = SubscriptionOptions(platform: Constants.os, - options: subscriptionOptions, - features: SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) }) - - return message + return nil } } @@ -154,20 +145,37 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec print("Selected: \(subscriptionSelection.id)") - let emailAccessToken = try? EmailManager().getToken() + // Trigger sign in pop-up + switch await PurchaseManager.shared.syncAppleIDAccount() { + case .success: + break + case .failure: + return nil + } + // Check for active subscriptions + if await PurchaseManager.hasActiveSubscription() { + print("hasActiveSubscription: TRUE") + // TODO: Present something here + // await WindowControllersManager.shared.lastKeyMainWindowController?.showSubscriptionFoundAlert(originalMessage: message) + return nil + } + + let emailAccessToken = try? EmailManager().getToken() + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { case .success: break - case .failure(let error): - print("Purchase failed: \(error)") + case .failure: return nil } - await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 15) - - DispatchQueue.main.async { - self.pushAction(method: .onPurchaseUpdate, webView: message.webView!, params: PurchaseUpdate(type: "completed")) + switch await AppStorePurchaseFlow.completeSubscriptionPurchase() { + case .success(let purchaseUpdate): + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) + case .failure: + // TODO: handle errors - missing entitlements on post purchase check + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) } } @@ -176,12 +184,31 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - // WIP + guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + return nil + } + + let authToken = subscriptionValues.token + let accountManager = AccountManager() + if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAuthToken(token: authToken) + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + return nil } func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { - // WIP + let accountManager = AccountManager() + if let accessToken = accountManager.accessToken, + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + } + + // TODO: Navigate back to settings + return nil } @@ -204,12 +231,15 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec return nil } + // MARK: Push actions + enum SubscribeActionName: String { case onPurchaseUpdate } - struct PurchaseUpdate: Codable { - let type: String + @MainActor + func pushPurchaseUpdate(originalMessage: WKScriptMessage, purchaseUpdate: PurchaseUpdate) async { + pushAction(method: .onPurchaseUpdate, webView: originalMessage.webView!, params: purchaseUpdate) } func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index addc18fdd9..a200f0aae8 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -30,7 +30,7 @@ final class SubscriptionFlowViewModel: ObservableObject { let purchaseManager: PurchaseManager let purchaseURL = URL.purchaseSubscription - let viewTitle = SubscriptionUserText.navigationTitle + let viewTitle = "Privacy Pro" @Published var transactionInProgress = false private var cancellables = Set() diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index 2db5693f2a..de2685dac4 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -44,14 +44,16 @@ struct HeadlessWebview: UIViewRepresentable { webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)" // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent - webView.load(URLRequest(url: url)) + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + webView.load(URLRequest(url: url)) + } -#if DEBUG +// #if DEBUG if #available(iOS 16.4, *) { webView.isInspectable = true } -#endif +// #endif return webView } diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift index 25dcbdd245..a006850ef1 100644 --- a/DuckDuckGo/SettingsPrivacyProView.swift +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -43,12 +43,13 @@ struct SettingsPrivacyProView: View { } var body: some View { - Section(header: Text(UserText.settingsPProSection)) { - - SettingsCustomCell(content: { privacyProDescriptionView }) - - NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { - SettingsCustomCell(content: { learnMoreView }) + if viewModel.canPurchaseSubscription { + Section(header: Text(UserText.settingsPProSection)) { + SettingsCustomCell(content: { privacyProDescriptionView }) + + NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { + SettingsCustomCell(content: { learnMoreView }) + } } } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 6d9e037855..93b7e67f25 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -81,6 +81,7 @@ final class SettingsViewModel: ObservableObject { var shouldShowNoMicrophonePermissionAlert: Bool = false var shouldShowDebugCell: Bool { return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild } var shouldShowPrivacyProCell: Bool { return featureFlagger.isFeatureOn(.privacyPro) } + @Published var canPurchaseSubscription = false var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION @@ -227,6 +228,11 @@ extension SettingsViewModel { version: versionProvider.versionAndBuildNumber ) setupSubscribers() +#if SUBSCRIPTION + if #available(iOS 15, *) { + Task { await setupSubscriptionEnvironment() } + } +#endif } private func firePixel(_ event: Pixel.Event) { @@ -243,6 +249,19 @@ extension SettingsViewModel { completion(true) } } + +#if SUBSCRIPTION + @available(iOS 15.0, *) + private func setupSubscriptionEnvironment() async { + await PurchaseManager.shared.updateAvailableProducts() + PurchaseManager.shared.$availableProducts + .receive(on: RunLoop.main) + .sink { [weak self] products in + self?.canPurchaseSubscription = !products.isEmpty + }.store(in: &cancellables) + + } +#endif #if NETWORK_PROTECTION private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 2771af7e10..9e603d7d54 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1378,9 +1378,6 @@ https://duckduckgo.com/mac"; /* No comment provided by engineer. */ "menu.button.hint" = "Browsing Menu"; -/* Navigation Bar Title for Feature */ -"nagivation.title" = "Privacy Pro"; - /* Title for back button in navigation bar */ "navbar.back-button.title" = "Back"; diff --git a/LocalPackages/Subscription/.gitignore b/LocalPackages/Subscription/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/Subscription/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/Subscription/Package.resolved b/LocalPackages/Subscription/Package.resolved new file mode 100644 index 0000000000..c4a8bbc9a0 --- /dev/null +++ b/LocalPackages/Subscription/Package.resolved @@ -0,0 +1,104 @@ +{ + "pins" : [ + { + "identity" : "bloom_cpp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/bloom_cpp.git", + "state" : { + "revision" : "8076199456290b61b4544bf2f4caf296759906a0", + "version" : "3.0.0" + } + }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "revision" : "4cf8e857cd78e15c64ba37839634970fc675947c", + "version" : "81.4.0" + } + }, + { + "identity" : "content-scope-scripts", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/content-scope-scripts", + "state" : { + "revision" : "aa279a3b006a0b1e009707311283c7fcaed24fb7", + "version" : "4.39.0" + } + }, + { + "identity" : "duckduckgo-autofill", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state" : { + "revision" : "6dd7d696d4e666cedb2f1890a46fe53615226646", + "version" : "8.4.2" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/GRDB.swift.git", + "state" : { + "revision" : "77d9a83191a74e319a5cfa27b0e3145d15607573", + "version" : "2.2.0" + } + }, + { + "identity" : "privacy-dashboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/privacy-dashboard", + "state" : { + "revision" : "51e2b46f413bf3ef18afefad631ca70f2c25ef70", + "version" : "1.4.0" + } + }, + { + "identity" : "punycodeswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gumob/PunycodeSwift.git", + "state" : { + "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b", + "version" : "2.1.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version" : "0.5.0" + } + }, + { + "identity" : "sync_crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/sync_crypto", + "state" : { + "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version" : "0.2.0" + } + }, + { + "identity" : "trackerradarkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "state" : { + "revision" : "4684440d03304e7638a2c8086895367e90987463", + "version" : "1.2.1" + } + }, + { + "identity" : "wireguard-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/wireguard-apple", + "state" : { + "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", + "version" : "1.1.1" + } + } + ], + "version" : 2 +} diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift new file mode 100644 index 0000000000..c65b6109c7 --- /dev/null +++ b/LocalPackages/Subscription/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Subscription", + platforms: [ .macOS(.v11), .iOS(.v14) ], + products: [ + .library( + name: "Subscription", + targets: ["Subscription"]), + ], + dependencies: [ + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "94.0.0"), + ], + targets: [ + .target( + name: "Subscription", + dependencies: [ + .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), + ]), + .testTarget( + name: "SubscriptionTests", + dependencies: ["Subscription"]), + ] +) diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift new file mode 100644 index 0000000000..32417fc73a --- /dev/null +++ b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import Subscription + +final class SubscriptionTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} From 61d4bea16581b84a4bfb63f100c1f5025191fe2a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 13 Dec 2023 17:50:55 +0100 Subject: [PATCH 88/99] Use a custom agent temporarily --- DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift index de2685dac4..164a19014f 100644 --- a/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift +++ b/DuckDuckGo/PrivacyPro/Views/HeadlessWebView.swift @@ -42,18 +42,15 @@ struct HeadlessWebview: UIViewRepresentable { // We're using the macOS agent as the config for iOS has not been deployed in test env webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)" - // webView.customUserAgent = DefaultUserAgentManager.duckDuckGoUserAgent - - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - webView.load(URLRequest(url: url)) - } + // DefaultUserAgentManager.shared.update(webView: webView, isDesktop: false, url: url) + webView.load(URLRequest(url: url)) -// #if DEBUG +#if DEBUG if #available(iOS 16.4, *) { webView.isInspectable = true } -// #endif +#endif return webView } From 66cc3b7d118f22e8612316f98563590cd2b80c6e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 13 Dec 2023 18:32:34 +0100 Subject: [PATCH 89/99] Initialize subscription environment in AppDelegate --- DuckDuckGo/AppDelegate.swift | 4 ++++ .../PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift | 1 - DuckDuckGo/SettingsViewModel.swift | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index a7b87702f1..89f2672916 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -356,6 +356,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { widgetRefreshModel.beginObservingVPNStatus() NetworkProtectionAccessController().refreshNetworkProtectionAccess() #endif + +#if SUBSCRIPTION + SubscriptionPurchaseEnvironment.current = .appStore +#endif return true } diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index a200f0aae8..8263998e8b 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -60,7 +60,6 @@ final class SubscriptionFlowViewModel: ObservableObject { func initializeViewData() async { await self.setupTransactionObserver() - await purchaseManager.updateAvailableProducts() } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 93b7e67f25..ca50913a05 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -81,7 +81,7 @@ final class SettingsViewModel: ObservableObject { var shouldShowNoMicrophonePermissionAlert: Bool = false var shouldShowDebugCell: Bool { return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild } var shouldShowPrivacyProCell: Bool { return featureFlagger.isFeatureOn(.privacyPro) } - @Published var canPurchaseSubscription = false + @Published var canPurchaseSubscription = SubscriptionPurchaseEnvironment.canPurchase var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION From 01c0fbe00dcddd8991f3a2d342cdae3d9c00c12c Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 13 Dec 2023 18:35:52 +0100 Subject: [PATCH 90/99] Move subscription init to a method --- DuckDuckGo/AppDelegate.swift | 8 +++++++- .../PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 89f2672916..550d62108e 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -358,7 +358,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif #if SUBSCRIPTION - SubscriptionPurchaseEnvironment.current = .appStore + setupSubscriptionsEnvironment() #endif return true @@ -402,6 +402,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }) } +#if SUBSCRIPTION + private func setupSubscriptionsEnvironment() { + SubscriptionPurchaseEnvironment.current = .appStore + } +#endif + func applicationDidBecomeActive(_ application: UIApplication) { guard !testing else { return } diff --git a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift index 8263998e8b..a5bf1c4e58 100644 --- a/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/PrivacyPro/ViewModel/SubscriptionFlowViewModel.swift @@ -30,7 +30,7 @@ final class SubscriptionFlowViewModel: ObservableObject { let purchaseManager: PurchaseManager let purchaseURL = URL.purchaseSubscription - let viewTitle = "Privacy Pro" + let viewTitle = UserText.settingsPProSection @Published var transactionInProgress = false private var cancellables = Set() From 264766dd4b24515f4250362ad6f3bb20eebdcc53 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 13 Dec 2023 18:37:32 +0100 Subject: [PATCH 91/99] Removed non-required scripts --- DuckDuckGo.xcodeproj/project.pbxproj | 12 ++----- .../UserScripts/SimpleUserScript.swift | 34 ------------------ .../UserScripts/TestUserScript.swift | 36 ------------------- 3 files changed, 2 insertions(+), 80 deletions(-) delete mode 100644 DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift delete mode 100644 DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index aee2eb2d72..13223d8df6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -770,9 +770,7 @@ D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */; }; D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */; }; D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */; }; - D664C7CA2B289AA200CBFA76 /* SimpleUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */; }; D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */; }; - D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B42B289AA000CBFA76 /* TestUserScript.swift */; }; D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; }; D69FBF762B28BE3600B505F1 /* SettingsPrivacyProView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */; }; @@ -2407,9 +2405,7 @@ D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; - D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleUserScript.swift; sourceTree = ""; }; D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; - D664C7B42B289AA000CBFA76 /* TestUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUserScript.swift; sourceTree = ""; }; D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; D664C7DC2B28A02800CBFA76 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; D69FBF752B28BE3600B505F1 /* SettingsPrivacyProView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyProView.swift; sourceTree = ""; }; @@ -4500,12 +4496,12 @@ D664C7922B289AA000CBFA76 /* PrivacyPro */ = { isa = PBXGroup; children = ( - D6D12C8A2B291CA90054390C /* Subscription */, D664C7952B289AA000CBFA76 /* PrivacyPro.storekit */, D664C7932B289AA000CBFA76 /* ViewModel */, - D664C7962B289AA000CBFA76 /* Extensions */, D664C7AC2B289AA000CBFA76 /* Views */, D664C7B02B289AA000CBFA76 /* UserScripts */, + D664C7962B289AA000CBFA76 /* Extensions */, + D6D12C8A2B291CA90054390C /* Subscription */, ); path = PrivacyPro; sourceTree = ""; @@ -4539,10 +4535,8 @@ D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D664C7B12B289AA000CBFA76 /* SimpleUserScript.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, - D664C7B42B289AA000CBFA76 /* TestUserScript.swift */, ); path = UserScripts; sourceTree = ""; @@ -6595,7 +6589,6 @@ EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */, C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */, 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, - D664C7CD2B289AA200CBFA76 /* TestUserScript.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, @@ -6855,7 +6848,6 @@ 0290472229E723260008FE3C /* AppTPManageTrackerCell.swift in Sources */, 985AAE4524899369007A43EC /* HomeScreenTransition.swift in Sources */, 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, - D664C7CA2B289AA200CBFA76 /* SimpleUserScript.swift in Sources */, 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 9865DFF922A8220D00D27829 /* FavoritesOverlay.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift deleted file mode 100644 index bc5220aad8..0000000000 --- a/DuckDuckGo/PrivacyPro/UserScripts/SimpleUserScript.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// SimpleUserScript.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 WebKit - -public final class SimpleUserScript: NSObject, WKScriptMessageHandler { - public var source: String { - return "" // Empty since the test script is in the HTML content - } - - public let context = "subscriptionPages" - - public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name == context { - print("Message received from web content: \(message.body)") - } - } -} diff --git a/DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift b/DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift deleted file mode 100644 index 4d5433ed13..0000000000 --- a/DuckDuckGo/PrivacyPro/UserScripts/TestUserScript.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// TestUserScript.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 WebKit - -public final class TestUserScript: NSObject, WKScriptMessageHandler { - public var source: String { - // Here, you can inject any JavaScript if needed - return "" - } - - public let context = "simpleContext" - - // Implement WKScriptMessageHandler method - public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name == context { - print("Message received from web content: \(message.body)") - } - } -} From d0e199d36e9efa95fbbadefdd78f7bbda73e1cad Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 5 Jan 2024 17:56:29 +0100 Subject: [PATCH 92/99] Noop change --- DuckDuckGo/SettingsLoginsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index e194f90770..d9acb97ef6 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -15,7 +15,7 @@ // 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. -// +// NOOP import SwiftUI import UIKit From c1039601689a04864b7425dd4fbfcbe520133e72 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 5 Jan 2024 17:56:57 +0100 Subject: [PATCH 93/99] NOOP --- DuckDuckGo/SettingsLoginsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index d9acb97ef6..e194f90770 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -15,7 +15,7 @@ // 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. -// NOOP +// import SwiftUI import UIKit From 10fa11d6f186b4c6a334e956bd3bb8ae4edd86b7 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 5 Jan 2024 18:09:39 +0100 Subject: [PATCH 94/99] =?UTF-8?q?We=20are=20now=20using=20=E2=80=9CPasswor?= =?UTF-8?q?ds=E2=80=9D=20for=20login=20items?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DuckDuckGo/UserText.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 40f32bff13..e8f9b136a2 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -940,7 +940,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsAddToDock = NSLocalizedString("settings.add.to.dock", value: "Add App to Your Dock", comment: "Settings screen cell text for adding the app to the dock") public static let settingsAddWidget = NSLocalizedString("settings.add.widget", value: "Add Widget to Home Screen", comment: "Settings screen cell text for add widget to the home screen") public static let settingsSync = NSLocalizedString("settings.sync", value: "Sync & Backup", comment: "Settings screen cell text for sync and backup") - public static let settingsLogins = NSLocalizedString("settings.logins", value: "Logins", comment: "Settings screen cell text for logins") + public static let settingsLogins = NSLocalizedString("settings.logins", value: "Passwords", comment: "Settings screen cell text for passwords") // Appeareance Section public static let settingsAppearanceSection = NSLocalizedString("settings.appearance", value: "Appearance", comment: "Settings screen appearance section title") From 06ffe723771bc60391258d06bd1760cc4e013702 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 8 Jan 2024 17:01:14 +0100 Subject: [PATCH 95/99] Merged Feedback from Settings Swift UI PR --- DuckDuckGo/FontSettings.swift | 2 +- DuckDuckGo/MainViewController+Segues.swift | 8 -------- DuckDuckGo/SettingsCell.swift | 6 +++--- DuckDuckGo/SettingsCustomizeView.swift | 2 +- DuckDuckGo/SettingsHostingController.swift | 4 ++-- DuckDuckGo/SettingsMoreView.swift | 2 +- DuckDuckGo/UserText.swift | 4 ++-- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/DuckDuckGo/FontSettings.swift b/DuckDuckGo/FontSettings.swift index 7e7728424b..2ece2774f8 100644 --- a/DuckDuckGo/FontSettings.swift +++ b/DuckDuckGo/FontSettings.swift @@ -19,7 +19,7 @@ import UIKit -struct FontSettings { +enum FontSettings { static var fontSizeForHeaderView: CGFloat { let contentSize = UIApplication.shared.preferredContentSizeCategory switch contentSize { diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 3dd12b6bc7..f523c6763f 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -231,14 +231,6 @@ extension MainViewController { } } - class HostingControllerCommunicator: ObservableObject { - var pushView: (() -> Void)? - - func requestPush() { - pushView?() - } - } - private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, syncDataProviders: syncDataProviders, diff --git a/DuckDuckGo/SettingsCell.swift b/DuckDuckGo/SettingsCell.swift index f409d8f7c9..76a0e469dd 100644 --- a/DuckDuckGo/SettingsCell.swift +++ b/DuckDuckGo/SettingsCell.swift @@ -30,7 +30,7 @@ struct SettingsCellComponents { /// Encapsulates a View representing a Cell with different configurations struct SettingsCellView: View, Identifiable { - enum Accesory { + enum Accessory { case none case rightDetail(String) case toggle(isOn: Binding) @@ -43,7 +43,7 @@ struct SettingsCellView: View, Identifiable { var image: Image? var action: () -> Void = {} var enabled: Bool = true - var accesory: Accesory + var accesory: Accessory var asLink: Bool var disclosureIndicator: Bool var id: UUID = UUID() @@ -60,7 +60,7 @@ struct SettingsCellView: View, Identifiable { /// - enabled: A Boolean value that determines whether the cell is enabled. /// - asLink: Wraps the view inside a Button. Used for views not wrapped in a NavigationLink /// - disclosureIndicator: Forces Adds a disclosure indicator on the right (chevron) - init(label: String, subtitle: String? = nil, image: Image? = nil, action: @escaping () -> Void = {}, accesory: Accesory = .none, enabled: Bool = true, asLink: Bool = false, disclosureIndicator: Bool = false) { + init(label: String, subtitle: String? = nil, image: Image? = nil, action: @escaping () -> Void = {}, accesory: Accessory = .none, enabled: Bool = true, asLink: Bool = false, disclosureIndicator: Bool = false) { self.label = label self.subtitle = subtitle self.image = image diff --git a/DuckDuckGo/SettingsCustomizeView.swift b/DuckDuckGo/SettingsCustomizeView.swift index f226897473..c7e4cf4564 100644 --- a/DuckDuckGo/SettingsCustomizeView.swift +++ b/DuckDuckGo/SettingsCustomizeView.swift @@ -26,7 +26,7 @@ struct SettingsCustomizeView: View { @State var shouldShowNoMicrophonePermissionAlert = false var body: some View { - Section(header: Text(UserText.settingsCustomizesection), + Section(header: Text(UserText.settingsCustomizeSection), footer: Text(UserText.settingsAssociatedAppsDescription)) { SettingsCellView(label: UserText.settingsKeyboard, diff --git a/DuckDuckGo/SettingsHostingController.swift b/DuckDuckGo/SettingsHostingController.swift index a0f339f202..e76e7ac83b 100644 --- a/DuckDuckGo/SettingsHostingController.swift +++ b/DuckDuckGo/SettingsHostingController.swift @@ -34,7 +34,7 @@ class SettingsHostingController: UIHostingController { } viewModel.onRequestPresentLegacyView = { [weak self] vc, modal in - self?.presentLegacyViewCOntroller(vc, modal: modal) + self?.presentLegacyViewController(vc, modal: modal) } viewModel.onRequestPopLegacyView = { [weak self] in @@ -57,7 +57,7 @@ class SettingsHostingController: UIHostingController { navigationController?.pushViewController(vc, animated: true) } - func presentLegacyViewCOntroller(_ vc: UIViewController, modal: Bool = false) { + func presentLegacyViewController(_ vc: UIViewController, modal: Bool = false) { if modal { vc.modalPresentationStyle = .fullScreen } diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index 39294c1e50..d1aa624b8f 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -26,7 +26,7 @@ struct SettingsMoreView: View { var body: some View { - Section(header: Text(UserText.settingsMoreSction)) { + Section(header: Text(UserText.settingsMoreSection)) { SettingsCellView(label: UserText.settingsEmailProtection, subtitle: UserText.settingsEmailProtectionDescription, diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index e8f9b136a2..8226da7082 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -974,7 +974,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsPProLearnMore = NSLocalizedString("settings.ppro.learn.more", value: "Learn More", comment: "Learn more button text for privacy pro") // Customize Section - public static let settingsCustomizesection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") + public static let settingsCustomizeSection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") public static let settingsPreviews = NSLocalizedString("settings.previews", value: "Long-Press Previews", comment: "Settings screen cell for long press previews") public static let settingsAutocomplete = NSLocalizedString("settings.autocomplete", value: "Autocomplete Suggestions", comment: "Settings screen cell for autocomplete") @@ -983,7 +983,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsAssociatedAppsDescription = NSLocalizedString("settings.associated.apps.description", value: "Disable to prevent links from automatically opening in other installed apps.", comment: "Description for associated apps description") // More Section - public static let settingsMoreSction = NSLocalizedString("settings.more", value: "More from DuckDuckGo", comment: "Settings title for the 'More' section") + public static let settingsMoreSection = NSLocalizedString("settings.more", value: "More from DuckDuckGo", comment: "Settings title for the 'More' section") public static let settingsEmailProtection = NSLocalizedString("settings.emailProtection", value: "Email Protection", comment: "Settings cell for Email Protection") public static let settingsEmailProtectionDescription = NSLocalizedString("settings.emailProtection.description", value: "Block email trackers and hide your address", comment: "Settings cell for Email Protection") From 6a34923eb8cd8bb1db23a391c1803021e3568ac6 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 8 Jan 2024 17:05:37 +0100 Subject: [PATCH 96/99] Apply Grouped UI Style for settings --- DuckDuckGo/SettingsView.swift | 19 +++++++++++++++++-- DuckDuckGo/en.lproj/Localizable.strings | 4 ++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index e71ff6d488..2bf4e6e761 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -50,12 +50,27 @@ struct SettingsView: View { viewModel.onRequestDismissSettings?() }) .accentColor(Color(designSystemColor: .textPrimary)) - .environmentObject(viewModel) - + .conditionalInsetGroupedListStyle() .onAppear { viewModel.onAppear() } } } + +struct InsetGroupedListStyleModifier: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 15, *) { + return AnyView(content.applyInsetGroupedListStyle()) + } else { + return AnyView(content) + } + } +} + +extension View { + func conditionalInsetGroupedListStyle() -> some View { + self.modifier(InsetGroupedListStyleModifier()) + } +} diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index f739de8098..af4c4298df 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1828,8 +1828,8 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings screen cell for Keyboard */ "settings.keyboard" = "Keyboard"; -/* Settings screen cell text for logins */ -"settings.logins" = "Logins"; +/* Settings screen cell text for passwords */ +"settings.logins" = "Passwords"; /* Settings title for the 'More' section */ "settings.more" = "More from DuckDuckGo"; From 874e2ca2692c47e81278542be67a751f49eb24a6 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 9 Jan 2024 05:48:07 +0100 Subject: [PATCH 97/99] Update locig to display netP --- DuckDuckGo/SettingsViewModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index ca50913a05..ee0e173041 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -86,7 +86,8 @@ final class SettingsViewModel: ObservableObject { var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION if #available(iOS 15, *) { - return featureFlagger.isFeatureOn(.networkProtection) + let accessController = NetworkProtectionAccessController() + return accessController.networkProtectionAccessType() != .none } else { return false } From d4b11a4153a8b00957592e1e9f3e11dd49f2a556 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 9 Jan 2024 07:32:54 +0100 Subject: [PATCH 98/99] Move properties to Settings State --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/SettingsAppeareanceView.swift | 6 +- DuckDuckGo/SettingsCustomizeView.swift | 2 +- DuckDuckGo/SettingsLoginsView.swift | 2 +- DuckDuckGo/SettingsMoreView.swift | 4 +- DuckDuckGo/SettingsPrivacyProView.swift | 20 ++++-- DuckDuckGo/SettingsState.swift | 57 ++++++++++++---- DuckDuckGo/SettingsSyncView.swift | 2 +- DuckDuckGo/SettingsView.swift | 6 +- DuckDuckGo/SettingsViewModel.swift | 66 ++++++++++++------- 10 files changed, 114 insertions(+), 53 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2e11dc9239..521e82e398 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -156,7 +156,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/SettingsAppeareanceView.swift b/DuckDuckGo/SettingsAppeareanceView.swift index 351bf21f11..4a2f374f4c 100644 --- a/DuckDuckGo/SettingsAppeareanceView.swift +++ b/DuckDuckGo/SettingsAppeareanceView.swift @@ -42,15 +42,15 @@ struct SettingsAppeareanceView: View { options: FireButtonAnimationType.allCases, selectedOption: viewModel.fireButtonAnimationBinding) - if viewModel.shouldShowTextSizeCell { + if viewModel.state.textSize.enabled { SettingsCellView(label: UserText.settingsText, action: { viewModel.presentLegacyView(.textSize) }, - accesory: .rightDetail("\(viewModel.state.textSize)%"), + accesory: .rightDetail("\(viewModel.state.textSize.size)%"), asLink: true, disclosureIndicator: true) } - if viewModel.shouldShowAddressBarPositionCell { + if viewModel.state.addressbar.enabled { SettingsPickerCellView(label: UserText.settingsAddressBar, options: AddressBarPosition.allCases, selectedOption: viewModel.addressBarPositionBinding) diff --git a/DuckDuckGo/SettingsCustomizeView.swift b/DuckDuckGo/SettingsCustomizeView.swift index c7e4cf4564..699398f20b 100644 --- a/DuckDuckGo/SettingsCustomizeView.swift +++ b/DuckDuckGo/SettingsCustomizeView.swift @@ -37,7 +37,7 @@ struct SettingsCustomizeView: View { SettingsCellView(label: UserText.settingsAutocomplete, accesory: .toggle(isOn: viewModel.autocompleteBinding)) - if viewModel.shouldShowSpeechRecognitionCell { + if viewModel.state.speechRecognitionEnabled { SettingsCellView(label: UserText.settingsVoiceSearch, accesory: .toggle(isOn: viewModel.voiceSearchEnabledBinding)) } diff --git a/DuckDuckGo/SettingsLoginsView.swift b/DuckDuckGo/SettingsLoginsView.swift index e194f90770..cae2f09076 100644 --- a/DuckDuckGo/SettingsLoginsView.swift +++ b/DuckDuckGo/SettingsLoginsView.swift @@ -28,7 +28,7 @@ struct SettingsLoginsView: View { @EnvironmentObject var viewModel: SettingsViewModel var body: some View { - if viewModel.shouldShowLoginsCell { + if viewModel.state.loginsEnabled { Section { SettingsCellView(label: UserText.settingsLogins, action: { viewModel.presentLegacyView(.logins) }, diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index d1aa624b8f..0f983ddaed 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -47,9 +47,9 @@ struct SettingsMoreView: View { disclosureIndicator: true) #if NETWORK_PROTECTION - if viewModel.shouldShowNetworkProtectionCell { + if viewModel.state.networkProtection.enabled { SettingsCellView(label: UserText.netPNavTitle, - subtitle: viewModel.state.netPSubtitle != "" ? viewModel.state.netPSubtitle : nil, + subtitle: viewModel.state.networkProtection.status != "" ? viewModel.state.networkProtection.status : nil, action: { viewModel.presentLegacyView(.netP) }, asLink: true, disclosureIndicator: true) diff --git a/DuckDuckGo/SettingsPrivacyProView.swift b/DuckDuckGo/SettingsPrivacyProView.swift index a006850ef1..31c7bd0f45 100644 --- a/DuckDuckGo/SettingsPrivacyProView.swift +++ b/DuckDuckGo/SettingsPrivacyProView.swift @@ -42,14 +42,24 @@ struct SettingsPrivacyProView: View { .foregroundColor(Color.init(designSystemColor: .accent)) } + private var purchaseSubscriptionView: some View { + return Group { + SettingsCustomCell(content: { privacyProDescriptionView }) + NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { + SettingsCustomCell(content: { learnMoreView }) + } + } + } + var body: some View { - if viewModel.canPurchaseSubscription { + + if viewModel.state.privacyPro.enabled { Section(header: Text(UserText.settingsPProSection)) { - SettingsCustomCell(content: { privacyProDescriptionView }) + if viewModel.state.privacyPro.hasActiveSubscription { - NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) { - SettingsCustomCell(content: { learnMoreView }) - } + } else { + purchaseSubscriptionView + } } } } diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index 026599faf7..ad56cbf297 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -21,12 +21,33 @@ import BrowserServicesKit struct SettingsState { + struct AddressBar { + var enabled: Bool + var position: AddressBarPosition + } + + struct TextSize { + var enabled: Bool + var size: Int + } + + struct NetworkProtection { + var enabled: Bool + var status: String + } + + struct PrivacyPro { + var enabled: Bool + var canPurchaseSubscription: Bool + var hasActiveSubscription: Bool + } + // Appearance properties var appTheme: ThemeName var appIcon: AppIcon var fireButtonAnimation: FireButtonAnimationType - var textSize: Int - var addressBarPosition: AddressBarPosition + var textSize: TextSize + var addressbar: AddressBar // Privacy properties var sendDoNotSell: Bool @@ -36,37 +57,51 @@ struct SettingsState { // Customization properties var autocomplete: Bool - var voiceSearchEnabled: Bool var longPressPreviews: Bool var allowUniversalLinks: Bool // Logins properties var activeWebsiteAccount: SecureVaultModels.WebsiteAccount? - // Network Protection properties - var netPSubtitle: String - // About properties var version: String + + // Features + var debugModeEnabled: Bool + var syncEnabled: Bool + var voiceSearchEnabled: Bool + var speechRecognitionEnabled: Bool + var loginsEnabled: Bool + + // Network Protection properties + var networkProtection: NetworkProtection + + // Subscriptions Properties + var privacyPro: PrivacyPro static var defaults: SettingsState { return SettingsState( appTheme: .systemDefault, appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: .fireRising, - textSize: 100, - addressBarPosition: .top, + textSize: TextSize(enabled: false, size: 100), + addressbar: AddressBar(enabled: false, position: .top), sendDoNotSell: true, autoconsentEnabled: false, autoclearDataEnabled: false, applicationLock: false, autocomplete: true, - voiceSearchEnabled: false, longPressPreviews: true, allowUniversalLinks: true, activeWebsiteAccount: nil, - netPSubtitle: "", - version: "0.0.0.0" + version: "0.0.0.0", + debugModeEnabled: false, + syncEnabled: false, + voiceSearchEnabled: false, + speechRecognitionEnabled: false, + loginsEnabled: false, + networkProtection: NetworkProtection(enabled: false, status: ""), + privacyPro: PrivacyPro(enabled: false, canPurchaseSubscription: false, hasActiveSubscription: false) ) } } diff --git a/DuckDuckGo/SettingsSyncView.swift b/DuckDuckGo/SettingsSyncView.swift index f0c0d0a757..dfbf0d4856 100644 --- a/DuckDuckGo/SettingsSyncView.swift +++ b/DuckDuckGo/SettingsSyncView.swift @@ -31,7 +31,7 @@ struct SettingsSyncView: View { var body: some View { - if viewModel.shouldShowSyncCell { + if viewModel.state.syncEnabled { Section { SettingsCellView(label: UserText.settingsSync, action: { viewModel.presentLegacyView(.sync) }, diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 2bf4e6e761..d5372591a4 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -34,10 +34,8 @@ struct SettingsView: View { SettingsAppeareanceView() SettingsPrivacyView() #if SUBSCRIPTION - if viewModel.shouldShowPrivacyProCell { - if #available(iOS 15, *) { - SettingsPrivacyProView() - } + if #available(iOS 15, *) { + SettingsPrivacyProView() } #endif SettingsCustomizeView() diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index ee0e173041..8884c49589 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -71,17 +71,8 @@ final class SettingsViewModel: ObservableObject { case networkProtection #endif } - - var shouldShowSyncCell: Bool { featureFlagger.isFeatureOn(.sync) } - var shouldShowLoginsCell: Bool { featureFlagger.isFeatureOn(.autofillAccessCredentialManagement) } - var shouldShowTextSizeCell: Bool { !isPad } - var shouldShowVoiceSearchCell: Bool { AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } - var shouldShowAddressBarPositionCell: Bool { !isPad } - var shouldShowSpeechRecognitionCell: Bool { AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable } + var shouldShowNoMicrophonePermissionAlert: Bool = false - var shouldShowDebugCell: Bool { return featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild } - var shouldShowPrivacyProCell: Bool { return featureFlagger.isFeatureOn(.privacyPro) } - @Published var canPurchaseSubscription = SubscriptionPurchaseEnvironment.canPurchase var shouldShowNetworkProtectionCell: Bool { #if NETWORK_PROTECTION @@ -128,11 +119,11 @@ final class SettingsViewModel: ObservableObject { var addressBarPositionBinding: Binding { Binding( get: { - self.state.addressBarPosition + self.state.addressbar.position }, set: { self.appSettings.currentAddressBarPosition = $0 - self.state.addressBarPosition = $0 + self.state.addressbar.position = $0 } ) } @@ -214,19 +205,45 @@ extension SettingsViewModel { appTheme: appSettings.currentThemeName, appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: appSettings.currentFireButtonAnimation, - textSize: appSettings.textSize, - addressBarPosition: appSettings.currentAddressBarPosition, + textSize: SettingsState.TextSize(enabled: !isPad, size: appSettings.textSize), + addressbar: SettingsState.AddressBar(enabled: !isPad, position: appSettings.currentAddressBarPosition), sendDoNotSell: appSettings.sendDoNotSell, autoconsentEnabled: appSettings.autoconsentEnabled, autoclearDataEnabled: AutoClearSettingsModel(settings: appSettings) != nil, applicationLock: privacyStore.authenticationEnabled, autocomplete: appSettings.autocomplete, - voiceSearchEnabled: appSettings.voiceSearchEnabled && AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, longPressPreviews: appSettings.longPressPreviews, allowUniversalLinks: appSettings.allowUniversalLinks, - activeWebsiteAccount: nil, // Default value for logins - netPSubtitle: "", // Default value for Network Protection - version: versionProvider.versionAndBuildNumber + activeWebsiteAccount: nil, + version: versionProvider.versionAndBuildNumber, + debugModeEnabled: featureFlagger.isFeatureOn(.debugMenu) || isDebugBuild, + syncEnabled: featureFlagger.isFeatureOn(.sync), + voiceSearchEnabled: AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, + speechRecognitionEnabled: AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, + loginsEnabled: featureFlagger.isFeatureOn(.autofillAccessCredentialManagement), + networkProtection: { + var enabled = false +#if NETWORK_PROTECTION + if #available(iOS 15, *) { + let accessController = NetworkProtectionAccessController() + enabled = accessController.networkProtectionAccessType() != .none + } +#endif + return SettingsState.NetworkProtection(enabled: enabled, status: "") + }(), + privacyPro: { + var enabled = false + var canPurchaseSubscription = false + var hasActiveSubscription = false +#if SUBSCRIPTION + enabled = featureFlagger.isFeatureOn(.privacyPro) + canPurchaseSubscription = SubscriptionPurchaseEnvironment.canPurchase + hasActiveSubscription = false +#endif + return SettingsState.PrivacyPro(enabled: enabled, + canPurchaseSubscription: canPurchaseSubscription, + hasActiveSubscription: hasActiveSubscription) + }() ) setupSubscribers() #if SUBSCRIPTION @@ -258,23 +275,24 @@ extension SettingsViewModel { PurchaseManager.shared.$availableProducts .receive(on: RunLoop.main) .sink { [weak self] products in - self?.canPurchaseSubscription = !products.isEmpty + self?.state.privacyPro.enabled = !products.isEmpty + self?.state.privacyPro.canPurchaseSubscription = !products.isEmpty }.store(in: &cancellables) } #endif #if NETWORK_PROTECTION - private func updateNetPCellSubtitle(connectionStatus: ConnectionStatus) { + private func updateNetPStatus(connectionStatus: ConnectionStatus) { switch NetworkProtectionAccessController().networkProtectionAccessType() { case .none, .waitlistAvailable, .waitlistJoined, .waitlistInvitedPendingTermsAcceptance: - self.state.netPSubtitle = VPNWaitlist.shared.settingsSubtitle + self.state.networkProtection.status = VPNWaitlist.shared.settingsSubtitle case .waitlistInvited, .inviteCodeInvited: switch connectionStatus { case .connected: - self.state.netPSubtitle = UserText.netPCellConnected + self.state.networkProtection.status = UserText.netPCellConnected default: - self.state.netPSubtitle = UserText.netPCellDisconnected + self.state.networkProtection.status = UserText.netPCellDisconnected } } } @@ -291,7 +309,7 @@ extension SettingsViewModel { connectionObserver.publisher .receive(on: DispatchQueue.main) .sink { [weak self] status in - self?.updateNetPCellSubtitle(connectionStatus: status) + self?.updateNetPStatus(connectionStatus: status) } .store(in: &cancellables) #endif From b31a1fdcf61ab74622698c94175289d5da3136f6 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jan 2024 12:02:06 +0100 Subject: [PATCH 99/99] Fix lint issues --- DuckDuckGo.xcodeproj/project.pbxproj | 12 -- .../Subscription/AccountManager.swift | 1 + .../AccountKeychainStorage.swift | 1 + .../AccountStorage/AccountStorage.swift | 1 + .../AppStoreAccountManagementFlow.swift | 5 +- .../Flows/AppStore/AppStorePurchaseFlow.swift | 5 +- .../Flows/AppStore/AppStoreRestoreFlow.swift | 7 +- .../Subscription/Flows/PurchaseFlow.swift | 3 +- .../Flows/Stripe/StripePurchaseFlow.swift | 91 ---------- .../PrivacyPro/Subscription/Logging.swift | 1 + .../Subscription/PurchaseManager.swift | 1 + .../Subscription/Services/APIService.swift | 1 + .../Subscription/Services/AuthService.swift | 9 +- .../Services/SubscriptionService.swift | 5 +- .../SubscriptionPurchaseEnvironment.swift | 1 + .../Subscription/URL+Subscription.swift | 1 + ...scriptionPagesUseSubscriptionFeature.swift | 4 +- DuckDuckGo/SettingsViewModel.swift | 3 +- DuckDuckGo/SubscriptionDebugModel.swift | 169 ------------------ .../SubscriptionTests/SubscriptionTests.swift | 12 -- 20 files changed, 40 insertions(+), 293 deletions(-) delete mode 100644 DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift delete mode 100644 DuckDuckGo/SubscriptionDebugModel.swift delete mode 100644 LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1f0bd6cf46..c6e28cc8e6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -788,7 +788,6 @@ D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */; }; D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */; }; D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C972B291CA90054390C /* PurchaseFlow.swift */; }; - D6D12CA92B291CAA0054390C /* StripePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */; }; D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9B2B291CA90054390C /* SubscriptionService.swift */; }; D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9C2B291CA90054390C /* APIService.swift */; }; D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9D2B291CA90054390C /* AuthService.swift */; }; @@ -2427,7 +2426,6 @@ D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; D6D12C972B291CA90054390C /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; - D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StripePurchaseFlow.swift; sourceTree = ""; }; D6D12C9B2B291CA90054390C /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; D6D12C9C2B291CA90054390C /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; D6D12C9D2B291CA90054390C /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; @@ -4579,7 +4577,6 @@ children = ( D6D12C932B291CA90054390C /* AppStore */, D6D12C972B291CA90054390C /* PurchaseFlow.swift */, - D6D12C982B291CA90054390C /* Stripe */, ); path = Flows; sourceTree = ""; @@ -4594,14 +4591,6 @@ path = AppStore; sourceTree = ""; }; - D6D12C982B291CA90054390C /* Stripe */ = { - isa = PBXGroup; - children = ( - D6D12C992B291CA90054390C /* StripePurchaseFlow.swift */, - ); - path = Stripe; - sourceTree = ""; - }; D6D12C9A2B291CA90054390C /* Services */ = { isa = PBXGroup; children = ( @@ -6474,7 +6463,6 @@ B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, - D6D12CA92B291CAA0054390C /* StripePurchaseFlow.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, diff --git a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift index 655742298d..60b87a9d65 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountManager.swift @@ -1,5 +1,6 @@ // // AccountManager.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountKeychainStorage.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountKeychainStorage.swift index 19596f764e..5fc79bc636 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountKeychainStorage.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountKeychainStorage.swift @@ -1,5 +1,6 @@ // // AccountKeychainStorage.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountStorage.swift b/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountStorage.swift index 06b5e05cb5..9a1c98d6b7 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountStorage.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/AccountStorage/AccountStorage.swift @@ -1,5 +1,6 @@ // // AccountStorage.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift index a3a8b29add..a78faa029a 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift @@ -1,5 +1,6 @@ // // AppStoreAccountManagementFlow.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -36,7 +37,9 @@ public final class AppStoreAccountManagementFlow { if #available(macOS 12.0, iOS 15.0, *) { // In case of invalid token attempt store based authentication to obtain a new one - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.noPastTransaction) } + guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { + return .failure(.noPastTransaction) + } switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { case .success(let response): diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift index 2d4497cb00..1d4c153f3d 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift @@ -1,5 +1,6 @@ // // AppStorePurchaseFlow.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -67,7 +68,9 @@ public final class AppStorePurchaseFlow { case .subscriptionExpired(let expiredAccountDetails): externalID = expiredAccountDetails.externalID accountManager.storeAuthToken(token: expiredAccountDetails.authToken) - accountManager.storeAccount(token: expiredAccountDetails.accessToken, email: expiredAccountDetails.email, externalID: expiredAccountDetails.externalID) + accountManager.storeAccount(token: expiredAccountDetails.accessToken, + email: expiredAccountDetails.email, + externalID: expiredAccountDetails.externalID) case .missingAccountOrTransactions: // No history, create new account switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift index a23a3255f7..de10a8f3ab 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift @@ -1,5 +1,6 @@ // -// AppStorePurchaseFlow.swift +// AppStoreRestoreFlow.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -36,7 +37,9 @@ public final class AppStoreRestoreFlow { } public static func restoreAccountFromPastPurchase() async -> Result { - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { return .failure(.missingAccountOrTransactions) } + guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { + return .failure(.missingAccountOrTransactions) + } let accountManager = AccountManager() diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift index d1a666bde2..e5f7bec453 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Flows/PurchaseFlow.swift @@ -1,5 +1,6 @@ // -// AppStorePurchaseFlow.swift +// PurchaseFlow.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift b/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift deleted file mode 100644 index 67070f383b..0000000000 --- a/DuckDuckGo/PrivacyPro/Subscription/Flows/Stripe/StripePurchaseFlow.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// StripePurchaseFlow.swift -// -// 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 StoreKit - -public final class StripePurchaseFlow { - - public enum Error: Swift.Error { - case noProductsFound - case accountCreationFailed - } - - public static func subscriptionOptions() async -> Result { - - guard case let .success(products) = await SubscriptionService.getProducts(), !products.isEmpty else { return .failure(.noProductsFound) } - - let currency = products.first?.currency ?? "USD" - - let formatter = NumberFormatter() - formatter.numberStyle = .currency - formatter.locale = Locale(identifier: "en_US@currency=\(currency)") - - let options: [SubscriptionOption] = products.map { - var displayPrice = "\($0.price) \($0.currency)" - - if let price = Float($0.price), let formattedPrice = formatter.string(from: price as NSNumber) { - displayPrice = formattedPrice - } - - let cost = SubscriptionOptionCost(displayPrice: displayPrice, recurrence: $0.billingPeriod.lowercased()) - - return SubscriptionOption(id: $0.productId, - cost: cost) - } - - let features = SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) } - - return .success(SubscriptionOptions(platform: SubscriptionPlatformName.stripe.rawValue, - options: options, - features: features)) - } - - public static func prepareSubscriptionPurchase(emailAccessToken: String?) async -> Result { - - var authToken: String = "" - - switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { - case .success(let response): - authToken = response.authToken - AccountManager().storeAuthToken(token: authToken) - case .failure: - return .failure(.accountCreationFailed) - } - - return .success(PurchaseUpdate(type: "redirect", token: authToken)) - } - - public static func completeSubscriptionPurchase() async { - let accountManager = AccountManager() - - if let authToken = accountManager.authToken { - print("Exchanging token") - - if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(authToken), - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAuthToken(token: authToken) - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - } - } - - if #available(macOS 12.0, iOS 15.0, *) { - await AppStorePurchaseFlow.checkForEntitlements(wait: 2.0, retry: 5) - } - } -} diff --git a/DuckDuckGo/PrivacyPro/Subscription/Logging.swift b/DuckDuckGo/PrivacyPro/Subscription/Logging.swift index b15ec8e1fa..24dc783c6e 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Logging.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Logging.swift @@ -1,5 +1,6 @@ // // Logging.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift index fd4032b38b..fcdb4d230b 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/PurchaseManager.swift @@ -1,5 +1,6 @@ // // PurchaseManager.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/Services/APIService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/APIService.swift index 5a7d8f0d1b..741f3163dd 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Services/APIService.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Services/APIService.swift @@ -1,5 +1,6 @@ // // APIService.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/Services/AuthService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/AuthService.swift index 33a18262ad..3f1d3c4088 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Services/AuthService.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Services/AuthService.swift @@ -1,5 +1,6 @@ // // AuthService.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -83,9 +84,11 @@ public struct AuthService: APIService { public let externalID: String public let status: String + // swiftlint:disable nesting enum CodingKeys: String, CodingKey { case authToken = "authToken", externalID = "externalId", status // no underscores due to keyDecodingStrategy = .convertFromSnakeCase } + // swiftlint:enable nesting } // MARK: - @@ -105,8 +108,12 @@ public struct AuthService: APIService { public let id: Int public let status: String + // swiftlint:disable:next nesting enum CodingKeys: String, CodingKey { - case authToken = "authToken", email, externalID = "externalId", id, status // no underscores due to keyDecodingStrategy = .convertFromSnakeCase + case authToken = "authToken", + email, externalID = "externalId", + id, + status // no underscores due to keyDecodingStrategy = .convertFromSnakeCase } } } diff --git a/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift index 6c3ed27db1..347cec975a 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/Services/SubscriptionService.swift @@ -1,5 +1,6 @@ // // SubscriptionService.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -31,7 +32,9 @@ public struct SubscriptionService: APIService { // MARK: - public static func getSubscriptionDetails(token: String) async -> Result { - let result: Result = await executeAPICall(method: "GET", endpoint: "subscription", headers: makeAuthorizationHeader(for: token)) + let result: Result = await executeAPICall(method: "GET", + endpoint: "subscription", + headers: makeAuthorizationHeader(for: token)) switch result { case .success(let response): diff --git a/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift b/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift index 60ac8c3e53..8c7d499bfc 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/SubscriptionPurchaseEnvironment.swift @@ -1,5 +1,6 @@ // // SubscriptionPurchaseEnvironment.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift b/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift index 78061d4a93..8b2fded391 100644 --- a/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift +++ b/DuckDuckGo/PrivacyPro/Subscription/URL+Subscription.swift @@ -1,5 +1,6 @@ // // URL+Subscription.swift +// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 09c83c23e5..6f6187ecac 100644 --- a/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/PrivacyPro/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -89,14 +89,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec struct Subscription: Encodable { let token: String } - + /// Values that the Frontend can use to determine the current state. + // swiftlint:disable nesting struct SubscriptionValues: Codable { enum CodingKeys: String, CodingKey { case token } let token: String } + // swiftlint:enable nesting // Manage transation in progress flag private func withTransactionInProgress(_ work: () async throws -> T) async rethrows -> T { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 8884c49589..21b7593f80 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -16,7 +16,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // - +// swiftlint:disable file_length import Core import BrowserServicesKit import Persistence @@ -428,3 +428,4 @@ extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { onRequestPopLegacyView?() } } +// swiftlint:enable file_length diff --git a/DuckDuckGo/SubscriptionDebugModel.swift b/DuckDuckGo/SubscriptionDebugModel.swift deleted file mode 100644 index 9bc813254d..0000000000 --- a/DuckDuckGo/SubscriptionDebugModel.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// SubscriptionModel.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 - -// public final class SubscriptionDebugModel { - /* - private let accountManager = AccountManager() - - private var _purchaseManager: Any? - - @available(macOS 12.0, iOS 15.0, *) - fileprivate var purchaseManager: PurchaseManager { - if _purchaseManager == nil { - _purchaseManager = PurchaseManager() - } - // swiftlint:disable:next force_cast - return _purchaseManager as! PurchaseManager - } - - @objc - func simulateSubscriptionActiveState() { - accountManager.storeAccount(token: "fake-token", email: "fake@email.com", externalID: "123") - } - - @objc - func signOut() { - accountManager.signOut() - } - - @objc - func showAccountDetails() { - let title = accountManager.isUserAuthenticated ? "Authenticated" : "Not Authenticated" - let message = accountManager.isUserAuthenticated ? ["AuthToken: \(accountManager.authToken ?? "")", - "AccessToken: \(accountManager.accessToken ?? "")", - "Email: \(accountManager.email ?? "")"].joined(separator: "\n") : nil - showAlert(title: title, message: message) - } - - @objc - func validateToken() { - Task { - guard let token = accountManager.accessToken else { return } - switch await AuthService.validateToken(accessToken: token) { - case .success(let response): - showAlert(title: "Validate token", message: "\(response)") - case .failure(let error): - showAlert(title: "Validate token", message: "\(error)") - } - } - } - - @objc - func checkEntitlements() { - Task { - var results: [String] = [] - - for entitlementName in ["fake", "dummy1", "dummy2", "dummy3"] { - let result = await AccountManager().hasEntitlement(for: entitlementName) - let resultSummary = "Entitlement check for \(entitlementName): \(result)" - results.append(resultSummary) - print(resultSummary) - } - - showAlert(title: "Check Entitlements", message: results.joined(separator: "\n")) - } - } - - @objc - func getSubscriptionInfo() { - Task { - guard let token = accountManager.accessToken else { return } - switch await SubscriptionService.getSubscriptionInfo(token: token) { - case .success(let response): - showAlert(title: "Subscription info", message: "\(response)") - case .failure(let error): - showAlert(title: "Subscription info", message: "\(error)") - } - } - } - - @available(macOS 12.0, *) - @objc - func syncAppleIDAccount() { - Task { - await purchaseManager.syncAppleIDAccount() - } - } - - @available(macOS 12.0, *) - @objc - func checkProductsAvailability() { - Task { - - let result = await purchaseManager.hasProductsAvailable() - showAlert(title: "Check App Store Product Availability", - message: "Can purchase: \(result ? "YES" : "NO")") - } - } - - @objc - func restorePurchases(_ sender: Any?) { - if #available(macOS 12.0, *) { - Task { - await AppStoreRestoreFlow.restoreAccountFromPastPurchase() - } - } - } - - /*/ - @objc - func testError1(_ sender: Any?) { - Task { @MainActor in - let alert = NSAlert.init() - alert.messageText = "Something Went Wrong" - alert.informativeText = "The App Store was not able to process your purchase. Please try again later." - alert.addButton(withTitle: "OK") - alert.runModal() - } - } - - @objc - func testError2(_ sender: Any?) { - Task { @MainActor in - let alert = NSAlert.init() - alert.messageText = "Subscription Not Found" - alert.informativeText = "The subscription associated with this Apple ID is no longer active." - alert.addButton(withTitle: "View Plans") - alert.addButton(withTitle: "Cancel") - alert.runModal() - } - } - - @IBAction func showPurchaseView(_ sender: Any?) { - if #available(macOS 12.0, *) { - currentViewController()?.presentAsSheet(DebugPurchaseViewController()) - } - } - - private func showAlert(title: String, message: String? = nil) { - Task { @MainActor in - let alert = NSAlert.init() - alert.messageText = title - if let message = message { - alert.informativeText = message - } - alert.addButton(withTitle: "OK") - alert.runModal() - } - } - */ -} -*/ diff --git a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift b/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift deleted file mode 100644 index 32417fc73a..0000000000 --- a/LocalPackages/Subscription/Tests/SubscriptionTests/SubscriptionTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import Subscription - -final class SubscriptionTests: XCTestCase { - func testExample() throws { - // XCTest Documentation - // https://developer.apple.com/documentation/xctest - - // Defining Test Cases and Test Methods - // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods - } -}