diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8a6d1ba63b..c1b68f040c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,6 +44,7 @@ jobs: -scheme "DuckDuckGo" \ -destination "platform=iOS Simulator,name=iPhone 14,OS=16.4" -skipPackagePluginValidation \ + -skipMacroValidation \ - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index c0a2e39498..68abd594b3 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -3,6 +3,7 @@ name: End-to-End tests on: schedule: - cron: '0 4 * * *' # run at 4 AM UTC + workflow_dispatch: jobs: end-to-end-tests: @@ -43,6 +44,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ ONLY_ACTIVE_ARCH=NO \ | tee xcodebuild.log diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ae31bc6122..a53560b741 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -47,6 +47,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | tee xcodebuild.log \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml @@ -89,6 +90,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml - name: Publish unit tests report diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bdbdb037cc..462a3fc004 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -82,6 +82,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ DDG_SLOW_COMPILE_CHECK_THRESHOLD=250 \ | tee xcodebuild.log \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml @@ -188,6 +189,7 @@ jobs: -derivedDataPath "DerivedData" \ -configuration "Release" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | xcbeautify create-asana-task: diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index 78d7e2ad1e..af560b1dd5 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -44,6 +44,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ ONLY_ACTIVE_ARCH=NO \ | tee xcodebuild.log diff --git a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml index 9de815dc35..e2da0f3e3a 100644 --- a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml +++ b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml @@ -5,9 +5,10 @@ tags: --- # Set up +- clearKeychain - clearState - launchApp -- runFlow: +- runFlow: when: visible: text: "Let’s Do It!" @@ -22,6 +23,13 @@ tags: - inputText: "privacy blogs" - pressKey: Enter +# Dismiss Dax Dialogs if visible +- runFlow: + when: + visible: "Phew!" + commands: + - tapOn: "Phew!" + # Change settings - tapOn: "Safe search: moderate ▼" - tapOn: "Off" diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig index 4c15890703..940e9dfdcf 100644 --- a/Configuration/Configuration-Alpha.xcconfig +++ b/Configuration/Configuration-Alpha.xcconfig @@ -26,3 +26,6 @@ APP_ID = com.duckduckgo.mobile.ios.alpha // A prefix for group ids. Must start with "group.". GROUP_ID_PREFIX = group.com.duckduckgo.alpha + +// The keychain access group for subscriptions +SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions.alpha diff --git a/Configuration/DuckDuckGoDeveloper.xcconfig b/Configuration/DuckDuckGoDeveloper.xcconfig index d23a9aaa9c..37195c89c5 100644 --- a/Configuration/DuckDuckGoDeveloper.xcconfig +++ b/Configuration/DuckDuckGoDeveloper.xcconfig @@ -27,3 +27,6 @@ CODE_SIGN_STYLE = Manual // The manually specified provisioning profile PROVISIONING_PROFILE_SPECIFIER = Development - App + +// The keychain access group for subscriptions +SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 6921fbe607..96f77f5f65 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.110.0 +MARKETING_VERSION = 7.111.0 diff --git a/DuckDuckGo/Subscription/UserScripts/Subscription.swift b/Core/AccountManagerExtension.swift similarity index 74% rename from DuckDuckGo/Subscription/UserScripts/Subscription.swift rename to Core/AccountManagerExtension.swift index 9306a531c5..5dd738fa3d 100644 --- a/DuckDuckGo/Subscription/UserScripts/Subscription.swift +++ b/Core/AccountManagerExtension.swift @@ -1,5 +1,5 @@ // -// Subscription.swift +// AccountManagerExtension.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -17,8 +17,15 @@ // limitations under the License. // +#if SUBSCRIPTION + import Foundation +import Subscription -struct Subscription: Encodable { - let token: String +public extension AccountManager { + convenience init() { + self.init(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + } } + +#endif diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 2eb52672b6..37ce38ac13 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"20ceae04600e9d3c46898ac4471fcd70\"" - public static let embeddedDataSHA = "987f63a393724a34fe9d190c335f14b58398d2e9e02ccf38b84a7bd57c37b8ab" + public static let embeddedDataETag = "\"a427a69043b2baa27604bc10edb13de1\"" + public static let embeddedDataSHA = "1d5c2e4113713fbf02bc617fc689981604ea53be172569a9fd744054b7355c39" } public var embeddedDataEtag: String { diff --git a/Core/AppURLs.swift b/Core/AppURLs.swift index e2250c3744..eff05031d3 100644 --- a/Core/AppURLs.swift +++ b/Core/AppURLs.swift @@ -17,8 +17,9 @@ // limitations under the License. // -import Foundation import BrowserServicesKit +import Foundation +import Macros public extension URL { @@ -48,7 +49,7 @@ public extension URL { static let exti = URL(string: "\(base)/exti/\(devMode)")! static let feedback = URL(string: "\(base)/feedback.js?type=app-feedback")! - static let appStore = URL(string: "https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361")! + static let appStore = #URL("https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361") static let mac = URL(string: "\(base)/mac")! static let windows = URL(string: "\(base)/windows")! diff --git a/Core/BookmarksImporter.swift b/Core/BookmarksImporter.swift index 177dcf2955..4615e47689 100644 --- a/Core/BookmarksImporter.swift +++ b/Core/BookmarksImporter.swift @@ -17,11 +17,12 @@ // limitations under the License. // +import Bookmarks import Common import Foundation -import SwiftSoup -import Bookmarks +import Macros import Persistence +import SwiftSoup public enum BookmarksImportError: Error { case invalidHtmlNoDLTag @@ -217,7 +218,7 @@ final public class BookmarksImporter { static let FavoritesFolder = "DuckDuckGo Favorites" static let BookmarksFolder = "DuckDuckGo Bookmarks" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = URL(string: "https://duckduckgo.com")! + static let bookmarkURL = #URL("https://duckduckgo.com") static let favoriteAttribute = "duckduckgo:favorite" static let isFavorite = "true" static let idAttribute = "id" diff --git a/Core/BundleExtension.swift b/Core/BundleExtension.swift new file mode 100644 index 0000000000..97b82eaae5 --- /dev/null +++ b/Core/BundleExtension.swift @@ -0,0 +1,40 @@ +// +// BundleExtension.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension Bundle { + public func appGroup(bundle: BundleGroup) -> String { + var appGroupName: String + + switch bundle { + case .subs: + appGroupName = "SUBSCRIPTION_APP_GROUP" + } + + guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { + fatalError("Info.plist is missing \(appGroupName)") + } + return appGroup + } +} + +public enum BundleGroup { + case subs +} diff --git a/Core/BundleExtensions.swift b/Core/BundleExtensions.swift new file mode 100644 index 0000000000..2ba420193f --- /dev/null +++ b/Core/BundleExtensions.swift @@ -0,0 +1,40 @@ +// +// BundleExtensions.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension Bundle { + public func appGroup(bundle: BundleGroup) -> String { + var appGroupName: String + + switch bundle { + case .subs: + appGroupName = "SUBSCRIPTION_APP_GROUP" + } + + guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { + fatalError("Info.plist is missing \(appGroupName)") + } + return appGroup + } +} + +public enum BundleGroup { + case subs +} diff --git a/Core/DataStoreWarmup.swift b/Core/DataStoreWarmup.swift index 79087b9fc2..6554f0ca8c 100644 --- a/Core/DataStoreWarmup.swift +++ b/Core/DataStoreWarmup.swift @@ -18,6 +18,7 @@ // import Combine +import Macros import WebKit /// WKWebsiteDataStore is basically non-functional until a web view has been instanciated and a page is successfully loaded. @@ -27,7 +28,7 @@ public class DataStoreWarmup { @MainActor public func ensureReady() async { - await BlockingNavigationDelegate().loadInBackgroundWebView(url: URL(string: "about:blank")!) + await BlockingNavigationDelegate().loadInBackgroundWebView(url: #URL("about:blank")) } } diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 1246732321..0715f027d1 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -36,7 +36,6 @@ public enum FeatureFlag: String { case networkProtectionWaitlistActive case subscription case swipeTabs - case autoconsentOnByDefault case history } @@ -67,8 +66,6 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault)) case .incontextSignup: return .remoteReleasable(.feature(.incontextSignup)) - case .autoconsentOnByDefault: - return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault)) case .history: return .remoteReleasable(.feature(.history)) } diff --git a/Core/Pixel.swift b/Core/Pixel.swift index dd0a8e77a6..edf6d6611b 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -249,6 +249,16 @@ extension Pixel { } } +/// NSError supports this through `NSUnderlyingError`, but there's no support for this for Swift's `Error`. This protocol does that. +/// +/// The reason why this protocol returns a code and a domain instead of just an `Error` or `NSError` is so that the error implementing +/// this protocol has full control over these values, and is able to override them as it best sees fit. +/// +protocol ErrorWithUnderlyingError: Error { + var underlyingErrorCode: Int { get } + var underlyingErrorDomain: String { get } +} + extension Dictionary where Key == String, Value == String { mutating func appendErrorPixelParams(error: Error) { let nsError = error as NSError @@ -256,7 +266,10 @@ extension Dictionary where Key == String, Value == String { self[PixelParameters.errorCode] = "\(nsError.code)" self[PixelParameters.errorDomain] = nsError.domain - if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { + if let underlyingError = error as? ErrorWithUnderlyingError { + self[PixelParameters.underlyingErrorCode] = "\(underlyingError.underlyingErrorCode)" + self[PixelParameters.underlyingErrorDomain] = underlyingError.underlyingErrorDomain + } else if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { self[PixelParameters.underlyingErrorCode] = "\(underlyingError.code)" self[PixelParameters.underlyingErrorDomain] = underlyingError.domain } else if let sqlErrorCode = nsError.userInfo["NSSQLiteErrorDomain"] as? NSNumber { diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 527ce9c495..a8a2b9164a 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -266,6 +266,18 @@ extension Pixel { case networkProtectionActiveUser case networkProtectionNewUser + case networkProtectionControllerStartAttempt + case networkProtectionControllerStartSuccess + case networkProtectionControllerStartFailure + + case networkProtectionTunnelStartAttempt + case networkProtectionTunnelStartSuccess + case networkProtectionTunnelStartFailure + + case networkProtectionTunnelUpdateAttempt + case networkProtectionTunnelUpdateSuccess + case networkProtectionTunnelUpdateFailure + case networkProtectionEnableAttemptConnecting case networkProtectionEnableAttemptSuccess case networkProtectionEnableAttemptFailure @@ -280,6 +292,8 @@ extension Pixel { case networkProtectionBreakageReport + case networkProtectionRekeyAttempt + case networkProtectionRekeyFailure case networkProtectionRekeyCompleted case networkProtectionTunnelConfigurationNoServerRegistrationInfo @@ -499,6 +513,7 @@ extension Pixel { case syncRemoveDeviceError case syncDeleteAccountError case syncLoginExistingAccountError + case syncWrongEnvironment case swipeTabsUsed case swipeTabsUsedDaily @@ -785,6 +800,15 @@ extension Pixel.Event { case .networkProtectionActiveUser: return "m_netp_daily_active_d" case .networkProtectionNewUser: return "m_netp_daily_active_u" + case .networkProtectionControllerStartAttempt: return "m_netp_controller_start_attempt" + case .networkProtectionControllerStartSuccess: return "m_netp_controller_start_success" + case .networkProtectionControllerStartFailure: return "m_netp_controller_start_failure" + case .networkProtectionTunnelStartAttempt: return "m_netp_tunnel_start_attempt" + case .networkProtectionTunnelStartSuccess: return "m_netp_tunnel_start_success" + case .networkProtectionTunnelStartFailure: return "m_netp_tunnel_start_failure" + case .networkProtectionTunnelUpdateAttempt: return "m_netp_tunnel_update_attempt" + case .networkProtectionTunnelUpdateSuccess: return "m_netp_tunnel_update_success" + case .networkProtectionTunnelUpdateFailure: return "m_netp_tunnel_update_failure" case .networkProtectionEnableAttemptConnecting: return "m_netp_ev_enable_attempt" case .networkProtectionEnableAttemptSuccess: return "m_netp_ev_enable_attempt_success" case .networkProtectionEnableAttemptFailure: return "m_netp_ev_enable_attempt_failure" @@ -792,7 +816,9 @@ extension Pixel.Event { case .networkProtectionTunnelFailureRecovered: return "m_netp_ev_tunnel_failure_recovered" case .networkProtectionLatency(let quality): return "m_netp_ev_\(quality.rawValue)_latency" case .networkProtectionLatencyError: return "m_netp_ev_latency_error_d" + case .networkProtectionRekeyAttempt: return "m_mac_netp_rekey_attempt" case .networkProtectionRekeyCompleted: return "m_netp_rekey_completed" + case .networkProtectionRekeyFailure: return "m_netp_rekey_failure" case .networkProtectionEnabledOnSearch: return "m_netp_ev_enabled_on_search" case .networkProtectionBreakageReport: return "m_vpn_breakage_report" case .networkProtectionTunnelConfigurationNoServerRegistrationInfo: return "m_netp_tunnel_config_error_no_server_registration_info" @@ -1004,6 +1030,8 @@ extension Pixel.Event { case .syncDeleteAccountError: return "m_d_sync_delete_account_error" case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error" + case .syncWrongEnvironment: return "m_d_sync_wrong_environment_u" + case .swipeTabsUsed: return "m_swipe-tabs-used" case .swipeTabsUsedDaily: return "m_swipe-tabs-used-daily" diff --git a/Core/PrivacyFeatures.swift b/Core/PrivacyFeatures.swift index b3ca70c839..977a5d94f9 100644 --- a/Core/PrivacyFeatures.swift +++ b/Core/PrivacyFeatures.swift @@ -33,17 +33,21 @@ public final class PrivacyFeatures { } private static let httpsUpgradeDebugEvents = EventMapping { event, error, parameters, onComplete in let domainEvent: Pixel.Event + let dailyAndCount: Bool + switch event { case .dbSaveBloomFilterError: domainEvent = .dbSaveBloomFilterError + dailyAndCount = true case .dbSaveExcludedHTTPSDomainsError: domainEvent = .dbSaveExcludedHTTPSDomainsError + dailyAndCount = false } - if let error { - Pixel.fire(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + if dailyAndCount { + DailyPixel.fireDailyAndCount(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onCountComplete: onComplete) } else { - Pixel.fire(pixel: domainEvent, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + Pixel.fire(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) } } private static var httpsUpgradeStore: AppHTTPSUpgradeStore { diff --git a/Core/UserAgentManager.swift b/Core/UserAgentManager.swift index 80db1e64a0..32f90094d2 100644 --- a/Core/UserAgentManager.swift +++ b/Core/UserAgentManager.swift @@ -19,10 +19,11 @@ // swiftlint:disable file_length -import Foundation -import WebKit import BrowserServicesKit import Common +import Foundation +import Macros +import WebKit public protocol UserAgentManager { @@ -46,7 +47,7 @@ public class DefaultUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) + webview.load(URLRequest.developerInitiated(#URL("about:blank"))) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned diff --git a/Core/ios-config.json b/Core/ios-config.json index eaba26e1a1..d1f5828cd3 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1708953918019, + "version": 1709563256742, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -71,15 +71,15 @@ { "domain": "www.audiosciencereview.com" }, - { - "domain": "golf.com" - }, { "domain": "thehustle.co" }, { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -98,6 +98,7 @@ "^https?:\\/\\/\\S+ampproject\\.org\\/\\S\\/s\\/(\\S+)$" ], "keywords": [ + "amp=", "=amp", "&", "amp&", @@ -113,7 +114,7 @@ ] }, "state": "enabled", - "hash": "369b8aac2cb358e0df3a828c1e3b9a29" + "hash": "a92fae2cdccf479cc1bbe840cd32627b" }, "androidBrowserConfig": { "exceptions": [], @@ -263,6 +264,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -271,6 +275,9 @@ }, { "domain": "sundancecatalog.com" + }, + { + "domain": "instagram.com" } ], "settings": { @@ -298,7 +305,7 @@ } } }, - "hash": "7b3ce9784ee7348bb7a07d1868c0f3ae" + "hash": "b72f756b119c762183d04867e8927892" }, "autofill": { "exceptions": [ @@ -956,6 +963,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -981,13 +991,16 @@ } }, "state": "disabled", - "hash": "36e8971fa9bb204b78a5929a14a108dd" + "hash": "9c70121360bcdfeb63770d8d9aeee770" }, "clickToPlay": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1008,7 +1021,7 @@ } }, "state": "disabled", - "hash": "4390af06f967ef97a827aeab0ac0d1ca" + "hash": "ba97e20bd75a4dcd4ef376ec9b7fccc1" }, "clientBrandHint": { "exceptions": [], @@ -1039,6 +1052,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1049,7 +1065,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "e37447d42ee8194f185e35e40f577f41" + "hash": "910e25ffe4d683b3c708a1578d097a16" }, "cookie": { "settings": { @@ -1094,6 +1110,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1105,7 +1124,7 @@ } ], "state": "disabled", - "hash": "37a27966915571085613911b47e6e2eb" + "hash": "7c7ceca9eeb664059750ea96938669b0" }, "customUserAgent": { "settings": { @@ -1229,6 +1248,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -3977,13 +3999,16 @@ ] }, "state": "enabled", - "hash": "01a63fb9be9c1708398761f62f5f9598" + "hash": "70c18c8d5c08210844cb238822888fd7" }, "exceptionHandler": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -3995,7 +4020,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "fingerprintingAudio": { "state": "disabled", @@ -4006,6 +4031,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4016,7 +4044,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "f25a8f2709e865c2bd743828c7ee2f77" + "hash": "40b13d6ca36cd3de287345ab9e5839fb" }, "fingerprintingBattery": { "exceptions": [ @@ -4026,6 +4054,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4037,7 +4068,7 @@ } ], "state": "enabled", - "hash": "440f8d663d59430c93d66208655d9238" + "hash": "038608803499bebc30460a84ed27579f" }, "fingerprintingCanvas": { "settings": { @@ -4131,6 +4162,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4142,7 +4176,7 @@ } ], "state": "disabled", - "hash": "ea4c565bae27996f0d651300d757594c" + "hash": "98b5e91ff539dfb6c81699e32b76f70c" }, "fingerprintingHardware": { "settings": { @@ -4188,6 +4222,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4199,7 +4236,7 @@ } ], "state": "enabled", - "hash": "46fbcd4738329731c1b11e88e3afcb7b" + "hash": "ed0d208ef9ffcba9851eddf68a005583" }, "fingerprintingScreenSize": { "settings": { @@ -4242,6 +4279,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4253,7 +4293,7 @@ } ], "state": "enabled", - "hash": "0fb22f84b750e0d29bad55bd95d9ce2b" + "hash": "264749fcf7f5e7e03478bb6f0df4a48a" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -4269,6 +4309,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4280,13 +4323,16 @@ } ], "state": "enabled", - "hash": "f1632b92379847c92c95bcffefbc1bd2" + "hash": "c8f4dcd850359636b47ebc31a26f1f1d" }, "googleRejected": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4298,7 +4344,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "gpc": { "state": "enabled", @@ -4336,6 +4382,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4358,7 +4407,7 @@ "privacy-test-pages.site" ] }, - "hash": "549a6e76edaf16c1fffced31b97e9553" + "hash": "d1dd05d2cbbb9425a925cc162aaa681f" }, "harmfulApis": { "settings": { @@ -4463,6 +4512,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4474,7 +4526,7 @@ } ], "state": "disabled", - "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" + "hash": "9d0f5f4f8c02e79246e2d809cada2fdb" }, "history": { "state": "enabled", @@ -4496,6 +4548,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4506,7 +4561,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "f772808ed34cc9ea8cbcbb7cdaf74429" + "hash": "ea6d5ad048e35c75c451bff6fe58cb11" }, "incontextSignup": { "exceptions": [], @@ -4544,6 +4599,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4562,7 +4620,7 @@ ] }, "state": "enabled", - "hash": "698de7b963d7d7942c5c5d1e986bb1b1" + "hash": "f8dc40f1f5687f403f381452d66eb0d0" }, "networkProtection": { "state": "enabled", @@ -4590,6 +4648,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4601,7 +4662,7 @@ } ], "state": "disabled", - "hash": "841fa92b9728c9754f050662678f82c7" + "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, "notificationPermissions": { "exceptions": [], @@ -4616,10 +4677,20 @@ "rollout": { "steps": [] } + }, + "toggleReports": { + "state": "internal", + "rollout": { + "steps": [ + { + "percent": 5 + } + ] + } } }, - "state": "disabled", - "hash": "dede7e70939822f5ecb9eb5fae577fa3" + "state": "enabled", + "hash": "0d76cb4a367fc6738f7c4aa6a66f0a04" }, "privacyProtectionsPopup": { "state": "disabled", @@ -4649,6 +4720,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4660,7 +4734,7 @@ } ], "state": "disabled", - "hash": "0d3df0f7c24ebde89d2dced4e2d34322" + "hash": "1679be76968fe50858b3cc664b8fcbad" }, "requestFilterer": { "state": "disabled", @@ -4668,6 +4742,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4681,7 +4758,7 @@ "settings": { "windowInMs": 0 }, - "hash": "0fff8017d8ea4b5609b8f5c110be1401" + "hash": "219a51a9aafbc9c1bae4bad55d7ce437" }, "runtimeChecks": { "state": "disabled", @@ -4689,6 +4766,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4700,13 +4780,16 @@ } ], "settings": {}, - "hash": "800a19533c728bbec7e31e466f898268" + "hash": "e2246d7c78df2167134e1428b04d51ca" }, "serviceworkerInitiatedRequests": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4718,7 +4801,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "sync": { "state": "enabled", @@ -5833,6 +5916,12 @@ "domains": [ "channel4.com" ] + }, + { + "rule": "7cbf2.v.fwmrm.net/ad/g/1", + "domains": [ + "6play.fr" + ] } ] }, @@ -7649,6 +7738,12 @@ "domains": [ "fashionnova.com" ] + }, + { + "rule": "rapid-cdn.yottaa.com/rapid/lib/ZfJxptseJcUQIA.js", + "domains": [ + "aviatornation.com" + ] } ] }, @@ -7718,6 +7813,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7728,7 +7826,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "86a7c891d57513e67356a82da2a2aa1d" + "hash": "6768849b4f63b2e635698ac9dde79aa3" }, "trackingCookies1p": { "settings": { @@ -7741,6 +7839,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7752,7 +7853,7 @@ } ], "state": "disabled", - "hash": "4dddf681372a2aea9788090b13db6e6f" + "hash": "bfd8b32efe8d633fe670bf6ab1b00240" }, "trackingCookies3p": { "settings": { @@ -7762,6 +7863,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7773,7 +7877,7 @@ } ], "state": "disabled", - "hash": "841fa92b9728c9754f050662678f82c7" + "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, "trackingParameters": { "exceptions": [ @@ -7783,6 +7887,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7826,7 +7933,7 @@ ] }, "state": "enabled", - "hash": "1df4ca1a649e81401fb5e872212b4dd0" + "hash": "f64c29121e46b2c79c23e8e7efc58c59" }, "userAgentRotation": { "settings": { @@ -7836,6 +7943,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7847,7 +7957,7 @@ } ], "state": "disabled", - "hash": "f65d10dfdf6739feab99a08d42734747" + "hash": "4498ff835bed7ce27ff2a568db599155" }, "voiceSearch": { "exceptions": [], @@ -7859,6 +7969,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7909,7 +8022,7 @@ } ] }, - "hash": "592a1fb6314f04875fc44a66ef7c2433" + "hash": "1b8acba9eed9ba83fdfe0da1e9d8db87" }, "windowsPermissionUsage": { "exceptions": [], @@ -7921,6 +8034,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7932,7 +8048,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "windowsWaitlist": { "exceptions": [], diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b59c5e2ac1..90780819e7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -689,6 +689,14 @@ B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9847BFFD27A2DDB400DB07AA /* ContentBlocking.swift */; }; B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DF11287C336E00C12A9C /* ContentBlockingUpdating.swift */; }; B652DF13287C373A00C12A9C /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DEFE287BF1FE00C12A9C /* ScriptSourceProviding.swift */; }; + B6A26C042B98358B00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C032B98358B00DF9EAD /* Macros */; }; + B6A26C062B98359A00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C052B98359A00DF9EAD /* Macros */; }; + B6A26C082B9835A000DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C072B9835A000DF9EAD /* Macros */; }; + B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C092B9835A800DF9EAD /* Macros */; }; + B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0B2B9835AD00DF9EAD /* Macros */; }; + B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0D2B9835B100DF9EAD /* Macros */; }; + B6A26C102B9835B400DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0F2B9835B400DF9EAD /* Macros */; }; + B6A26C122B9835B800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C112B9835B800DF9EAD /* Macros */; }; B6AD9E3628D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3428D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift */; }; B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3528D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift */; }; B6AD9E3828D4512E0019CDE9 /* EmbeddedTrackerDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9801F08927E4B21100191874 /* EmbeddedTrackerDataTests.swift */; }; @@ -697,18 +705,26 @@ B6BA95C528894A28004ABA20 /* BrowsingMenuViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */; }; B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; }; B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */; }; + BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; }; BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; }; BD862E052B30DB250073E2EE /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */; }; BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */; }; BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */; }; BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */; }; + BDA583872B98B6C700732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; + BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; + BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; }; + BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */; }; C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */; }; C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */; }; C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */; }; + C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */; }; + C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */; }; + C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */; }; C14882DA27F2011C00D59F0C /* BookmarksExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D727F2011C00D59F0C /* BookmarksExporter.swift */; }; C14882DC27F2011C00D59F0C /* BookmarksImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D927F2011C00D59F0C /* BookmarksImporter.swift */; }; C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882E127F20D9A00D59F0C /* BookmarksExporterTests.swift */; }; @@ -798,7 +814,6 @@ D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */; }; D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; }; D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */; }; - D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92C2B696945008E2FF2 /* Subscription.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; @@ -2346,11 +2361,13 @@ B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BrowsingMenuViewController.storyboard; sourceTree = ""; }; B6BA95E728924730004ABA20 /* JSAlertController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = JSAlertController.storyboard; sourceTree = ""; }; B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64DownloadSession.swift; sourceTree = ""; }; + BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = ""; }; BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackCategory.swift; sourceTree = ""; }; BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackSender.swift; sourceTree = ""; }; BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNMetadataCollector.swift; sourceTree = ""; }; BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = ""; }; + BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerExtension.swift; sourceTree = ""; }; BDC234F62B27F51100D3C798 /* UniquePixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquePixel.swift; sourceTree = ""; }; C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = ""; }; C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = ""; }; @@ -2358,6 +2375,9 @@ C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewModel.swift; sourceTree = ""; }; C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewController.swift; sourceTree = ""; }; C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSettingStatus.swift; sourceTree = ""; }; + C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptView.swift; sourceTree = ""; }; + C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptViewController.swift; sourceTree = ""; }; + C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptViewModel.swift; sourceTree = ""; }; C14882D727F2011C00D59F0C /* BookmarksExporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksExporter.swift; sourceTree = ""; }; C14882D927F2011C00D59F0C /* BookmarksImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksImporter.swift; sourceTree = ""; }; C14882E127F20D9A00D59F0C /* BookmarksExporterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksExporterTests.swift; sourceTree = ""; }; @@ -2462,7 +2482,6 @@ D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionITPViewModel.swift; sourceTree = ""; }; D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesFeature.swift; sourceTree = ""; }; - D668D92C2B696945008E2FF2 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; @@ -2690,6 +2709,7 @@ 0202569029881ECA00E694E7 /* CocoaAsyncSocket in Frameworks */, 02025664298818B200E694E7 /* NetworkExtension.framework in Frameworks */, 4B470EE4299C6DFB0086EBDC /* Core.framework in Frameworks */, + B6A26C062B98359A00DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2697,6 +2717,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2715,6 +2736,7 @@ 853273B624FFE0BB00E3C778 /* WidgetKit.framework in Frameworks */, 0238E44F29C0FAA100615E30 /* FindInPageIOSJSSupport in Frameworks */, 3760DFED299315EF0045A446 /* Waitlist in Frameworks */, + B6A26C042B98358B00DF9EAD /* Macros in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */, 31E69A63280F4CB600478327 /* DuckUI in Frameworks */, @@ -2730,6 +2752,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */, F486D3362506A037002D07D7 /* OHHTTPStubs in Frameworks */, F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */, F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, @@ -2760,6 +2783,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C102B9835B400DF9EAD /* Macros in Frameworks */, 1E1D8B632995143200C96994 /* OHHTTPStubs in Frameworks */, 1E1D8B652995143200C96994 /* OHHTTPStubsSwift in Frameworks */, ); @@ -2769,6 +2793,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */, F486D31D2506980E002D07D7 /* Swifter in Frameworks */, 85F21DC021123B03002631A6 /* Core.framework in Frameworks */, ); @@ -2779,6 +2804,7 @@ buildActionMask = 2147483647; files = ( 98D4B7DF2944DDBD0068814D /* Core.framework in Frameworks */, + B6A26C122B9835B800DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2787,6 +2813,7 @@ buildActionMask = 2147483647; files = ( 4B948E2629DCCDB9002531FA /* Persistence in Frameworks */, + B6A26C082B9835A000DF9EAD /* Macros in Frameworks */, 98A50962294B48A400D10880 /* Bookmarks in Frameworks */, 1E60989B290009C700A508F9 /* Common in Frameworks */, 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */, @@ -4421,6 +4448,14 @@ path = Feedback; sourceTree = ""; }; + BDA583852B98B69C00732FDC /* Subscription */ = { + isa = PBXGroup; + children = ( + BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */, + ); + name = Subscription; + sourceTree = ""; + }; C14882D627F2010700D59F0C /* ImportExport */ = { isa = PBXGroup; children = ( @@ -4470,6 +4505,16 @@ name = PasswordGeneration; sourceTree = ""; }; + C1AFFC4B2B8773060060448E /* AuthConfirmation */ = { + isa = PBXGroup; + children = ( + C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */, + C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */, + C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */, + ); + name = AuthConfirmation; + sourceTree = ""; + }; C1B7B51D28941F160098FD6A /* RemoteMessaging */ = { isa = PBXGroup; children = ( @@ -4638,7 +4683,6 @@ D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D668D92C2B696945008E2FF2 /* Subscription.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, @@ -4807,6 +4851,7 @@ children = ( EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */, EE9D68DD2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift */, + BD15DB842B959CFD00821457 /* BundleExtension.swift */, ); name = NetworkProtection; sourceTree = ""; @@ -5105,6 +5150,7 @@ F143C2E51E4A4CD400CFDE3A /* Core */ = { isa = PBXGroup; children = ( + BDA583852B98B69C00732FDC /* Subscription */, 858479C72B8792C900D156C1 /* History */, EE7A92852AC6DE2500832A36 /* NetworkProtection */, 4B470ED4299C484B0086EBDC /* AppTrackingProtection */, @@ -5607,6 +5653,7 @@ C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */, 319A370F28299A850079FBCE /* PasswordHider.swift */, 31C70B5428045E3500FB6AD1 /* SecureVaultErrorReporter.swift */, + C1AFFC4B2B8773060060448E /* AuthConfirmation */, F407605328131910006B1E0B /* AutofillLoginUI */, 310C4B4A281B69BC00BA79A9 /* Management */, C17B59552A03AAC40055F2D1 /* PasswordGeneration */, @@ -5675,6 +5722,7 @@ name = PacketTunnelProvider; packageProductDependencies = ( 0202568F29881ECA00E694E7 /* CocoaAsyncSocket */, + B6A26C052B98359A00DF9EAD /* Macros */, ); productName = PacketTunnelProvider; productReference = 02025662298818B100E694E7 /* PacketTunnelProvider.appex */; @@ -5695,6 +5743,9 @@ 025CCFE82582601C001CD5BB /* PBXTargetDependency */, ); name = FingerprintingUITests; + packageProductDependencies = ( + B6A26C0D2B9835B100DF9EAD /* Macros */, + ); productName = FingerprintingUITests; productReference = 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; @@ -5754,6 +5805,7 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, + B6A26C032B98358B00DF9EAD /* Macros */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -5779,6 +5831,7 @@ F486D3372506A225002D07D7 /* OHHTTPStubsSwift */, EEFAB4662A73C230008A38E4 /* NetworkProtectionTestUtils */, F115ED9B2B4EFC8E001A0453 /* TestUtils */, + B6A26C092B9835A800DF9EAD /* Macros */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -5842,6 +5895,7 @@ packageProductDependencies = ( 1E1D8B622995143200C96994 /* OHHTTPStubs */, 1E1D8B642995143200C96994 /* OHHTTPStubsSwift */, + B6A26C0F2B9835B400DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 85D33FCB25C97B6E002B91A6 /* IntegrationTests.xctest */; @@ -5864,6 +5918,7 @@ name = AtbUITests; packageProductDependencies = ( F486D31C2506980E002D07D7 /* Swifter */, + B6A26C0B2B9835AD00DF9EAD /* Macros */, ); productName = AtbIntegrationTests; productReference = 85F21DAD210F5E32002631A6 /* AtbUITests.xctest */; @@ -5885,6 +5940,7 @@ ); name = PerformanceTests; packageProductDependencies = ( + B6A26C112B9835B800DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */; @@ -5937,6 +5993,7 @@ EE8E56892A56BCE400F11DCA /* NetworkProtection */, D61CDA152B7CF77300A0FBB9 /* Subscription */, D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, + B6A26C072B9835A000DF9EAD /* Macros */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -6467,6 +6524,7 @@ 02025AD42988229800E694E7 /* ProxySocket.swift in Sources */, 02025AD62988229800E694E7 /* SocketProtocol.swift in Sources */, 02025AD82988229800E694E7 /* Tunnel.swift in Sources */, + BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */, 02025ADA2988229800E694E7 /* Port.swift in Sources */, 02025ADB2988229800E694E7 /* HTTPStreamScanner.swift in Sources */, 02025ADC2988229800E694E7 /* UInt128.swift in Sources */, @@ -6739,6 +6797,7 @@ D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */, + BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, @@ -6876,6 +6935,7 @@ 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, + C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */, @@ -6883,6 +6943,7 @@ F17922DB1E717C8D006E3D97 /* Suggestion.swift in Sources */, 020108A729A6ABF600644F9D /* AppTPToggleView.swift in Sources */, 02A54A982A093126000C8FED /* AppTPHomeViewModel.swift in Sources */, + C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */, F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */, 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, @@ -6895,6 +6956,8 @@ 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, + BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */, + C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, EE4BE0092A740BED00CD6AA8 /* ClearTextField.swift in Sources */, @@ -6935,7 +6998,6 @@ EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, - D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */, AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, @@ -7251,6 +7313,7 @@ 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */, F1134EB01F40AC6300B73467 /* AtbParser.swift in Sources */, EE50052E29C369D300AE0773 /* FeatureFlag.swift in Sources */, + BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */, 37DF000F29F9D635002B7D3E /* SyncBookmarksAdapter.swift in Sources */, B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */, 4BE2756827304F57006B20B0 /* URLRequestExtension.swift in Sources */, @@ -7291,6 +7354,7 @@ B652DF0D287C2A6300C12A9C /* PrivacyFeatures.swift in Sources */, F10E522D1E946F8800CE1253 /* NSAttributedStringExtension.swift in Sources */, 9887DC252354D2AA005C85F5 /* Database.swift in Sources */, + BDA583872B98B6C700732FDC /* AccountManagerExtension.swift in Sources */, F143C3171E4A99D200CFDE3A /* AppURLs.swift in Sources */, C1963863283794A000298D4D /* BookmarksCachingSearch.swift in Sources */, ); @@ -8226,7 +8290,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8263,7 +8327,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8355,7 +8419,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8383,7 +8447,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8533,7 +8597,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8559,7 +8623,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8624,7 +8688,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8659,7 +8723,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8693,7 +8757,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8724,7 +8788,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9011,7 +9075,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9042,7 +9106,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9071,7 +9135,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9105,7 +9169,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9136,7 +9200,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9169,11 +9233,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9407,7 +9471,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9434,7 +9498,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9467,7 +9531,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9505,7 +9569,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9541,7 +9605,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9576,11 +9640,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9754,11 +9818,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9787,10 +9851,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9997,7 +10061,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.1.0; + version = 119.0.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10005,7 +10069,7 @@ repositoryURL = "https://github.com/duckduckgo/apple-toolbox.git"; requirement = { kind = exactVersion; - version = 1.0.0; + version = 2.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { @@ -10029,7 +10093,7 @@ repositoryURL = "https://github.com/onevcat/Kingfisher.git"; requirement = { kind = exactVersion; - version = 7.6.2; + version = 7.11.0; }; }; F486D2FD25069744002D07D7 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { @@ -10156,6 +10220,46 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; + B6A26C032B98358B00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C052B98359A00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C072B9835A000DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C092B9835A800DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0B2B9835AD00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0D2B9835B100DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0F2B9835B400DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C112B9835B800DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; B6F997CB2B8F380A00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 018b6e092f..9e06e3edb7 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/apple-toolbox.git", "state" : { - "revision" : "e3dc4faf70ca09718a2d20d5c47b449389e8c153", - "version" : "1.0.0" + "revision" : "d51beaf1736013b530576ace13a16d6d1a63742c", + "version" : "2.0.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", - "version" : "114.1.0" + "revision" : "a32ff4084e910354bdd948812793de6b04804a1b", + "version" : "119.0.1" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "a3690b7666a3617693383d948cb492513f6aa569", - "version" : "5.0.0" + "revision" : "f6241631fc14cc2d0f47950bfdc4d6c30bf90130", + "version" : "5.4.0" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/onevcat/Kingfisher.git", "state" : { - "revision" : "af4be924ad984cf4d16f4ae4df424e79a443d435", - "version" : "7.6.2" + "revision" : "5b92f029fab2cce44386d28588098b5be0824ef5", + "version" : "7.11.0" } }, { @@ -135,6 +135,15 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", + "version" : "509.1.1" + } + }, { "identity" : "swifter", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift index a9d3d31a80..6193491b4f 100644 --- a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift +++ b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift @@ -19,6 +19,7 @@ import AdServices import Common +import Macros protocol AdAttributionFetcher { func fetch() async -> AdServicesAttributionResponse? @@ -116,7 +117,7 @@ struct DefaultAdAttributionFetcher: AdAttributionFetcher { } private struct Constant { - static let attributionServiceURL = URL(string: "https://api-adservices.apple.com/api/v1/")! + static let attributionServiceURL = #URL("https://api-adservices.apple.com/api/v1/") static let maxRetries = 3 } } diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index db54ce6826..7fae089578 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -22,6 +22,7 @@ import Core import BackgroundTasks import NetworkProtection import Waitlist +import BrowserServicesKit extension AppDelegate { @@ -51,12 +52,17 @@ extension AppDelegate { VPNWaitlist.shared.fetchInviteCodeIfAvailable { [weak self] error in guard error == nil else { -#if !DEBUG + if error == .alreadyHasInviteCode, UIApplication.shared.applicationState == .active { // If the user already has an invite code but their auth token has gone missing, attempt to redeem it again. let tokenStore = NetworkProtectionKeychainTokenStore() let waitlistStorage = VPNWaitlist.shared.waitlistStorage - if let inviteCode = waitlistStorage.getWaitlistInviteCode(), !tokenStore.isFeatureActivated { + let configManager = ContentBlocking.shared.privacyConfigurationManager + let waitlistBetaActive = configManager.privacyConfig.isSubfeatureEnabled(NetworkProtectionSubfeature.waitlistBetaActive) + + if let inviteCode = waitlistStorage.getWaitlistInviteCode(), + !tokenStore.isFeatureActivated, + waitlistBetaActive { let pixel: Pixel.Event = .networkProtectionWaitlistRetriedInviteCodeRedemption do { @@ -72,7 +78,7 @@ extension AppDelegate { self?.fetchVPNWaitlistAuthToken(inviteCode: inviteCode) } } -#endif + return } diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 3bd12868ed..b0b88edc4a 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -74,6 +74,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() + private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults #endif private var autoClear: AutoClear? @@ -301,12 +302,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() #endif -#if NETWORK_PROTECTION && SUBSCRIPTION - if VPNSettings(defaults: .networkProtectionGroupDefaults).showEntitlementAlert { - presentExpiredEntitlementAlert() - } -#endif - RemoteMessaging.registerBackgroundRefreshTaskHandler( bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: AppDependencyProvider.shared.appSettings.favoritesDisplayMode @@ -354,13 +349,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window?.rootViewController?.present(alertController, animated: true, completion: nil) } +#if NETWORK_PROTECTION private func presentExpiredEntitlementAlert() { - let alertController = CriticalAlerts.makeExpiredEntitlementAlert() - window?.rootViewController?.present(alertController, animated: true) { - VPNSettings(defaults: .networkProtectionGroupDefaults).apply(change: .setShowEntitlementAlert(false)) + let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in + self?.mainViewController?.segueToPrivacyPro() + } + window?.rootViewController?.present(alertController, animated: true) { [weak self] in + self?.tunnelDefaults.showEntitlementAlert = false } } + private func presentExpiredEntitlementNotification() { + let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( + settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + defaults: .networkProtectionGroupDefaults, + wrappee: NetworkProtectionUNNotificationPresenter() + ) + presenter.showEntitlementNotification() + } +#endif + private func cleanUpMacPromoExperiment2() { UserDefaults.standard.removeObject(forKey: "com.duckduckgo.ios.macPromoMay23.exp2.cohort") } @@ -399,6 +407,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard !testing else { return } syncService.initializeIfNeeded() + if syncService.authState == .active && + (InternalUserStore().isInternalUser == false && syncService.serverEnvironment == .development) { + UniquePixel.fire(pixel: .syncWrongEnvironment) + } syncDataProviders.setUpDatabaseCleanersIfNeeded(syncService: syncService) if !(overlayWindow?.rootViewController is AuthenticationViewController) { @@ -441,6 +453,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION widgetRefreshModel.refreshVPNWidget() + + if tunnelDefaults.showEntitlementAlert { + presentExpiredEntitlementAlert() + } + + presentExpiredEntitlementNotification() #endif } diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 446bd75fc3..7be4a03d4e 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -22,7 +22,6 @@ import Bookmarks import Core import WidgetKit -// swiftlint:disable file_length public class AppUserDefaults: AppSettings { public struct Notifications { @@ -291,29 +290,8 @@ public class AppUserDefaults: AppSettings { } } - var autoconsentEnabled: Bool { - get { - // Use settings value if present - if let isEnabled = autoconsentEnabledSetting { - return isEnabled - } - - // Use onByDefault rollout otherwise - return featureFlagger.isFeatureOn(.autoconsentOnByDefault) - } - - set { - autoconsentEnabledSetting = newValue - } - } - - // Only for testing and `DebugViewController` purposes - func clearAutoconsentUserSetting() { - autoconsentEnabledSetting = nil - } - - @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: false) - private var autoconsentEnabledSetting: Bool? + @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: true) + var autoconsentEnabled: Bool var inspectableWebViewEnabled: Bool { get { diff --git a/DuckDuckGo/AuthConfirmationPromptView.swift b/DuckDuckGo/AuthConfirmationPromptView.swift new file mode 100644 index 0000000000..6c8a157718 --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptView.swift @@ -0,0 +1,130 @@ +// +// AuthConfirmationPromptView.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct AuthConfirmationPromptView: View { + + @State var frame: CGSize = .zero + @ObservedObject var viewModel: AuthConfirmationPromptViewModel + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.verticalSizeClass) var verticalSizeClass + + var body: some View { + GeometryReader { geometry in + makeBodyView(geometry) + } + } + + private func makeBodyView(_ geometry: GeometryProxy) -> some View { + DispatchQueue.main.async { self.frame = geometry.size } + + return VStack { + HStack { + Button { + viewModel.cancelButtonPressed() + } label: { + Text(UserText.actionCancel) + .foregroundColor(Color(designSystemColor: .textSecondary)) + } + Spacer() + } + .padding([.top, .leading], Const.Size.closeButtonPadding) + + Group { + Spacer() + .frame(height: Const.Size.topPadding) + Image + .lock + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Headline(title: UserText.autofillDeleteAllPasswordsAuthenticationPromptTitle) + contentViewSpacer + AutofillViews.PrimaryButton(title: UserText.autofillDeleteAllPasswordsAuthenticationPromptButton, + action: { viewModel.authenticatePressed() }) + .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad + : Const.Size.bottomPadding) + } + .padding(.horizontal, horizontalPadding) + } + .background(GeometryReader { proxy -> Color in + DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } + return Color.clear + }) + .useScrollView(shouldUseScrollView(), minHeight: frame.height) + } + + private var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.horizontalPaddingPortraitSmallFrame + } else { + return Const.Size.horizontalPaddingPortrait + } + } else { + return Const.Size.horizontalPadding + } + } + + private var contentViewSpacer: some View { + VStack { + if AutofillViews.isIPhoneLandscape(verticalSizeClass) { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeightLandscape) + } else { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeight) + } + } + } + + private func shouldUseScrollView() -> Bool { + var useScrollView: Bool = false + + if #available(iOS 16.0, *) { + useScrollView = AutofillViews.contentHeightExceedsScreenHeight(viewModel.contentHeight) + } else { + useScrollView = viewModel.contentHeight > frame.height + Const.Size.ios15scrollOffset + } + + return useScrollView + } +} + +private enum Const { + enum Size { + static let closeButtonPadding: CGFloat = 16.0 + static let horizontalPadding: CGFloat = 48.0 + static let horizontalPaddingPortrait: CGFloat = 44.0 + static let horizontalPaddingPortraitSmallFrame: CGFloat = 16.0 + static let topPadding: CGFloat = 36.0 + static let headlineTopPadding: CGFloat = 24.0 + static let ios15scrollOffset: CGFloat = 80.0 + static let contentSpacerHeight: CGFloat = 44.0 + static let contentSpacerHeightLandscape: CGFloat = 50.0 + static let bottomPadding: CGFloat = 12.0 + static let bottomPaddingIPad: CGFloat = 24.0 + } +} + +private extension Image { + static let lock = Image("AutofillLock") +} + +#Preview { + AuthConfirmationPromptView(viewModel: AuthConfirmationPromptViewModel()) +} diff --git a/DuckDuckGo/AuthConfirmationPromptViewController.swift b/DuckDuckGo/AuthConfirmationPromptViewController.swift new file mode 100644 index 0000000000..fabcf3273c --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptViewController.swift @@ -0,0 +1,106 @@ +// +// AuthConfirmationPromptViewController.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import SwiftUI + +final class AuthConfirmationPromptViewController: UIViewController { + + typealias AuthConfirmationCompletion = (_ authenticated: Bool) -> Void + + private let didBeginAuthenticating: () -> Void + private let authConfirmationCompletion: AuthConfirmationCompletion + + private var viewModel: AuthConfirmationPromptViewModel? + + init(didBeginAuthenticating: @escaping () -> Void, + authConfirmationCompletion: @escaping AuthConfirmationCompletion) { + self.didBeginAuthenticating = didBeginAuthenticating + self.authConfirmationCompletion = authConfirmationCompletion + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor(named: "AutofillPromptLargeBackground") + + setupAuthConfirmationPromptView() + } + + private func setupAuthConfirmationPromptView() { + let authConfirmationPromptViewModel = AuthConfirmationPromptViewModel() + authConfirmationPromptViewModel.delegate = self + viewModel = authConfirmationPromptViewModel + + let authConfirmationPromptView = AuthConfirmationPromptView(viewModel: authConfirmationPromptViewModel) + let controller = UIHostingController(rootView: authConfirmationPromptView) + controller.view.backgroundColor = .clear + presentationController?.delegate = self + installChildViewController(controller) + } + +} + +// MARK: UISheetPresentationControllerDelegate + +extension AuthConfirmationPromptViewController: UISheetPresentationControllerDelegate { + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + authConfirmationCompletion(false) + } + +} + +// MARK: AuthConfirmationPromptViewModelDelegate + +extension AuthConfirmationPromptViewController: AuthConfirmationPromptViewModelDelegate { + + func authConfirmationPromptViewModelDidBeginAuthenticating(_ viewModel: AuthConfirmationPromptViewModel) { + didBeginAuthenticating() + } + + func authConfirmationPromptViewModelDidAuthenticate(_ viewModel: AuthConfirmationPromptViewModel, success: Bool) { + dismiss(animated: true) { + self.authConfirmationCompletion(success) + } + } + + func authConfirmationPromptViewModelDidCancel(_ viewModel: AuthConfirmationPromptViewModel) { + dismiss(animated: true) { + self.authConfirmationCompletion(false) + } + } + + func authConfirmationPromptViewModelDidResizeContent(_ viewModel: AuthConfirmationPromptViewModel, contentHeight: CGFloat) { + if #available(iOS 16.0, *) { + if let sheetPresentationController = self.presentationController as? UISheetPresentationController { + sheetPresentationController.animateChanges { + sheetPresentationController.detents = [.custom(resolver: { _ in contentHeight })] + } + } + } + } + +} diff --git a/DuckDuckGo/AuthConfirmationPromptViewModel.swift b/DuckDuckGo/AuthConfirmationPromptViewModel.swift new file mode 100644 index 0000000000..0ac7cadc50 --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptViewModel.swift @@ -0,0 +1,60 @@ +// +// AuthConfirmationPromptViewModel.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol AuthConfirmationPromptViewModelDelegate: AnyObject { + func authConfirmationPromptViewModelDidBeginAuthenticating(_ viewModel: AuthConfirmationPromptViewModel) + func authConfirmationPromptViewModelDidAuthenticate(_ viewModel: AuthConfirmationPromptViewModel, success: Bool) + func authConfirmationPromptViewModelDidCancel(_ viewModel: AuthConfirmationPromptViewModel) + func authConfirmationPromptViewModelDidResizeContent(_ viewModel: AuthConfirmationPromptViewModel, contentHeight: CGFloat) +} + +final class AuthConfirmationPromptViewModel: ObservableObject { + + weak var delegate: AuthConfirmationPromptViewModelDelegate? + private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillDeleteAllPasswordsAuthenticationReason) + + var contentHeight: CGFloat = AutofillViews.deleteAllPromptMinHeight { + didSet { + guard contentHeight != oldValue else { + return + } + delegate?.authConfirmationPromptViewModelDidResizeContent(self, + contentHeight: max(contentHeight, AutofillViews.deleteAllPromptMinHeight)) + } + } + + func authenticatePressed() { + delegate?.authConfirmationPromptViewModelDidBeginAuthenticating(self) + + authenticator.authenticate { [weak self] error in + self?.authCompleted(with: error == nil) + } + } + + func cancelButtonPressed() { + delegate?.authConfirmationPromptViewModelDidCancel(self) + } + + private func authCompleted(with success: Bool) { + delegate?.authConfirmationPromptViewModelDidAuthenticate(self, success: success) + } + +} diff --git a/DuckDuckGo/AutofillLoginDetailsView.swift b/DuckDuckGo/AutofillLoginDetailsView.swift index 589d858324..ba9eee9654 100644 --- a/DuckDuckGo/AutofillLoginDetailsView.swift +++ b/DuckDuckGo/AutofillLoginDetailsView.swift @@ -291,8 +291,8 @@ struct AutofillLoginDetailsView: View { let deleteAction = ActionSheet.Button.destructive(Text(UserText.autofillLoginDetailsDeleteConfirmationButtonTitle)) { viewModel.delete() } - return ActionSheet(title: Text(UserText.autofillLoginDetailsDeleteConfirmationTitle), - message: nil, + return ActionSheet(title: Text(UserText.autofillDeleteAllPasswordsActionTitle(for: 1)), + message: Text(viewModel.deleteMessage()), buttons: [deleteAction, ActionSheet.Button.cancel()]) }) .foregroundColor(Color.red) diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index c44a48f94f..803e5d1cce 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -22,6 +22,7 @@ import SwiftUI import BrowserServicesKit import Common import Combine +import DDGSync protocol AutofillLoginDetailsViewControllerDelegate: AnyObject { func autofillLoginDetailsViewControllerDidSave(_ controller: AutofillLoginDetailsViewController, account: SecureVaultModels.WebsiteAccount?) @@ -69,8 +70,8 @@ class AutofillLoginDetailsViewController: UIViewController { constant: 144) }() - init(authenticator: AutofillLoginListAuthenticator, account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, authenticationNotRequired: Bool = false) { - self.viewModel = AutofillLoginDetailsViewModel(account: account, tld: tld) + init(authenticator: AutofillLoginListAuthenticator, syncService: DDGSyncing, account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, authenticationNotRequired: Bool = false) { + self.viewModel = AutofillLoginDetailsViewModel(account: account, syncService: syncService, tld: tld) self.authenticator = authenticator self.authenticationNotRequired = authenticationNotRequired super.init(nibName: nil, bundle: nil) diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 0e9ec04872..5aacbeea91 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -19,13 +19,15 @@ // swiftlint:disable file_length -import Foundation import BrowserServicesKit import Common -import SwiftUI import Core +import DDGSync import DesignResourcesKit +import Foundation +import Macros import SecureStorage +import SwiftUI protocol AutofillLoginDetailsViewModelDelegate: AnyObject { func autofillLoginDetailsViewModelDidSave() @@ -57,12 +59,13 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } enum Constants { - static let privateEmailURL = URL(string: "https://duckduckgo.com/email")! + static let privateEmailURL = #URL("https://duckduckgo.com/email") } weak var delegate: AutofillLoginDetailsViewModelDelegate? var account: SecureVaultModels.WebsiteAccount? var emailManager: EmailManager + private let syncService: DDGSyncing private let tld: TLD private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher() @@ -178,9 +181,11 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } internal init(account: SecureVaultModels.WebsiteAccount? = nil, + syncService: DDGSyncing, tld: TLD, emailManager: EmailManager = EmailManager()) { self.account = account + self.syncService = syncService self.tld = tld self.headerViewModel = AutofillLoginDetailsHeaderViewModel() self.emailManager = emailManager @@ -228,7 +233,15 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } } } - + + func deleteMessage() -> String { + if syncService.authState == .inactive { + return UserText.autofillDeleteAllPasswordsActionMessage(for: 1) + } else { + return UserText.autofillDeleteAllPasswordsSyncActionMessage(for: 1) + } + } + func copyToPasteboard(_ action: PasteboardCopyAction) { var message = "" switch action { diff --git a/DuckDuckGo/AutofillLoginListViewModel.swift b/DuckDuckGo/AutofillLoginListViewModel.swift index 20abb86e80..bead3e9829 100644 --- a/DuckDuckGo/AutofillLoginListViewModel.swift +++ b/DuckDuckGo/AutofillLoginListViewModel.swift @@ -50,6 +50,7 @@ internal enum EnableAutofillRows: Int, CaseIterable { case resetNeverPromptWebsites } +// swiftlint:disable file_length type_body_length final class AutofillLoginListViewModel: ObservableObject { enum ViewState { @@ -63,8 +64,13 @@ final class AutofillLoginListViewModel: ObservableObject { let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) var isSearching: Bool = false + var isEditing: Bool = false { + didSet { + sections = makeSections(with: accounts) + } + } var authenticationNotRequired = false - private var accounts = [SecureVaultModels.WebsiteAccount]() + @Published private var accounts = [SecureVaultModels.WebsiteAccount]() private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]() private var cancellables: Set = [] private var appSettings: AppSettings @@ -87,6 +93,16 @@ final class AutofillLoginListViewModel: ObservableObject { var hasAccountsSaved: Bool { return !accounts.isEmpty } + + var accountsCount: Int { + accounts.count + } + + var accountsCountPublisher: AnyPublisher { + $accounts + .map { $0.count } + .eraseToAnyPublisher() + } var isAutofillEnabledInSettings: Bool { get { appSettings.autofillCredentialsEnabled } @@ -124,6 +140,10 @@ final class AutofillLoginListViewModel: ObservableObject { return false } + func deleteAllCredentials() -> Bool { + return deleteAll() + } + func undoLastDelete() { guard let cachedDeletedCredentials = cachedDeletedCredentials else { return @@ -134,7 +154,17 @@ final class AutofillLoginListViewModel: ObservableObject { func clearUndoCache() { cachedDeletedCredentials = nil } - + + func clearAllAccounts() { + accounts = [] + accountsToSuggest = [] + sections = makeSections(with: accounts) + } + + func undoClearAllAccounts() { + updateData() + } + func lockUI() { authenticationNotRequired = !hasAccountsSaved authenticator.logOut() @@ -233,7 +263,9 @@ final class AutofillLoginListViewModel: ObservableObject { var newSections = [AutofillLoginListSectionType]() if !isSearching { - newSections.append(.enableAutofill) + if !isEditing { + newSections.append(.enableAutofill) + } if !accountsToSuggest.isEmpty { let accountItems = accountsToSuggest.map { AutofillLoginListItemViewModel(account: $0, @@ -278,6 +310,8 @@ final class AutofillLoginListViewModel: ObservableObject { } else { newViewState = .searching } + } else if isEditing { + newViewState = sections.count >= 1 ? .showItems : .empty } else { newViewState = sections.count > 1 ? .showItems : .empty } @@ -338,7 +372,21 @@ final class AutofillLoginListViewModel: ObservableObject { Pixel.fire(pixel: .secureVaultError, error: error) } } + + @discardableResult + private func deleteAll() -> Bool { + guard let secureVault = secureVault else { return false } + + do { + try secureVault.deleteAllWebsiteCredentials() + return true + } catch { + Pixel.fire(pixel: .secureVaultError, error: error) + return false + } + } } +// swiftlint:enable type_body_length extension AutofillLoginListItemViewModel: Comparable { static func < (lhs: AutofillLoginListItemViewModel, rhs: AutofillLoginListItemViewModel) -> Bool { diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index c3b6e26c05..7b90ad5868 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -55,7 +55,34 @@ final class AutofillLoginSettingsListViewController: UIViewController { target: self, action: #selector(addButtonPressed)) }() - + + private lazy var deleteAllButtonItem: UIBarButtonItem = { + let button = UIBarButtonItem(title: UserText.autofillLoginListToolbarDeleteAllButton, + style: .plain, + target: self, + action: #selector(deleteAll)) + button.tintColor = .systemRed + return button + }() + + private lazy var accountsCountLabel: UILabel = { + let label = UILabel() + label.font = .daxCaption() + label.textColor = UIColor(designSystemColor: .textSecondary) + label.text = UserText.autofillLoginListToolbarPasswordsCount(viewModel.accountsCount) + return label + }() + + private lazy var accountsCountButtonItem: UIBarButtonItem = { + let item = UIBarButtonItem(customView: accountsCountLabel) + return item + }() + + private lazy var flexibleSpace: UIBarButtonItem = { + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + return space + }() + private var cancellables: Set = [] private lazy var searchController: UISearchController = { let searchController = UISearchController(searchResultsController: nil) @@ -78,8 +105,6 @@ final class AutofillLoginSettingsListViewController: UIViewController { tableView.registerCell(ofType: AutofillListItemTableViewCell.self) tableView.registerCell(ofType: EnableAutofillSettingsTableViewCell.self) tableView.registerCell(ofType: AutofillNeverSavedTableViewCell.self) - // Have to set tableHeaderView height otherwise tableView content will jump when adding / removing searchController due to tableView insetGrouped style - tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 24)) return tableView }() @@ -102,7 +127,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { multiplier: 1, constant: (tableView.frame.height / 2)) }() - + var selectedAccount: SecureVaultModels.WebsiteAccount? init(appSettings: AppSettings, currentTabUrl: URL? = nil, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, selectedAccount: SecureVaultModels.WebsiteAccount?) { @@ -156,6 +181,9 @@ final class AutofillLoginSettingsListViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + if isMovingFromParent { + navigationController?.isToolbarHidden = true + } if viewModel.authenticator.canAuthenticate() && viewModel.authenticator.state == .loggedIn { AppDependencyProvider.shared.autofillLoginSession.startSession() } @@ -192,13 +220,19 @@ final class AutofillLoginSettingsListViewController: UIViewController { tableView.setEditing(editing, animated: animated) + // trigger re-build of table sections + viewModel.isEditing = editing + tableView.reloadData() + updateNavigationBarButtons() updateSearchController() + updateToolbar() } - + @objc func addButtonPressed() { let detailsController = AutofillLoginDetailsViewController(authenticator: viewModel.authenticator, + syncService: syncService, tld: tld, authenticationNotRequired: viewModel.authenticationNotRequired) detailsController.delegate = self @@ -209,6 +243,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { func makeAccountDetailsScreen(_ account: SecureVaultModels.WebsiteAccount) -> AutofillLoginDetailsViewController { let detailsController = AutofillLoginDetailsViewController(authenticator: viewModel.authenticator, + syncService: syncService, account: account, tld: tld, authenticationNotRequired: viewModel.authenticationNotRequired) @@ -253,24 +288,31 @@ final class AutofillLoginSettingsListViewController: UIViewController { self?.tableView.reloadData() } .store(in: &cancellables) + + viewModel.accountsCountPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateToolbarLabel() + } + .store(in: &cancellables) + } private func configureNotification() { - let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, - selector: #selector(appWillMoveToForegroundCallback), - name: UIApplication.willEnterForegroundNotification, object: nil) - - notificationCenter.addObserver(self, - selector: #selector(appWillMoveToBackgroundCallback), - name: UIApplication.willResignActiveNotification, object: nil) + addObserver(for: UIApplication.didBecomeActiveNotification, selector: #selector(appDidBecomeActiveCallback)) + addObserver(for: UIApplication.willResignActiveNotification, selector: #selector(appWillResignActiveCallback)) + addObserver(for: AutofillLoginListAuthenticator.Notifications.invalidateContext, selector: #selector(authenticatorInvalidateContext)) + } - notificationCenter.addObserver(self, - selector: #selector(authenticatorInvalidateContext), - name: AutofillLoginListAuthenticator.Notifications.invalidateContext, object: nil) + private func addObserver(for notification: Notification.Name, selector: Selector) { + NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: nil) } - - @objc private func appWillMoveToForegroundCallback() { + + private func removeObserver(for notification: Notification.Name) { + NotificationCenter.default.removeObserver(self, name: notification, object: nil) + } + + @objc private func appDidBecomeActiveCallback() { // AutofillLoginDetailsViewController will handle calling authenticate() if it is the top view controller guard navigationController?.topViewController is AutofillLoginDetailsViewController else { authenticate() @@ -278,7 +320,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } - @objc private func appWillMoveToBackgroundCallback() { + @objc private func appWillResignActiveCallback() { viewModel.lockUI() } @@ -324,7 +366,83 @@ final class AutofillLoginSettingsListViewController: UIViewController { userInfo: [FireproofFaviconUpdater.UserInfoKeys.faviconDomain: domain]) }) } - + + @objc private func deleteAll() { + let message = self.syncService.authState == .inactive ? UserText.autofillDeleteAllPasswordsActionMessage(for: viewModel.accountsCount) + : UserText.autofillDeleteAllPasswordsSyncActionMessage(for: viewModel.accountsCount) + let alert = UIAlertController(title: UserText.autofillDeleteAllPasswordsActionTitle(for: viewModel.accountsCount), + message: message, + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: UserText.actionCancel, style: .cancel)) + let deleteAllAction = UIAlertAction(title: UserText.actionDelete, style: .destructive) {[weak self] _ in + self?.presentAuthConfirmationPrompt() + } + alert.addAction(deleteAllAction) + alert.preferredAction = deleteAllAction + present(controller: alert, fromView: tableView) + } + + private func presentAuthConfirmationPrompt() { + let authConfirmationPromptViewController = AuthConfirmationPromptViewController( + didBeginAuthenticating: { [weak self] in + self?.configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: true) + }, authConfirmationCompletion: { [weak self] authenticated in + self?.configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: false) + + if authenticated { + let accountsCount = self?.viewModel.accountsCount ?? 0 + self?.viewModel.clearAllAccounts() + self?.presentDeleteAllConfirmation(accountsCount) + } + } + ) + + if #available(iOS 15.0, *) { + if let presentationController = authConfirmationPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.deleteAllPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] + } + } + } + + present(authConfirmationPromptViewController, animated: true) + } + + private func configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: Bool) { + if isAuthenticating { + addObserver(for: UIApplication.didEnterBackgroundNotification, selector: #selector(appWillResignActiveCallback)) + removeObserver(for: UIApplication.willResignActiveNotification) + } else { + addObserver(for: UIApplication.willResignActiveNotification, selector: #selector(appWillResignActiveCallback)) + removeObserver(for: UIApplication.didEnterBackgroundNotification) + } + } + + private func presentDeleteAllConfirmation(_ numberOfAccounts: Int) { + var shouldDeleteAccounts = true + + ActionMessageView.present(message: UserText.autofillAllPasswordsDeletedToastMessage(for: numberOfAccounts), + actionTitle: UserText.actionGenericUndo, + presentationLocation: .withoutBottomBar, + onAction: { + shouldDeleteAccounts = false + }, onDidDismiss: { + if shouldDeleteAccounts { + if self.viewModel.deleteAllCredentials() { + self.syncService.scheduler.notifyDataChanged() + self.viewModel.resetNeverPromptWebsites() + self.viewModel.updateData() + } + } else { + self.viewModel.undoClearAllAccounts() + } + }) + } + // MARK: Subviews Setup private func updateViewState() { @@ -370,6 +488,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } updateNavigationBarButtons() updateSearchController() + updateToolbar() tableView.reloadData() } @@ -424,6 +543,24 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } + private func updateToolbar() { + if tableView.isEditing && viewModel.viewState == .showItems { + updateToolbarLabel() + navigationController?.isToolbarHidden = false + toolbarItems = [deleteAllButtonItem, flexibleSpace, accountsCountButtonItem, flexibleSpace] + } else { + toolbarItems?.removeAll() + navigationController?.isToolbarHidden = true + } + } + + private func updateToolbarLabel() { + guard tableView.isEditing else { return } + + accountsCountLabel.text = UserText.autofillLoginListToolbarPasswordsCount(viewModel.accountsCount) + accountsCountLabel.sizeToFit() + } + private func installSubviews() { view.addSubview(tableView) tableView.addSubview(emptySearchView) diff --git a/DuckDuckGo/AutofillViews.swift b/DuckDuckGo/AutofillViews.swift index aae551c98f..633d2fe261 100644 --- a/DuckDuckGo/AutofillViews.swift +++ b/DuckDuckGo/AutofillViews.swift @@ -29,6 +29,7 @@ struct AutofillViews { static let updateUsernameMinHeight = 310.0 static let passwordGenerationMinHeight: CGFloat = 310.0 static let emailSignupPromptMinHeight: CGFloat = 260.0 + static let deleteAllPromptMinHeight: CGFloat = 360.0 struct CloseButtonHeader: View { let action: () -> Void diff --git a/DuckDuckGo/ConfigurationURLDebugViewController.swift b/DuckDuckGo/ConfigurationURLDebugViewController.swift index f508a40393..7a6e970cc4 100644 --- a/DuckDuckGo/ConfigurationURLDebugViewController.swift +++ b/DuckDuckGo/ConfigurationURLDebugViewController.swift @@ -127,7 +127,7 @@ final class ConfigurationURLDebugViewController: UITableViewController { cell.subtitle.text = url(for: row) cell.subtitle.textColor = customURL(for: row) != nil ? UIColor(designSystemColor: .accent) : .black cell.ternary.text = lastConfigurationUpdateDate != nil ? dateFormatter.string(from: lastConfigurationUpdateDate!) : "-" - cell.refresh.addAction(makeAction(for: row), for: .allEvents) + cell.refresh.addAction(refreshAction, for: .primaryActionTriggered) return cell } @@ -136,12 +136,10 @@ final class ConfigurationURLDebugViewController: UITableViewController { presentCustomURLAlert(for: row) } - private func makeAction(for row: CustomURLsRows) -> UIAction { - UIAction { [weak self] _ in - self?.lastConfigurationRefreshDate = Date.distantPast - self?.fetchAssets() - self?.tableView.reloadData() - } + private lazy var refreshAction = UIAction { [weak self] _ in + self?.lastConfigurationRefreshDate = Date.distantPast + self?.fetchAssets() + self?.tableView.reloadData() } private func presentCustomURLAlert(for row: CustomURLsRows) { diff --git a/DuckDuckGo/CriticalAlerts.swift b/DuckDuckGo/CriticalAlerts.swift index c62183fe0c..0d3f25796f 100644 --- a/DuckDuckGo/CriticalAlerts.swift +++ b/DuckDuckGo/CriticalAlerts.swift @@ -70,19 +70,20 @@ struct CriticalAlerts { return alertController } - static func makeExpiredEntitlementAlert() -> UIAlertController { + static func makeExpiredEntitlementAlert(completion: @escaping () -> Void) -> UIAlertController { let alertController = UIAlertController(title: UserText.vpnAccessRevokedAlertTitle, message: UserText.vpnAccessRevokedAlertMessage, preferredStyle: .alert) alertController.overrideUserInterfaceStyle() let closeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionCancel, style: .cancel) - let signInButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in - UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) + let subscribeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in + completion() } alertController.addAction(closeButton) - alertController.addAction(signInButton) + alertController.addAction(subscribeButton) + alertController.preferredAction = subscribeButton return alertController } diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 7443344405..fdab02c1d8 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -233,17 +233,8 @@ - - - - - - - - - - + @@ -252,7 +243,7 @@ - + @@ -261,7 +252,7 @@ - + @@ -270,7 +261,7 @@ - + @@ -846,17 +837,17 @@ - + - + - + - +