From b2e69fae298d7288a1cb4193c0b1a9b22d2744a7 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Mon, 21 Oct 2024 12:33:46 +0200 Subject: [PATCH 01/26] Enable credentials import promo for all users (#3427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1202926619870900/1208282574906775/f **Description**: Just a BSK bump to allow for https://github.com/duckduckgo/macos-browser/pull/3383 **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Copy Testing**: * [ ] Use of correct apostrophes in new copy, ie `’` rather than `'` **Orientation Testing**: * [ ] Portrait * [ ] Landscape **Device Testing**: * [ ] iPhone SE (1st Gen) * [ ] iPhone 8 * [ ] iPhone X * [ ] iPhone 14 Pro * [ ] iPad **OS Testing**: * [ ] iOS 15 * [ ] iOS 16 * [ ] iOS 17 **Theme Testing**: * [ ] Light theme * [ ] Dark theme --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +--- .../xcshareddata/swiftpm/Package.resolved | 8 ++--- ...StubAutofillLoginImportStateProvider.swift | 35 ------------------- DuckDuckGo/UserScripts.swift | 2 +- 4 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 DuckDuckGo/StubAutofillLoginImportStateProvider.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e27f3415c1..0f19b3d6bc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1023,7 +1023,6 @@ EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */; }; EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */; }; EE0798C52B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0798C42B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift */; }; - EE0D1B9C2C8B41DB00AC0987 /* StubAutofillLoginImportStateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0D1B9B2C8B41DB00AC0987 /* StubAutofillLoginImportStateProvider.swift */; }; EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; EE3B226B29DE0F110082298A /* MockInternalUserStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */; }; EE3B226C29DE0FD30082298A /* MockInternalUserStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */; }; @@ -2846,7 +2845,6 @@ EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNSettingsViewModel.swift; sourceTree = ""; }; EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNLocationView.swift; sourceTree = ""; }; EE0798C42B179936000A4F64 /* NetworkProtectionVPNCountryLabelsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNCountryLabelsModel.swift; sourceTree = ""; }; - EE0D1B9B2C8B41DB00AC0987 /* StubAutofillLoginImportStateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubAutofillLoginImportStateProvider.swift; sourceTree = ""; }; EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionUNNotificationPresenter.swift; sourceTree = ""; }; EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockInternalUserStoring.swift; sourceTree = ""; }; EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoAlpha.entitlements; sourceTree = ""; }; @@ -6399,7 +6397,6 @@ 31951E9328230D8900CAF535 /* Shared */, F407605428131923006B1E0B /* SaveLogin */, EE5929612C5A8AF40029380B /* AutofillUsageMonitor.swift */, - EE0D1B9B2C8B41DB00AC0987 /* StubAutofillLoginImportStateProvider.swift */, ); name = Autofill; sourceTree = ""; @@ -7778,7 +7775,6 @@ 0283A2012C6E46E300508FBD /* BrokenSitePromptLimiter.swift in Sources */, 85DFEDF924CF3D0E00973FE7 /* TabsBarCell.swift in Sources */, 851672D32BED23FE00592F24 /* AutocompleteViewModel.swift in Sources */, - EE0D1B9C2C8B41DB00AC0987 /* StubAutofillLoginImportStateProvider.swift in Sources */, 8562CE152B9B645C00E1D399 /* CachedBookmarkSuggestions.swift in Sources */, C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */, F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, @@ -10966,7 +10962,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 199.4.0; + version = 200.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 24fccdfaf2..3a477518cb 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "f1a033cc4b97ab6b4d845815e825ba179c02635a", - "version" : "199.4.0" + "revision" : "9f62aacd878a0c05bff7256eb25b8776aa4e917f", + "version" : "200.0.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "1fee787458d13f8ed07f9fe81aecd6e59609339e", - "version" : "13.1.0" + "revision" : "945ac09a0189dc6736db617867fde193ea984b20", + "version" : "15.0.0" } }, { diff --git a/DuckDuckGo/StubAutofillLoginImportStateProvider.swift b/DuckDuckGo/StubAutofillLoginImportStateProvider.swift deleted file mode 100644 index 2b9c6382ab..0000000000 --- a/DuckDuckGo/StubAutofillLoginImportStateProvider.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// StubAutofillLoginImportStateProvider.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 -import BrowserServicesKit - -struct StubAutofillLoginImportStateProvider: AutofillLoginImportStateProvider { - public var isNewDDGUser: Bool = false - public var hasImportedLogins: Bool = false - var credentialsImportPromptPresentationCount: Int = 0 - - var isAutofillEnabled: Bool { - AppDependencyProvider.shared.appSettings.autofillCredentialsEnabled - } - - func hasNeverPromptWebsitesFor(_ domain: String) -> Bool { - AppDependencyProvider.shared.autofillNeverPromptWebsitesManager.hasNeverPromptWebsitesFor(domain: domain) - } -} diff --git a/DuckDuckGo/UserScripts.swift b/DuckDuckGo/UserScripts.swift index 30904c6324..4b003e0a57 100644 --- a/DuckDuckGo/UserScripts.swift +++ b/DuckDuckGo/UserScripts.swift @@ -56,7 +56,7 @@ final class UserScripts: UserScriptsProvider { init(with sourceProvider: ScriptSourceProviding) { contentBlockerUserScript = ContentBlockerRulesUserScript(configuration: sourceProvider.contentBlockerRulesConfig) surrogatesScript = SurrogatesUserScript(configuration: sourceProvider.surrogatesConfig) - autofillUserScript = AutofillUserScript(scriptSourceProvider: sourceProvider.autofillSourceProvider, loginImportStateProvider: StubAutofillLoginImportStateProvider()) + autofillUserScript = AutofillUserScript(scriptSourceProvider: sourceProvider.autofillSourceProvider) autofillUserScript.sessionKey = sourceProvider.contentScopeProperties.sessionKey loginFormDetectionScript = sourceProvider.loginDetectionEnabled ? LoginFormDetectionUserScript() : nil From 29e974f9447bf8aaaeb6fcaa0e60ad5bf590e165 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Mon, 21 Oct 2024 16:11:06 +0200 Subject: [PATCH 02/26] Update fastlane to 2.225.0 (#3462) Task/Issue URL: https://app.asana.com/0/1203301625297703/1208583505730567/f --- Gemfile | 2 +- Gemfile.lock | 46 ++++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Gemfile b/Gemfile index d2fc002a76..610772ceae 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source 'https://rubygems.org' -gem 'fastlane', '2.222.0' +gem 'fastlane', '2.225.0' diff --git a/Gemfile.lock b/Gemfile.lock index a3d1c3461a..ca9c16ec9b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,20 +10,20 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.963.0) - aws-sdk-core (3.201.4) + aws-partitions (1.992.0) + aws-sdk-core (3.210.0) aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.8) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.88.0) - aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-kms (1.95.0) + aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.157.0) - aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-s3 (1.169.0) + aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.9.1) + aws-sigv4 (1.10.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) @@ -38,8 +38,8 @@ GEM domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.111.0) - faraday (1.10.3) + excon (0.112.0) + faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -65,10 +65,10 @@ GEM faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) - faraday_middleware (1.2.0) + faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.222.0) + fastlane (2.225.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -84,6 +84,7 @@ GEM faraday-cookie_jar (~> 0.0.6) faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) @@ -109,6 +110,8 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) gh_inspector (1.1.3) google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) @@ -147,12 +150,12 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.6) + http-cookie (1.0.7) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) json (2.7.2) - jwt (2.8.2) + jwt (2.9.3) base64 mini_magick (4.13.2) mini_mime (1.1.5) @@ -171,8 +174,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.6) - strscan + rexml (3.3.8) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -185,7 +187,7 @@ GEM simctl (1.6.10) CFPropertyList naturally - strscan (3.1.0) + sysrandom (1.0.5) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -195,15 +197,15 @@ GEM tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) word_wrap (1.0.0) - xcodeproj (1.25.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) @@ -213,7 +215,7 @@ PLATFORMS ruby DEPENDENCIES - fastlane (= 2.222.0) + fastlane (= 2.225.0) BUNDLED WITH 2.3.26 From efdacc02b17ec7440e5b4fad9bc8696b6df010d4 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 21 Oct 2024 18:31:39 +0100 Subject: [PATCH 03/26] Release 7.142.0-0 (#3464) Please make sure all GH checks passed before merging. It can take around 20 minutes. Briefly review this PR to see if there are no issues or red flags and then merge it. --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/AppTrackerDataSetProvider.swift | 4 +- Core/ios-config.json | 224 ++++++++---- Core/trackerData.json | 344 +++++++++++++----- DuckDuckGo.xcodeproj/project.pbxproj | 56 +-- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 7 files changed, 442 insertions(+), 194 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 3b2b7dc2b3..5a76c2767c 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.141.0 +MARKETING_VERSION = 7.142.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index e26b5f00c5..c044c7c70a 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 = "\"4ffe7d2b6c8e252d0289b1398cc2685d\"" - public static let embeddedDataSHA = "9795ade4fdbc474688250f2ecfa097e917feea21a54fd97c524b851245d170e8" + public static let embeddedDataETag = "\"6098a36b63dcb92404871b8b6c92a46e\"" + public static let embeddedDataSHA = "a72538e90ef1aba77d30dde1379e7344fe9f3ca1655796c9b555bed412daa205" } public var embeddedDataEtag: String { diff --git a/Core/AppTrackerDataSetProvider.swift b/Core/AppTrackerDataSetProvider.swift index 29f59c5818..57696ef15b 100644 --- a/Core/AppTrackerDataSetProvider.swift +++ b/Core/AppTrackerDataSetProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppTrackerDataSetProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"f5dad10599a8d6088ec1908da3eeb3cc\"" - public static let embeddedDataSHA = "bb4c80f41383971693b241b4580fa74bbec1378ca6c2f8da8745dc6044fa1af9" + public static let embeddedDataETag = "\"0b9329cf89ed936dab485072c32814c4\"" + public static let embeddedDataSHA = "09b3b38cb556f93ac3d7c45e2535677285f5beef4cb4c0af2aaaa00b25f311d4" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index 161c16b811..bd6b729093 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1728900024972, + "version": 1729265730687, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -60,6 +60,11 @@ }, "hash": "b813ade8472a097ffbd43a3331116fe1" }, + "aiChat": { + "state": "disabled", + "exceptions": [], + "hash": "c292bb627849854515cebbded288ef5a" + }, "ampLinks": { "exceptions": [ { @@ -88,6 +93,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -114,7 +122,7 @@ ] }, "state": "enabled", - "hash": "fa5f86bac5946c528cd6bc7449a2718a" + "hash": "d9703d9553194bc54e66db1f2a4ec1f8" }, "androidBrowserConfig": { "exceptions": [], @@ -366,6 +374,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -393,7 +404,7 @@ } } }, - "hash": "ea2fef42c1666e9d9e4cabd49e57764d" + "hash": "fba0eeeda60051f6354cf5d26c1e1a63" }, "autofillBreakageReporter": { "state": "enabled", @@ -517,9 +528,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "37e0cf88badfc8b01b6394f0884502f6" + "hash": "3766f6af346d3fffdf1e8ffce682c66e" }, "brokenSitePrompt": { "state": "enabled", @@ -1223,6 +1237,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -1240,7 +1257,7 @@ } }, "state": "disabled", - "hash": "cb1f114a9e0314393b2a0f789cba163f" + "hash": "3973e9d924c9a054df7f5dffad1f1d19" }, "clickToPlay": { "exceptions": [ @@ -1258,6 +1275,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -1270,7 +1290,7 @@ } }, "state": "disabled", - "hash": "894fb86c1f058aee9db47cfcdf3637de" + "hash": "31a06101df1dc362bfcef2d7a6320f80" }, "clientBrandHint": { "exceptions": [], @@ -1315,9 +1335,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "96b2f778bab196aa424e9c859ddea778" + "hash": "b4eff737bff7f262ceb567b735e1cc41" }, "cookie": { "settings": { @@ -1382,10 +1405,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "6b4d2cef180104c5c84f5687479b8492" + "hash": "cef2b67a9df0d36b0875e7b54d33a4d0" }, "customUserAgent": { "settings": { @@ -1570,6 +1596,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -3526,6 +3555,23 @@ } ] }, + { + "domain": "mantan-web.jp", + "rules": [ + { + "selector": "center > div", + "type": "hide-empty" + }, + { + "selector": ".ad-rec", + "type": "hide-empty" + }, + { + "selector": ".conteiner__head", + "type": "hide-empty" + } + ] + }, { "domain": "marketwatch.com", "rules": [ @@ -4375,6 +4421,10 @@ { "selector": "[data-aa-adunit]", "type": "hide" + }, + { + "selector": "[data-adpath]", + "type": "hide-empty" } ] }, @@ -4868,7 +4918,7 @@ ] }, "state": "enabled", - "hash": "91a3dbe67daa37762247c415766a2634" + "hash": "9518158b11d290809536a99f637f467e" }, "exceptionHandler": { "exceptions": [ @@ -4886,10 +4936,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "be6751fe0307a7e1b9476f4d8b8d0aaf" + "hash": "a214254da3cc914ed5bfc0a2d893b589" }, "extendedOnboarding": { "exceptions": [], @@ -4916,9 +4969,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "7f042650922da2636492e77ed1101bce" + "hash": "008c61cd03c28287a7f86b598c37078b" }, "fingerprintingBattery": { "exceptions": [ @@ -4939,10 +4995,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "enabled", - "hash": "fcc2138fa97c35ded544b39708fda919" + "hash": "d05606a02ffd6ce5e223bc26e748a203" }, "fingerprintingCanvas": { "settings": { @@ -5047,10 +5106,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "49a3d497835bf5715aaaa73f87dd974f" + "hash": "b0eef1a098ab8c6cc9d6da35a9cfb7ad" }, "fingerprintingHardware": { "settings": { @@ -5116,10 +5178,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "enabled", - "hash": "cd4a8461973d1c1648dd20e6d1f532a7" + "hash": "25a38bd7ccbca83ce0899548608235a7" }, "fingerprintingScreenSize": { "settings": { @@ -5173,10 +5238,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "enabled", - "hash": "046340bb9287a20efed6189525ec5fed" + "hash": "c22a6e9f1c03693516589c47970d7a04" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -5203,10 +5271,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "enabled", - "hash": "14b7fe3d276b52109c59f0c71aee4f71" + "hash": "48b1d8e96ee94825378d12a8d5a66895" }, "googleRejected": { "exceptions": [ @@ -5224,55 +5295,37 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "be6751fe0307a7e1b9476f4d8b8d0aaf" + "hash": "a214254da3cc914ed5bfc0a2d893b589" }, "gpc": { "state": "enabled", "exceptions": [ - { - "domain": "allegiantair.com" - }, { "domain": "boston.com" }, { "domain": "costco.com" }, - { - "domain": "crunchyroll.com" - }, { "domain": "espn.com" }, - { - "domain": "eventbrite.com" - }, - { - "domain": "duluthtrading.com" - }, - { - "domain": "web.whatsapp.com" - }, - { - "domain": "verizon.com" - }, { "domain": "chime.com" }, { "domain": "tirerack.com" }, - { - "domain": "milesplit.live" - }, { "domain": "dollargeneral.com" }, { - "domain": "monstergear.monsterenergy.com" + "domain": "monsterenergy.com" }, { "domain": "npr.org" @@ -5280,12 +5333,6 @@ { "domain": "norton.com" }, - { - "domain": "jcrew.com" - }, - { - "domain": "pgealerts.alerts.pge.com" - }, { "domain": "marvel.com" }, @@ -5299,10 +5346,10 @@ "domain": "flexmls.com" }, { - "domain": "oreillyauto.com" + "domain": "instructure.com" }, { - "domain": "instructure.com" + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -5314,7 +5361,7 @@ "privacy-test-pages.site" ] }, - "hash": "fb031f27a1e1c2b143505c4f2e7b3ba7" + "hash": "be142a65e913cf958af67e2cd5dd8cc4" }, "harmfulApis": { "settings": { @@ -5430,10 +5477,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "fb598c4167ff166d85dd49c701cc5579" + "hash": "f255e336420119584b7000846be6d456" }, "history": { "state": "enabled", @@ -5484,9 +5534,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "b47d255c6f836ecb7ae0b3e61cc2c025" + "hash": "7407fc43cbd260f9aaca7cb7dab15bf4" }, "incontextSignup": { "exceptions": [], @@ -5545,6 +5598,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -5555,7 +5611,7 @@ ] }, "state": "enabled", - "hash": "d14f6e3a9aa4139ee1d517016b59691e" + "hash": "a1100eac5ecca0a11501df9f4dafa31a" }, "networkProtection": { "state": "enabled", @@ -5602,10 +5658,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "82088db85ca7f64418fbfd57db25ade1" + "hash": "5646a778c1cb6ec6e9c0da2c7dbd4bdb" }, "performanceMetrics": { "state": "enabled", @@ -5624,9 +5683,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "6792064606a5a72c5cd44addb4d40bda" + "hash": "60c3c3eed29e1e0c092fad8775483210" }, "phishingDetection": { "state": "disabled", @@ -5645,9 +5707,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "37e0cf88badfc8b01b6394f0884502f6" + "hash": "3766f6af346d3fffdf1e8ffce682c66e" }, "pluginPointFocusedViewPlugin": { "state": "disabled", @@ -5764,10 +5829,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "138c3b2409f6b3bf967b804ab9bf2ce2" + "hash": "68eb25a9461b134838100eecb0271905" }, "remoteMessaging": { "state": "enabled", @@ -5791,12 +5859,15 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { "windowInMs": 0 }, - "hash": "baf19d9e0f506ed09f46c95b1849adee" + "hash": "13d2723b0c33943f086acb8c239e22e8" }, "runtimeChecks": { "state": "disabled", @@ -5815,10 +5886,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": {}, - "hash": "dfede9f06b9e322e198736703d013d15" + "hash": "568cf394681d38683d1aeb8f0d0e6a7c" }, "sendFullPackageInstallSource": { "state": "enabled", @@ -5842,10 +5916,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "be6751fe0307a7e1b9476f4d8b8d0aaf" + "hash": "a214254da3cc914ed5bfc0a2d893b589" }, "sslCertificates": { "state": "enabled", @@ -7320,6 +7397,7 @@ "bodyelectricvitality.com.au", "cosmicbook.news", "eatroyo.com", + "experian.com", "piedmontng.com", "thesimsresource.com", "tradersync.com", @@ -9251,9 +9329,12 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], - "hash": "0692c3f8cc6179433b9f23f00c7dd0ac" + "hash": "e61f68717bcb4a465182a7ddb7d5cc4d" }, "trackingCookies1p": { "settings": { @@ -9277,10 +9358,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "763f56424b0827b5731927a043219912" + "hash": "a5c95510cb55fbe69cbff10e55a982dd" }, "trackingCookies3p": { "settings": { @@ -9301,10 +9385,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "82088db85ca7f64418fbfd57db25ade1" + "hash": "5646a778c1cb6ec6e9c0da2c7dbd4bdb" }, "trackingParameters": { "exceptions": [ @@ -9328,6 +9415,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "settings": { @@ -9360,7 +9450,7 @@ ] }, "state": "enabled", - "hash": "3805ecfb8a129f70a99e73a364b38f38" + "hash": "e530308726226930ff9a058fa064a39f" }, "userAgentRotation": { "settings": { @@ -9381,10 +9471,13 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "disabled", - "hash": "9225b8785d6973db37abde99d81d219c" + "hash": "dd373ef0993c7ca9d9fa949db6d6aca0" }, "voiceSearch": { "exceptions": [], @@ -9415,6 +9508,9 @@ }, { "domain": "instructure.com" + }, + { + "domain": "centerwellpharmacy.com" } ], "state": "enabled", @@ -9487,7 +9583,7 @@ } ] }, - "hash": "2853748f3ebb813d59f4db4a7bb13c83" + "hash": "ed17f6ff342f200305eb4bbe544efec0" }, "webViewBlobDownload": { "exceptions": [], diff --git a/Core/trackerData.json b/Core/trackerData.json index fafcb8a1a9..72548a65e2 100644 --- a/Core/trackerData.json +++ b/Core/trackerData.json @@ -1,6 +1,6 @@ { "_builtWith": { - "tracker-radar": "377ccbb6904d70981db82b125f927d0aab7cbc1101fa5daf7774eee9b18e6668-4013b4e91930c643394cb31c6c745356f133b04f", + "tracker-radar": "115cad5b09c64b18754dbfb462f35cb96a8b016d6ef8b6238e792f053a2a69b7-4013b4e91930c643394cb31c6c745356f133b04f", "tracker-surrogates": "0528e3226df15b1a3e319ad68ef76612a8f26623" }, "readme": "https://github.com/duckduckgo/tracker-blocklists", @@ -32940,6 +32940,17 @@ "cookies": 0.01, "default": "block" }, + "alertarithmetic.com": { + "domain": "alertarithmetic.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (alertarithmetic.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "aliasanvil.com": { "domain": "aliasanvil.com", "owner": { @@ -35074,6 +35085,17 @@ "cookies": 0.01, "default": "block" }, + "concernedchange.com": { + "domain": "concernedchange.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (concernedchange.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "concernedchickens.com": { "domain": "concernedchickens.com", "owner": { @@ -35624,17 +35646,6 @@ "cookies": 0.01, "default": "block" }, - "cumbersomecloud.com": { - "domain": "cumbersomecloud.com", - "owner": { - "name": "Leven Labs, Inc. DBA Admiral (cumbersomecloud.com)", - "displayName": "Admiral" - }, - "prevalence": 0.0118, - "fingerprinting": 1, - "cookies": 0.01, - "default": "block" - }, "curiouschalk.com": { "domain": "curiouschalk.com", "owner": { @@ -36350,17 +36361,6 @@ "cookies": 0.01, "default": "block" }, - "dreamycanyon.com": { - "domain": "dreamycanyon.com", - "owner": { - "name": "Leven Labs, Inc. DBA Admiral (dreamycanyon.com)", - "displayName": "Admiral" - }, - "prevalence": 0.0118, - "fingerprinting": 1, - "cookies": 0.01, - "default": "block" - }, "driftpizza.com": { "domain": "driftpizza.com", "owner": { @@ -37472,6 +37472,17 @@ "cookies": 0.01, "default": "block" }, + "fertilefeeling.com": { + "domain": "fertilefeeling.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (fertilefeeling.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "fewjuice.com": { "domain": "fewjuice.com", "owner": { @@ -40112,6 +40123,17 @@ "cookies": 0.01, "default": "block" }, + "lonelybulb.com": { + "domain": "lonelybulb.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (lonelybulb.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "lonelyflavor.com": { "domain": "lonelyflavor.com", "owner": { @@ -40486,6 +40508,17 @@ "cookies": 0.01, "default": "block" }, + "marrowleaves.com": { + "domain": "marrowleaves.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (marrowleaves.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "massivemark.com": { "domain": "massivemark.com", "owner": { @@ -41355,6 +41388,17 @@ "cookies": 0.01, "default": "block" }, + "numberlessring.com": { + "domain": "numberlessring.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (numberlessring.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "numerousnest.com": { "domain": "numerousnest.com", "owner": { @@ -41399,6 +41443,17 @@ "cookies": 0.01, "default": "block" }, + "objecthero.com": { + "domain": "objecthero.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (objecthero.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "obscenesidewalk.com": { "domain": "obscenesidewalk.com", "owner": { @@ -42774,6 +42829,17 @@ "cookies": 0.01, "default": "block" }, + "rarestcandy.com": { + "domain": "rarestcandy.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (rarestcandy.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "raresummer.com": { "domain": "raresummer.com", "owner": { @@ -42939,6 +43005,17 @@ "cookies": 0.01, "default": "block" }, + "reflectivestatement.com": { + "domain": "reflectivestatement.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (reflectivestatement.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "refundradar.com": { "domain": "refundradar.com", "owner": { @@ -42950,6 +43027,17 @@ "cookies": 0.01, "default": "block" }, + "regexmail.com": { + "domain": "regexmail.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (regexmail.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "regularplants.com": { "domain": "regularplants.com", "owner": { @@ -43676,6 +43764,17 @@ "cookies": 0.01, "default": "block" }, + "scribbleson.com": { + "domain": "scribbleson.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (scribbleson.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "scrollservice.com": { "domain": "scrollservice.com", "owner": { @@ -43764,6 +43863,17 @@ "cookies": 0.01, "default": "block" }, + "seemlysuggestion.com": { + "domain": "seemlysuggestion.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (seemlysuggestion.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "selfishsea.com": { "domain": "selfishsea.com", "owner": { @@ -45073,6 +45183,17 @@ "cookies": 0.01, "default": "block" }, + "steepscale.com": { + "domain": "steepscale.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral (steepscale.com)", + "displayName": "Admiral" + }, + "prevalence": 0.0118, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, "steepsister.com": { "domain": "steepsister.com", "owner": { @@ -45491,17 +45612,6 @@ "cookies": 0.01, "default": "block" }, - "superviseshoes.com": { - "domain": "superviseshoes.com", - "owner": { - "name": "Leven Labs, Inc. DBA Admiral (superviseshoes.com)", - "displayName": "Admiral" - }, - "prevalence": 0.0118, - "fingerprinting": 1, - "cookies": 0.01, - "default": "block" - }, "supportwaves.com": { "domain": "supportwaves.com", "owner": { @@ -46162,17 +46272,6 @@ "cookies": 0.01, "default": "block" }, - "tranquilarchipelago.com": { - "domain": "tranquilarchipelago.com", - "owner": { - "name": "Leven Labs, Inc. DBA Admiral (tranquilarchipelago.com)", - "displayName": "Admiral" - }, - "prevalence": 0.0118, - "fingerprinting": 1, - "cookies": 0.01, - "default": "block" - }, "tranquilcan.com": { "domain": "tranquilcan.com", "owner": { @@ -46184,17 +46283,6 @@ "cookies": 0.01, "default": "block" }, - "tranquilcanyon.com": { - "domain": "tranquilcanyon.com", - "owner": { - "name": "Leven Labs, Inc. DBA Admiral (tranquilcanyon.com)", - "displayName": "Admiral" - }, - "prevalence": 0.0118, - "fingerprinting": 1, - "cookies": 0.01, - "default": "block" - }, "tranquilplume.com": { "domain": "tranquilplume.com", "owner": { @@ -57867,6 +57955,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (alertarithmetic.com)": { + "domains": [ + "alertarithmetic.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (aliasanvil.com)": { "domains": [ "aliasanvil.com" @@ -59470,6 +59565,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (concernedchange.com)": { + "domains": [ + "concernedchange.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (concernedchickens.com)": { "domains": [ "concernedchickens.com" @@ -59855,13 +59957,6 @@ "prevalence": 0.0118, "displayName": "Admiral" }, - "Leven Labs, Inc. DBA Admiral (cumbersomecloud.com)": { - "domains": [ - "cumbersomecloud.com" - ], - "prevalence": 0.0118, - "displayName": "Admiral" - }, "Leven Labs, Inc. DBA Admiral (curiouschalk.com)": { "domains": [ "curiouschalk.com" @@ -60415,13 +60510,6 @@ "prevalence": 0.0118, "displayName": "Admiral" }, - "Leven Labs, Inc. DBA Admiral (dreamycanyon.com)": { - "domains": [ - "dreamycanyon.com" - ], - "prevalence": 0.0118, - "displayName": "Admiral" - }, "Leven Labs, Inc. DBA Admiral (driftpizza.com)": { "domains": [ "driftpizza.com" @@ -61185,6 +61273,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (fertilefeeling.com)": { + "domains": [ + "fertilefeeling.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (fewjuice.com)": { "domains": [ "fewjuice.com" @@ -63068,6 +63163,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (lonelybulb.com)": { + "domains": [ + "lonelybulb.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (lonelyflavor.com)": { "domains": [ "lonelyflavor.com" @@ -63341,6 +63443,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (marrowleaves.com)": { + "domains": [ + "marrowleaves.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (massivemark.com)": { "domains": [ "massivemark.com" @@ -63957,6 +64066,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (numberlessring.com)": { + "domains": [ + "numberlessring.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (numerousnest.com)": { "domains": [ "numerousnest.com" @@ -63992,6 +64108,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (objecthero.com)": { + "domains": [ + "objecthero.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (obscenesidewalk.com)": { "domains": [ "obscenesidewalk.com" @@ -65056,6 +65179,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (rarestcandy.com)": { + "domains": [ + "rarestcandy.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (raresummer.com)": { "domains": [ "raresummer.com" @@ -65182,6 +65312,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (reflectivestatement.com)": { + "domains": [ + "reflectivestatement.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (refundradar.com)": { "domains": [ "refundradar.com" @@ -65189,6 +65326,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (regexmail.com)": { + "domains": [ + "regexmail.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (regularplants.com)": { "domains": [ "regularplants.com" @@ -65784,6 +65928,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (scribbleson.com)": { + "domains": [ + "scribbleson.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (scribblestring.com)": { "domains": [ "scribblestring.com" @@ -65854,6 +66005,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (seemlysuggestion.com)": { + "domains": [ + "seemlysuggestion.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (selectivesummer.com)": { "domains": [ "selectivesummer.com" @@ -66862,6 +67020,13 @@ "prevalence": 0.0118, "displayName": "Admiral" }, + "Leven Labs, Inc. DBA Admiral (steepscale.com)": { + "domains": [ + "steepscale.com" + ], + "prevalence": 0.0118, + "displayName": "Admiral" + }, "Leven Labs, Inc. DBA Admiral (steepsister.com)": { "domains": [ "steepsister.com" @@ -67219,13 +67384,6 @@ "prevalence": 0.0118, "displayName": "Admiral" }, - "Leven Labs, Inc. DBA Admiral (superviseshoes.com)": { - "domains": [ - "superviseshoes.com" - ], - "prevalence": 0.0118, - "displayName": "Admiral" - }, "Leven Labs, Inc. DBA Admiral (supportwaves.com)": { "domains": [ "supportwaves.com" @@ -67702,13 +67860,6 @@ "prevalence": 0.0118, "displayName": "Admiral" }, - "Leven Labs, Inc. DBA Admiral (tranquilarchipelago.com)": { - "domains": [ - "tranquilarchipelago.com" - ], - "prevalence": 0.0118, - "displayName": "Admiral" - }, "Leven Labs, Inc. DBA Admiral (tranquilcan.com)": { "domains": [ "tranquilcan.com" @@ -67716,13 +67867,6 @@ "prevalence": 0.0118, "displayName": "Admiral" }, - "Leven Labs, Inc. DBA Admiral (tranquilcanyon.com)": { - "domains": [ - "tranquilcanyon.com" - ], - "prevalence": 0.0118, - "displayName": "Admiral" - }, "Leven Labs, Inc. DBA Admiral (tranquilplume.com)": { "domains": [ "tranquilplume.com" @@ -69436,6 +69580,7 @@ "aheadgrow.com": "Leven Labs, Inc. DBA Admiral (aheadgrow.com)", "aheadmachine.com": "Leven Labs, Inc. DBA Admiral (aheadmachine.com)", "ak0gsh40.com": "Leven Labs, Inc. DBA Admiral (ak0gsh40.com)", + "alertarithmetic.com": "Leven Labs, Inc. DBA Admiral (alertarithmetic.com)", "aliasanvil.com": "Leven Labs, Inc. DBA Admiral (aliasanvil.com)", "alikeaddition.com": "Leven Labs, Inc. DBA Admiral (alikeaddition.com)", "aliveachiever.com": "Leven Labs, Inc. DBA Admiral (aliveachiever.com)", @@ -69665,6 +69810,7 @@ "comfygoodness.com": "Leven Labs, Inc. DBA Admiral (comfygoodness.com)", "comparereaction.com": "Leven Labs, Inc. DBA Admiral (comparereaction.com)", "compiledoctor.com": "Leven Labs, Inc. DBA Admiral (compiledoctor.com)", + "concernedchange.com": "Leven Labs, Inc. DBA Admiral (concernedchange.com)", "concernedchickens.com": "Leven Labs, Inc. DBA Admiral (concernedchickens.com)", "condemnedcomb.com": "Leven Labs, Inc. DBA Admiral (condemnedcomb.com)", "conditionchange.com": "Leven Labs, Inc. DBA Admiral (conditionchange.com)", @@ -69720,7 +69866,6 @@ "culturedfeather.com": "Leven Labs, Inc. DBA Admiral (culturedfeather.com)", "cumbersomecar.com": "Leven Labs, Inc. DBA Admiral (cumbersomecar.com)", "cumbersomecarpenter.com": "Leven Labs, Inc. DBA Admiral (cumbersomecarpenter.com)", - "cumbersomecloud.com": "Leven Labs, Inc. DBA Admiral (cumbersomecloud.com)", "curiouschalk.com": "Leven Labs, Inc. DBA Admiral (curiouschalk.com)", "curioussuccess.com": "Leven Labs, Inc. DBA Admiral (curioussuccess.com)", "curlycannon.com": "Leven Labs, Inc. DBA Admiral (curlycannon.com)", @@ -69800,7 +69945,6 @@ "dq95d35.com": "Leven Labs, Inc. DBA Admiral (dq95d35.com)", "drainpaste.com": "Leven Labs, Inc. DBA Admiral (drainpaste.com)", "dramaticdirection.com": "Leven Labs, Inc. DBA Admiral (dramaticdirection.com)", - "dreamycanyon.com": "Leven Labs, Inc. DBA Admiral (dreamycanyon.com)", "driftpizza.com": "Leven Labs, Inc. DBA Admiral (driftpizza.com)", "drollwharf.com": "Leven Labs, Inc. DBA Admiral (drollwharf.com)", "drydrum.com": "Leven Labs, Inc. DBA Admiral (drydrum.com)", @@ -69910,6 +70054,7 @@ "feeblestamp.com": "Leven Labs, Inc. DBA Admiral (feeblestamp.com)", "feignedfaucet.com": "Leven Labs, Inc. DBA Admiral (feignedfaucet.com)", "fernwaycloud.com": "Leven Labs, Inc. DBA Admiral (fernwaycloud.com)", + "fertilefeeling.com": "Leven Labs, Inc. DBA Admiral (fertilefeeling.com)", "fewjuice.com": "Leven Labs, Inc. DBA Admiral (fewjuice.com)", "fewkittens.com": "Leven Labs, Inc. DBA Admiral (fewkittens.com)", "finalizeforce.com": "Leven Labs, Inc. DBA Admiral (finalizeforce.com)", @@ -70179,6 +70324,7 @@ "livingsleet.com": "Leven Labs, Inc. DBA Admiral (livingsleet.com)", "lizardslaugh.com": "Leven Labs, Inc. DBA Admiral (lizardslaugh.com)", "loadsurprise.com": "Leven Labs, Inc. DBA Admiral (loadsurprise.com)", + "lonelybulb.com": "Leven Labs, Inc. DBA Admiral (lonelybulb.com)", "lonelyflavor.com": "Leven Labs, Inc. DBA Admiral (lonelyflavor.com)", "longingtrees.com": "Leven Labs, Inc. DBA Admiral (longingtrees.com)", "looseloaf.com": "Leven Labs, Inc. DBA Admiral (looseloaf.com)", @@ -70218,6 +70364,7 @@ "marriedbelief.com": "Leven Labs, Inc. DBA Admiral (marriedbelief.com)", "marriedmailbox.com": "Leven Labs, Inc. DBA Admiral (marriedmailbox.com)", "marriedvalue.com": "Leven Labs, Inc. DBA Admiral (marriedvalue.com)", + "marrowleaves.com": "Leven Labs, Inc. DBA Admiral (marrowleaves.com)", "massivemark.com": "Leven Labs, Inc. DBA Admiral (massivemark.com)", "materialisticmoon.com": "Leven Labs, Inc. DBA Admiral (materialisticmoon.com)", "materialmilk.com": "Leven Labs, Inc. DBA Admiral (materialmilk.com)", @@ -70306,11 +70453,13 @@ "notifyglass.com": "Leven Labs, Inc. DBA Admiral (notifyglass.com)", "nudgeduck.com": "Leven Labs, Inc. DBA Admiral (nudgeduck.com)", "nullnorth.com": "Leven Labs, Inc. DBA Admiral (nullnorth.com)", + "numberlessring.com": "Leven Labs, Inc. DBA Admiral (numberlessring.com)", "numerousnest.com": "Leven Labs, Inc. DBA Admiral (numerousnest.com)", "nutritiousbean.com": "Leven Labs, Inc. DBA Admiral (nutritiousbean.com)", "nuttyorganization.com": "Leven Labs, Inc. DBA Admiral (nuttyorganization.com)", "oafishchance.com": "Leven Labs, Inc. DBA Admiral (oafishchance.com)", "oafishobservation.com": "Leven Labs, Inc. DBA Admiral (oafishobservation.com)", + "objecthero.com": "Leven Labs, Inc. DBA Admiral (objecthero.com)", "obscenesidewalk.com": "Leven Labs, Inc. DBA Admiral (obscenesidewalk.com)", "observantice.com": "Leven Labs, Inc. DBA Admiral (observantice.com)", "oldfashionedoffer.com": "Leven Labs, Inc. DBA Admiral (oldfashionedoffer.com)", @@ -70463,6 +70612,7 @@ "rangecake.com": "Leven Labs, Inc. DBA Admiral (rangecake.com)", "rangeplayground.com": "Leven Labs, Inc. DBA Admiral (rangeplayground.com)", "rangergustav.com": "Leven Labs, Inc. DBA Admiral (rangergustav.com)", + "rarestcandy.com": "Leven Labs, Inc. DBA Admiral (rarestcandy.com)", "raresummer.com": "Leven Labs, Inc. DBA Admiral (raresummer.com)", "reactjspdf.com": "Leven Labs, Inc. DBA Admiral (reactjspdf.com)", "readingguilt.com": "Leven Labs, Inc. DBA Admiral (readingguilt.com)", @@ -70481,7 +70631,9 @@ "reconditeprison.com": "Leven Labs, Inc. DBA Admiral (reconditeprison.com)", "reconditerake.com": "Leven Labs, Inc. DBA Admiral (reconditerake.com)", "reconditerespect.com": "Leven Labs, Inc. DBA Admiral (reconditerespect.com)", + "reflectivestatement.com": "Leven Labs, Inc. DBA Admiral (reflectivestatement.com)", "refundradar.com": "Leven Labs, Inc. DBA Admiral (refundradar.com)", + "regexmail.com": "Leven Labs, Inc. DBA Admiral (regexmail.com)", "regularplants.com": "Leven Labs, Inc. DBA Admiral (regularplants.com)", "regulatesleet.com": "Leven Labs, Inc. DBA Admiral (regulatesleet.com)", "rehabilitatereason.com": "Leven Labs, Inc. DBA Admiral (rehabilitatereason.com)", @@ -70567,6 +70719,7 @@ "screechingfurniture.com": "Leven Labs, Inc. DBA Admiral (screechingfurniture.com)", "screechingstocking.com": "Leven Labs, Inc. DBA Admiral (screechingstocking.com)", "screechingstove.com": "Leven Labs, Inc. DBA Admiral (screechingstove.com)", + "scribbleson.com": "Leven Labs, Inc. DBA Admiral (scribbleson.com)", "scribblestring.com": "Leven Labs, Inc. DBA Admiral (scribblestring.com)", "scrollservice.com": "Leven Labs, Inc. DBA Admiral (scrollservice.com)", "scrubswim.com": "Leven Labs, Inc. DBA Admiral (scrubswim.com)", @@ -70577,6 +70730,7 @@ "secretspiders.com": "Leven Labs, Inc. DBA Admiral (secretspiders.com)", "secretturtle.com": "Leven Labs, Inc. DBA Admiral (secretturtle.com)", "seedscissors.com": "Leven Labs, Inc. DBA Admiral (seedscissors.com)", + "seemlysuggestion.com": "Leven Labs, Inc. DBA Admiral (seemlysuggestion.com)", "selectivesummer.com": "Leven Labs, Inc. DBA Admiral (selectivesummer.com)", "selfishsea.com": "Leven Labs, Inc. DBA Admiral (selfishsea.com)", "selfishsnake.com": "Leven Labs, Inc. DBA Admiral (selfishsnake.com)", @@ -70721,6 +70875,7 @@ "steadfastsystem.com": "Leven Labs, Inc. DBA Admiral (steadfastsystem.com)", "steadycopper.com": "Leven Labs, Inc. DBA Admiral (steadycopper.com)", "stealsteel.com": "Leven Labs, Inc. DBA Admiral (stealsteel.com)", + "steepscale.com": "Leven Labs, Inc. DBA Admiral (steepscale.com)", "steepsister.com": "Leven Labs, Inc. DBA Admiral (steepsister.com)", "steepsquirrel.com": "Leven Labs, Inc. DBA Admiral (steepsquirrel.com)", "stepcattle.com": "Leven Labs, Inc. DBA Admiral (stepcattle.com)", @@ -70772,7 +70927,6 @@ "superficialeyes.com": "Leven Labs, Inc. DBA Admiral (superficialeyes.com)", "superficialspring.com": "Leven Labs, Inc. DBA Admiral (superficialspring.com)", "superficialsquare.com": "Leven Labs, Inc. DBA Admiral (superficialsquare.com)", - "superviseshoes.com": "Leven Labs, Inc. DBA Admiral (superviseshoes.com)", "supportwaves.com": "Leven Labs, Inc. DBA Admiral (supportwaves.com)", "suspectmark.com": "Leven Labs, Inc. DBA Admiral (suspectmark.com)", "swankysquare.com": "Leven Labs, Inc. DBA Admiral (swankysquare.com)", @@ -70841,9 +70995,7 @@ "trackcaddie.com": "Leven Labs, Inc. DBA Admiral (trackcaddie.com)", "tradetooth.com": "Leven Labs, Inc. DBA Admiral (tradetooth.com)", "trafficviews.com": "Leven Labs, Inc. DBA Admiral (trafficviews.com)", - "tranquilarchipelago.com": "Leven Labs, Inc. DBA Admiral (tranquilarchipelago.com)", "tranquilcan.com": "Leven Labs, Inc. DBA Admiral (tranquilcan.com)", - "tranquilcanyon.com": "Leven Labs, Inc. DBA Admiral (tranquilcanyon.com)", "tranquilplume.com": "Leven Labs, Inc. DBA Admiral (tranquilplume.com)", "tranquilside.com": "Leven Labs, Inc. DBA Admiral (tranquilside.com)", "tranquilveil.com": "Leven Labs, Inc. DBA Admiral (tranquilveil.com)", diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0f19b3d6bc..cde6568b79 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9177,7 +9177,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9214,7 +9214,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9304,7 +9304,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9331,7 +9331,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9480,7 +9480,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9505,7 +9505,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9574,7 +9574,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9608,7 +9608,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9641,7 +9641,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9671,7 +9671,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9981,7 +9981,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10012,7 +10012,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10040,7 +10040,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10073,7 +10073,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -10103,7 +10103,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10136,11 +10136,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10373,7 +10373,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10400,7 +10400,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10432,7 +10432,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10469,7 +10469,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10504,7 +10504,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10539,11 +10539,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10716,11 +10716,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10749,10 +10749,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 3d6a4bec95..549ce4019a 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.141.0 + 7.142.0 Key version Title From de78c0519a0b71f84d3fd0e1a097890a345c67ec Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Mon, 21 Oct 2024 11:26:00 -0700 Subject: [PATCH 04/26] Remove MockDependencyProvider (#3456) Task/Issue URL: https://app.asana.com/0/414235014887631/1208580381705388/f Tech Design URL: CC: Description: This PR removes MockDependencyProvider and the ability to create testing instances of AppDependencyProvider. Any test files that use it have been updated to take their dependencies directly. The only tests affected by this change are subscription ones; DownloadManagerTests had a mock dependency provider property, but it didn't appear to be used in any way. --- DuckDuckGo.xcodeproj/project.pbxproj | 4 - DuckDuckGo/AppDependencyProvider.swift | 5 - DuckDuckGoTests/DownloadManagerTests.swift | 2 - DuckDuckGoTests/MockDependencyProvider.swift | 92 ------------------- .../SubscriptionContainerViewModelTests.swift | 16 +++- .../SubscriptionFlowViewModelTests.swift | 15 ++- 6 files changed, 29 insertions(+), 105 deletions(-) delete mode 100644 DuckDuckGoTests/MockDependencyProvider.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0f19b3d6bc..f346bd392a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -673,7 +673,6 @@ 98B001AA251EABB40090EC07 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98B001A8251EABB40090EC07 /* Localizable.strings */; }; 98B001B0251EABB40090EC07 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98B001AE251EABB40090EC07 /* InfoPlist.strings */; }; 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98B001B1251EABB40090EC07 /* InfoPlist.strings */; }; - 98B31290218CCB2200E54DE1 /* MockDependencyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B3128F218CCB2200E54DE1 /* MockDependencyProvider.swift */; }; 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B31291218CCB8C00E54DE1 /* AppDependencyProvider.swift */; }; 98BFA911294A2086004EA636 /* bookmarks_3k.html in Resources */ = {isa = PBXBuildFile; fileRef = 98BFA910294A2086004EA636 /* bookmarks_3k.html */; }; 98BFA913294A3DDC004EA636 /* BookmarksEditModelPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFA912294A3DDC004EA636 /* BookmarksEditModelPerformanceTests.swift */; }; @@ -2454,7 +2453,6 @@ 98B12739251EABD5007473E4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; 98B1273B251EABD5007473E4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; 98B1273C251EABD5007473E4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; - 98B3128F218CCB2200E54DE1 /* MockDependencyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDependencyProvider.swift; sourceTree = ""; }; 98B31291218CCB8C00E54DE1 /* AppDependencyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDependencyProvider.swift; sourceTree = ""; }; 98B4904D251EAC2200A1B398 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 98B4904E251EAC2200A1B398 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -5986,7 +5984,6 @@ children = ( 9F4CC51A2C48C0C7006A96EB /* MockTabDelegate.swift */, C14882E927F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift */, - 98B3128F218CCB2200E54DE1 /* MockDependencyProvider.swift */, C158AC7A297AB5DC0008723A /* MockSecureVault.swift */, F1134EBA1F40D3D000B73467 /* MockStatisticsStore.swift */, 026DABA328242BC80089E0B5 /* MockUserAgent.swift */, @@ -7934,7 +7931,6 @@ F13B4BF91F18CA0600814661 /* TabsModelTests.swift in Sources */, F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */, 987243142C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift in Sources */, - 98B31290218CCB2200E54DE1 /* MockDependencyProvider.swift in Sources */, CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */, D6F557BA2C8859040034444B /* DuckPlayerExperimentTests.swift in Sources */, 986B45D0299E30A50089D2D7 /* BookmarkEntityTests.swift in Sources */, diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 7460cb6bf4..4997f73e56 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -139,9 +139,4 @@ final class AppDependencyProvider: DependencyProvider { accountManager: accountManager) } - /// Only meant to be used for testing. - /// - static func makeTestingInstance() -> Self { - Self.init() - } } diff --git a/DuckDuckGoTests/DownloadManagerTests.swift b/DuckDuckGoTests/DownloadManagerTests.swift index 86367d7144..ec613dee5a 100644 --- a/DuckDuckGoTests/DownloadManagerTests.swift +++ b/DuckDuckGoTests/DownloadManagerTests.swift @@ -26,8 +26,6 @@ import WidgetKit class DownloadManagerTests: XCTestCase { private let downloadManagerTestsHelper = DownloadTestsHelper(downloadsDirectory: DownloadManager().downloadsDirectory) - var mockDependencyProvider: MockDependencyProvider! - override func tearDown() { super.tearDown() downloadManagerTestsHelper.deleteAllFiles() diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift deleted file mode 100644 index 05e28803f7..0000000000 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// MockDependencyProvider.swift -// DuckDuckGo -// -// Copyright © 2018 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Core -import BrowserServicesKit -import DDGSync -import Subscription -import SubscriptionTestingUtilities -import NetworkProtection -import RemoteMessaging -@testable import DuckDuckGo - -class MockDependencyProvider: DependencyProvider { - var appSettings: AppSettings - var variantManager: VariantManager - var featureFlagger: FeatureFlagger - var internalUserDecider: InternalUserDecider - var storageCache: StorageCache - var downloadManager: DownloadManager - var autofillLoginSession: AutofillLoginSession - var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager - var configurationManager: ConfigurationManager - var configurationStore: ConfigurationStore - var userBehaviorMonitor: UserBehaviorMonitor - var subscriptionManager: SubscriptionManager - var accountManager: AccountManager - var vpnFeatureVisibility: DefaultNetworkProtectionVisibility - var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore - var networkProtectionTunnelController: NetworkProtectionTunnelController - var connectionObserver: NetworkProtection.ConnectionStatusObserver - var serverInfoObserver: NetworkProtection.ConnectionServerInfoObserver - var vpnSettings: NetworkProtection.VPNSettings - var persistentPixel: PersistentPixelFiring - - init() { - let defaultProvider = AppDependencyProvider.makeTestingInstance() - appSettings = defaultProvider.appSettings - variantManager = defaultProvider.variantManager - featureFlagger = defaultProvider.featureFlagger - internalUserDecider = defaultProvider.internalUserDecider - storageCache = defaultProvider.storageCache - downloadManager = defaultProvider.downloadManager - autofillLoginSession = defaultProvider.autofillLoginSession - autofillNeverPromptWebsitesManager = defaultProvider.autofillNeverPromptWebsitesManager - configurationStore = defaultProvider.configurationStore - configurationManager = defaultProvider.configurationManager - userBehaviorMonitor = defaultProvider.userBehaviorMonitor - - accountManager = AccountManagerMock() - - let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: .production) - let authService = DefaultAuthEndpointService(currentServiceEnvironment: .production) - let storePurchaseManager = DefaultStorePurchaseManager() - subscriptionManager = SubscriptionManagerMock(accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore), - canPurchase: true) - - let accessTokenProvider: () -> String? = { { "sometoken" } }() - networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) - networkProtectionTunnelController = NetworkProtectionTunnelController(accountManager: accountManager, - tokenStore: networkProtectionKeychainTokenStore, - persistentPixel: MockPersistentPixel()) - vpnFeatureVisibility = DefaultNetworkProtectionVisibility(userDefaults: .networkProtectionGroupDefaults, - accountManager: accountManager) - - connectionObserver = ConnectionStatusObserverThroughSession() - serverInfoObserver = ConnectionServerInfoObserverThroughSession() - vpnSettings = VPNSettings(defaults: .networkProtectionGroupDefaults) - persistentPixel = MockPersistentPixel() - } -} diff --git a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift index 49d41fd704..2d4979997f 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift @@ -24,7 +24,21 @@ import SubscriptionTestingUtilities final class SubscriptionContainerViewModelTests: XCTestCase { var sut: SubscriptionContainerViewModel! - let subscriptionManager = MockDependencyProvider().subscriptionManager + + let subscriptionManager: SubscriptionManager = { + let accountManager = AccountManagerMock() + let subscriptionService = SubscriptionEndpointServiceMock() + let authService = AuthEndpointServiceMock() + let storePurchaseManager = StorePurchaseManagerMock() + return SubscriptionManagerMock(accountManager: accountManager, + subscriptionEndpointService: subscriptionService, + authEndpointService: authService, + storePurchaseManager: storePurchaseManager, + currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, + purchasePlatform: .appStore), + canPurchase: true) + }() + let subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { diff --git a/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift index 166657aa67..67235b541e 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift @@ -25,7 +25,20 @@ import SubscriptionTestingUtilities final class SubscriptionFlowViewModelTests: XCTestCase { private var sut: SubscriptionFlowViewModel! - let subscriptionManager = MockDependencyProvider().subscriptionManager + let subscriptionManager: SubscriptionManager = { + let accountManager = AccountManagerMock() + let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: .production) + let authService = DefaultAuthEndpointService(currentServiceEnvironment: .production) + let storePurchaseManager = DefaultStorePurchaseManager() + return SubscriptionManagerMock(accountManager: accountManager, + subscriptionEndpointService: subscriptionService, + authEndpointService: authService, + storePurchaseManager: storePurchaseManager, + currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, + purchasePlatform: .appStore), + canPurchase: true) + }() + let subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { From 2ed846eadddc4271384e4cefc792b8ae2d0d8cf0 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Tue, 22 Oct 2024 12:04:02 +0100 Subject: [PATCH 05/26] support third party marketplace URLs (#3461) Task/Issue URL: https://app.asana.com/0/414709148257752/1207138951730082/f Tech Design URL: CC: **Description**: Allow marketplace URLs to work. Note that the URL must be from a 'user' request. So we can't cancel and re-issue the URL otherwise it doesn't work. This also means that can't handle the case where it doesn't work unless we want to check the user has an EU apple account, which we don't. **Steps to test this PR**: 1. With an EU Apple ID, visit altstore.io and install their marketplace. Should cause iOS system prompts and then install successfully. 2. With a non-EU Apple ID, visit altstore.io and try to install their marketplace. Nothing will happen (unfortunately we are unable to catch this case). --- Core/SchemeHandler.swift | 9 +++++++++ DuckDuckGo/TabViewController.swift | 17 ++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Core/SchemeHandler.swift b/Core/SchemeHandler.swift index 059e1fee45..535b0686e9 100644 --- a/Core/SchemeHandler.swift +++ b/Core/SchemeHandler.swift @@ -29,6 +29,7 @@ public class SchemeHandler { } public enum SchemeType: Equatable { + case allow case navigational case external(Action) case blob @@ -51,6 +52,7 @@ public class SchemeHandler { case shortcuts case shortcutsProduction = "shortcuts-production" case workflow + case marketplaceKit = "marketplace-kit" } private enum BlockedScheme: String { @@ -74,6 +76,13 @@ public class SchemeHandler { } switch PlatformScheme(rawValue: schemeString) { + case .marketplaceKit: + // marketplaceKit urls have to be allowed through without interference + if #available(iOS 17.4, *) { + return .allow + } else { + return .unknown + } case .sms, .mailto, .itms, .itmss, .itunes, .itmsApps, .itmsAppss, .shortcuts, .shortcutsProduction, .workflow: return .external(.askForConfirmation) case .none: diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 772bc1e760..09490abf98 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1699,19 +1699,6 @@ extension TabViewController: WKNavigationDelegate { decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if #available(iOS 17.4, *), - navigationAction.request.url?.scheme == "marketplace-kit", - internalUserDecider.isInternalUser { - - decisionHandler(.allow) - let urlString = navigationAction.request.url?.absoluteString ?? "" - ActionMessageView.present(message: "Marketplace Kit URL detected", - actionTitle: "COPY", - presentationLocation: .withoutBottomBar, onAction: { - UIPasteboard.general.string = urlString - }) - return - } if let url = navigationAction.request.url { if !tabURLInterceptor.allowsNavigatingTo(url: url) { @@ -1902,6 +1889,10 @@ extension TabViewController: WKNavigationDelegate { let schemeType = SchemeHandler.schemeType(for: url) self.blobDownloadTargetFrame = nil switch schemeType { + case .allow: + completion(.allow) + return + case .navigational: performNavigationFor(url: url, navigationAction: navigationAction, From 2bab26ba8b01d2e48f45db8f8ca02aa422cd00d3 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 22 Oct 2024 17:29:57 +0100 Subject: [PATCH 06/26] Update BSK (#3468) Task/Issue URL: https://app.asana.com/0/1204167627774280/1208539744359975/f **Description**: No changes in code, just updating BSK to have the latest version --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5622a3bd47..436ac71509 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10958,7 +10958,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 200.0.0; + version = 200.1.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3a477518cb..de02322be0 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "9f62aacd878a0c05bff7256eb25b8776aa4e917f", - "version" : "200.0.0" + "revision" : "65cfb8b60909be241d4b5d11c764c1eb3f174534", + "version" : "200.1.0" } }, { From ec52eaae07443a9abec89c479443387b07b6741f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Wed, 23 Oct 2024 17:31:01 +0200 Subject: [PATCH 07/26] Use New Tab Page implementation without customization features (#3453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/72649045549333/1208547223289955/f Tech Design URL: CC: **Description**: Uses `NewTabPageController` as a home controller, but removes new tab page customization capabilities based on `newTabPageSections` feature flag state. Existing features of Home Screen should remain unchanged. Summary of changes done in this PR: * `SimpleNewTabPageView` now serving as a view for new tab page without customization features. Created based on the fully featured `NewTabPageView`. * Favorites placeholders and add button are not visible when customization flag is disabled. * Report action is not added to the browsing menu when link is missing. This prevents showing option which performs no action in case menu is shown on NTP. (cc @afterxleep) * Favorites data source adapter is listening for display mode changes and updates favorites accordingly. * Current (old) Dax Onboarding is integrated with the New Tab Page. (cc @alessandroboron) **Steps to test this PR**: #### Dax onboarding 1. Install fresh app 2. Go through onboarding, make sure Dax dialogs are shown properly on empty tab. #### Basic functionality 1. Make sure NTP flag is disabled in debug menu. 3. Without favorites, Dax logo should be visible. 4. Add some favorites. 5. It should be possible to long press to edit/remove and drag and drop to reorder. #### Toolbar menu items 1. Check toolbar displays bookmarks icon when NTP sections flag disabled. 2. Enable NTP sections flag in debug menu. 3. Reopen new tab page 4. Ensure browsing menu with shortcuts is available. #### Sync 1. Enable sync on two devices. 2. Verify updates for favorites are visible. **Definition of Done (Internal Only)**: * [x] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Copy Testing**: * [x] Use of correct apostrophes in new copy, ie `’` rather than `'` **Orientation Testing**: * [x] Portrait * [x] Landscape **Device Testing**: * [ ] iPhone SE (1st Gen) * [x] iPhone 8 * [ ] iPhone X * [x] iPhone 14 Pro * [ ] iPad **OS Testing**: * [x] iOS 15 * [ ] iOS 16 * [x] iOS 17 **Theme Testing**: * [x] Light theme * [x] Dark theme --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- DuckDuckGo.xcodeproj/project.pbxproj | 24 +- .../BrowsingMenu/BrowsingMenuAnimator.swift | 2 +- DuckDuckGo/DaxDialogViewController.swift | 15 +- DuckDuckGo/FavoritesFaviconLoader.swift | 6 +- ... => FavoritesListInteractingAdapter.swift} | 24 +- DuckDuckGo/FavoritesViewModel.swift | 19 +- DuckDuckGo/HomeScreenTransition.swift | 4 +- .../MainViewController+KeyCommands.swift | 4 +- DuckDuckGo/MainViewController.swift | 177 ++++---------- DuckDuckGo/NewTabPage.swift | 3 +- DuckDuckGo/NewTabPageView.swift | 11 + DuckDuckGo/NewTabPageViewController.swift | 121 ++++++++-- DuckDuckGo/NewTabPageViewModel.swift | 10 + DuckDuckGo/SimpleNewTabPageView.swift | 220 ++++++++++++++++++ DuckDuckGo/TabSwitcherTransition.swift | 2 +- ...bViewControllerBrowsingMenuExtension.swift | 14 +- ...FavoritesListInteractingAdapterTests.swift | 91 ++++++++ ... NewTabPageControllerDaxDialogTests.swift} | 25 +- .../NewTabPageFavoritesModelTests.swift | 42 +++- 19 files changed, 622 insertions(+), 192 deletions(-) rename DuckDuckGo/{FavoriteDataSource.swift => FavoritesListInteractingAdapter.swift} (72%) create mode 100644 DuckDuckGo/SimpleNewTabPageView.swift create mode 100644 DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift rename DuckDuckGoTests/{HomeViewControllerDaxDialogTests.swift => NewTabPageControllerDaxDialogTests.swift} (93%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 436ac71509..c86d5611cf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -268,7 +268,7 @@ 560E990F2BEE2CB800507CE0 /* SyncErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560E990E2BEE2CB800507CE0 /* SyncErrorMessage.swift */; }; 564DE4532C3ED1B700D23241 /* NewTabDaxDialogFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4522C3ED1B700D23241 /* NewTabDaxDialogFactory.swift */; }; 564DE4552C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4542C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift */; }; - 564DE4572C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */; }; + 564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */; }; 564DE45A2C450BE600D23241 /* DaxDialogsNewTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */; }; 564DE45E2C45218500D23241 /* OnboardingNavigationDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */; }; 564DE4602C4544CA00D23241 /* HomePageDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE45F2C4544CA00D23241 /* HomePageDependencies.swift */; }; @@ -306,7 +306,9 @@ 6F3537A42C4AC140009F8717 /* NewTabPageDaxLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3537A32C4AC140009F8717 /* NewTabPageDaxLogoView.swift */; }; 6F40D15B2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F40D15A2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift */; }; 6F40D15E2C34436500BF22F0 /* HomePageDisplayDailyPixelBucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F40D15C2C34436200BF22F0 /* HomePageDisplayDailyPixelBucketTests.swift */; }; + 6F5041C92CC11A5100989E48 /* SimpleNewTabPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5041C82CC11A5100989E48 /* SimpleNewTabPageView.swift */; }; 6F5345AF2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5345AE2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift */; }; + 6F5AA3EF2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5AA3EE2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift */; }; 6F5CC0812C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5CC0802C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift */; }; 6F64AA532C47E92600CF4489 /* FavoritesFaviconLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA522C47E92600CF4489 /* FavoritesFaviconLoader.swift */; }; 6F64AA592C4818D700CF4489 /* NewTabPageShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA582C4818D700CF4489 /* NewTabPageShortcut.swift */; }; @@ -361,7 +363,7 @@ 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */; }; 6FE1274B2C20943500EB5724 /* ShortcutItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */; }; 6FEC0B852C999352006B4F6E /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */; }; - 6FEC0B882C999961006B4F6E /* FavoriteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */; }; + 6FEC0B882C999961006B4F6E /* FavoritesListInteractingAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; @@ -1551,7 +1553,7 @@ 560E990E2BEE2CB800507CE0 /* SyncErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorMessage.swift; sourceTree = ""; }; 564DE4522C3ED1B700D23241 /* NewTabDaxDialogFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabDaxDialogFactory.swift; sourceTree = ""; }; 564DE4542C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingNewTabDialogFactoryTests.swift; sourceTree = ""; }; - 564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewControllerDaxDialogTests.swift; sourceTree = ""; }; + 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerDaxDialogTests.swift; sourceTree = ""; }; 564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogsNewTabTests.swift; sourceTree = ""; }; 564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationDelegateTests.swift; sourceTree = ""; }; 564DE45F2C4544CA00D23241 /* HomePageDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageDependencies.swift; sourceTree = ""; }; @@ -1589,7 +1591,9 @@ 6F3537A32C4AC140009F8717 /* NewTabPageDaxLogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageDaxLogoView.swift; sourceTree = ""; }; 6F40D15A2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageDisplayDailyPixelBucket.swift; sourceTree = ""; }; 6F40D15C2C34436200BF22F0 /* HomePageDisplayDailyPixelBucketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageDisplayDailyPixelBucketTests.swift; sourceTree = ""; }; + 6F5041C82CC11A5100989E48 /* SimpleNewTabPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleNewTabPageView.swift; sourceTree = ""; }; 6F5345AE2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsPersistentStorage.swift; sourceTree = ""; }; + 6F5AA3EE2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapterTests.swift; sourceTree = ""; }; 6F5CC0802C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleExpandButtonStyle.swift; sourceTree = ""; }; 6F64AA522C47E92600CF4489 /* FavoritesFaviconLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesFaviconLoader.swift; sourceTree = ""; }; 6F64AA582C4818D700CF4489 /* NewTabPageShortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageShortcut.swift; sourceTree = ""; }; @@ -1645,7 +1649,7 @@ 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageViewController.swift; sourceTree = ""; }; 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutItemView.swift; sourceTree = ""; }; 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = ""; }; - 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteDataSource.swift; sourceTree = ""; }; + 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapter.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; @@ -3852,8 +3856,9 @@ 6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */, 6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */, 6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */, - 564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */, + 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */, 6FABAA682C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift */, + 6F5AA3EE2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift */, ); name = NewTabPage; sourceTree = ""; @@ -3907,7 +3912,7 @@ 6FD3F8122C3EFDA200DA5797 /* FavoritesPreviewDataSource.swift */, 6FA3438E2C3D3BC300470677 /* Favorite.swift */, 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */, - 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */, + 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */, ); name = Model; sourceTree = ""; @@ -3958,6 +3963,7 @@ 6F03CAF82C32C3AA004179A8 /* Messages */, 6FE127372C20492500EB5724 /* NewTabPage.swift */, 6FD8E51F2C5BA23200345670 /* NewTabPageViewModel.swift */, + 6F5041C82CC11A5100989E48 /* SimpleNewTabPageView.swift */, 6FE127392C204BD000EB5724 /* NewTabPageView.swift */, 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */, 6FD3F8182C41252900DA5797 /* NewTabPageControllerDelegate.swift */, @@ -7462,6 +7468,7 @@ C185ED612BD4329700BAE9DC /* ImportPasswordsStatusHandler.swift in Sources */, CB9B8739278C8E72001F4906 /* WidgetEducationViewController.swift in Sources */, F4D9C4FA25117A0F00814B71 /* HomeMessageStorage.swift in Sources */, + 6F5041C92CC11A5100989E48 /* SimpleNewTabPageView.swift in Sources */, D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */, D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */, AA3D854523D9942200788410 /* AppIconSettingsViewController.swift in Sources */, @@ -7859,7 +7866,7 @@ 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, 37C696772C4957940073E131 /* RemoteMessagingDebugViewController.swift in Sources */, 31860A5B2C57ED2D005561F5 /* DuckPlayerStorage.swift in Sources */, - 6FEC0B882C999961006B4F6E /* FavoriteDataSource.swift in Sources */, + 6FEC0B882C999961006B4F6E /* FavoritesListInteractingAdapter.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, 6FEC0B852C999352006B4F6E /* FavoriteItem.swift in Sources */, @@ -7915,7 +7922,7 @@ 98DA35C4268CC81E00159906 /* DomainMatchingReportTests.swift in Sources */, 8590CB632684F10F0089F6BF /* ContentBlockerProtectionStoreTests.swift in Sources */, 83EDCC411F86B89C005CDFCD /* StatisticsLoaderTests.swift in Sources */, - 564DE4572C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift in Sources */, + 564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */, C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */, 85C29708247BDD060063A335 /* DaxDialogsBrowsingSpecTests.swift in Sources */, 9FE05CF12C36468A00D9046B /* OnboardingPixelReporterTests.swift in Sources */, @@ -8026,6 +8033,7 @@ C14882EA27F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift in Sources */, 1E05D1DB29C47B3300BF9A1F /* DailyPixelTests.swift in Sources */, 564DE4552C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift in Sources */, + 6F5AA3EF2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift in Sources */, 981FED7422046017008488D7 /* AutoClearTests.swift in Sources */, 98DDF9F322C4029D00DE38DB /* InitHelpers.swift in Sources */, B6AD9E3628D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift in Sources */, diff --git a/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift b/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift index 401993c4da..5fe45d94f5 100644 --- a/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift +++ b/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift @@ -106,7 +106,7 @@ final class BrowsingMenuAnimator: NSObject, UIViewControllerAnimatedTransitionin fromViewController.view.isHidden = true - if toViewController.homeViewController != nil { + if toViewController.newTabPageViewController != nil { toViewController.presentedMenuButton.setState(.bookmarksImage, animated: true) } else { toViewController.presentedMenuButton.setState(.menuImage, animated: true) diff --git a/DuckDuckGo/DaxDialogViewController.swift b/DuckDuckGo/DaxDialogViewController.swift index e231e6c566..78c524c2f5 100644 --- a/DuckDuckGo/DaxDialogViewController.swift +++ b/DuckDuckGo/DaxDialogViewController.swift @@ -42,7 +42,7 @@ class DaxDialogViewController: UIViewController { initCTA() } } - + func calculateHeight() -> CGFloat { guard let text = message ?? cta, !text.isEmpty else { return 370.0 } @@ -59,7 +59,7 @@ class DaxDialogViewController: UIViewController { let bottomMargin: CGFloat = 24.0 return iconHeight + topMargin + size.height + buttonHeight + bottomMargin } - + var onTapCta: (() -> Void)? private var position: Int = 0 @@ -188,3 +188,14 @@ extension DaxDialogViewController { } } } + +extension DaxDialogViewController { + static func loadFromStoryboard() -> DaxDialogViewController { + let storyboard = UIStoryboard(name: "DaxOnboarding", bundle: Bundle.main) + guard let controller = storyboard.instantiateViewController(identifier: "DaxDialog") as? DaxDialogViewController else { + fatalError("Failed to instantiate DaxDialogViewController from storyboard") + } + + return controller + } +} diff --git a/DuckDuckGo/FavoritesFaviconLoader.swift b/DuckDuckGo/FavoritesFaviconLoader.swift index 7adc516ce9..4716a21a48 100644 --- a/DuckDuckGo/FavoritesFaviconLoader.swift +++ b/DuckDuckGo/FavoritesFaviconLoader.swift @@ -40,8 +40,12 @@ actor FavoritesFaviconLoader: FavoritesFaviconLoading { } tasks[domain] = newTask + let value = await newTask.value + if value == nil { + tasks[domain] = nil + } - return await newTask.value + return value } nonisolated func existingFavicon(for favorite: Favorite, size: CGFloat) -> Favicon? { diff --git a/DuckDuckGo/FavoriteDataSource.swift b/DuckDuckGo/FavoritesListInteractingAdapter.swift similarity index 72% rename from DuckDuckGo/FavoriteDataSource.swift rename to DuckDuckGo/FavoritesListInteractingAdapter.swift index 921938d532..2f4e3fac30 100644 --- a/DuckDuckGo/FavoriteDataSource.swift +++ b/DuckDuckGo/FavoritesListInteractingAdapter.swift @@ -1,5 +1,5 @@ // -// FavoriteDataSource.swift +// FavoritesListInteractingAdapter.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -24,12 +24,30 @@ import Bookmarks final class FavoritesListInteractingAdapter: NewTabPageFavoriteDataSource { let favoritesListInteracting: FavoritesListInteracting + let appSettings: AppSettings - init(favoritesListInteracting: FavoritesListInteracting) { + private var cancellables: Set = [] + + private var displayModeSubject = PassthroughSubject() + + init(favoritesListInteracting: FavoritesListInteracting, appSettings: AppSettings = AppDependencyProvider.shared.appSettings) { self.favoritesListInteracting = favoritesListInteracting + self.appSettings = appSettings + self.externalUpdates = favoritesListInteracting.externalUpdates.merge(with: displayModeSubject).eraseToAnyPublisher() + + NotificationCenter.default.publisher(for: AppUserDefaults.Notifications.favoritesDisplayModeChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self else { + return + } + favoritesListInteracting.favoritesDisplayMode = self.appSettings.favoritesDisplayMode + displayModeSubject.send() + } + .store(in: &cancellables) } - var externalUpdates: AnyPublisher { favoritesListInteracting.externalUpdates } + let externalUpdates: AnyPublisher var favorites: [Favorite] { (try? favoritesListInteracting.favorites.map(Favorite.init)) ?? [] diff --git a/DuckDuckGo/FavoritesViewModel.swift b/DuckDuckGo/FavoritesViewModel.swift index 781d77a044..babd8e20c2 100644 --- a/DuckDuckGo/FavoritesViewModel.swift +++ b/DuckDuckGo/FavoritesViewModel.swift @@ -54,18 +54,23 @@ class FavoritesViewModel: ObservableObject { private let favoriteDataSource: NewTabPageFavoriteDataSource private let pixelFiring: PixelFiring.Type private let dailyPixelFiring: DailyPixelFiring.Type + private let isNewTabPageCustomizationEnabled: Bool var isEmpty: Bool { allFavorites.filter(\.isFavorite).isEmpty } - init(favoriteDataSource: NewTabPageFavoriteDataSource, + init(isNewTabPageCustomizationEnabled: Bool = false, + favoriteDataSource: NewTabPageFavoriteDataSource, faviconLoader: FavoritesFaviconLoading, pixelFiring: PixelFiring.Type = Pixel.self, dailyPixelFiring: DailyPixelFiring.Type = DailyPixel.self) { self.favoriteDataSource = favoriteDataSource self.pixelFiring = pixelFiring self.dailyPixelFiring = dailyPixelFiring + self.isNewTabPageCustomizationEnabled = isNewTabPageCustomizationEnabled + self.isCollapsed = isNewTabPageCustomizationEnabled + self.faviconLoader = MissingFaviconWrapper(loader: faviconLoader, onFaviconMissing: { [weak self] in guard let self else { return } @@ -73,8 +78,7 @@ class FavoritesViewModel: ObservableObject { self.faviconMissing() } }) - - + favoriteDataSource.externalUpdates.sink { [weak self] _ in self?.updateData() }.store(in: &cancellables) @@ -93,6 +97,10 @@ class FavoritesViewModel: ObservableObject { } func prefixedFavorites(for columnsCount: Int) -> FavoritesSlice { + guard isNewTabPageCustomizationEnabled else { + return .init(items: allFavorites, isCollapsible: false) + } + let hasFavorites = allFavorites.contains(where: \.isFavorite) let maxCollapsedItemsCount = hasFavorites ? columnsCount * 2 : columnsCount let isCollapsible = allFavorites.count > maxCollapsedItemsCount @@ -170,7 +178,10 @@ class FavoritesViewModel: ObservableObject { var allFavorites = favoriteDataSource.favorites.map { FavoriteItem.favorite($0) } - allFavorites.append(.addFavorite) + + if isNewTabPageCustomizationEnabled { + allFavorites.append(.addFavorite) + } self.allFavorites = allFavorites } diff --git a/DuckDuckGo/HomeScreenTransition.swift b/DuckDuckGo/HomeScreenTransition.swift index 8de6d19b3d..6fb2e8518b 100644 --- a/DuckDuckGo/HomeScreenTransition.swift +++ b/DuckDuckGo/HomeScreenTransition.swift @@ -90,7 +90,7 @@ class FromHomeScreenTransition: HomeScreenTransition { tabSwitcherViewController.view.frame = transitionContext.finalFrame(for: tabSwitcherViewController) tabSwitcherViewController.prepareForPresentation() - guard let homeScreen = mainViewController.homeController, + guard let homeScreen = mainViewController.newTabPageViewController, let tab = mainViewController.tabManager.model.currentTab, let rowIndex = tabSwitcherViewController.tabsModel.indexOf(tab: tab), let layoutAttr = tabSwitcherViewController.collectionView.layoutAttributesForItem(at: IndexPath(row: rowIndex, section: 0)) @@ -163,7 +163,7 @@ class ToHomeScreenTransition: HomeScreenTransition { prepareSubviews(using: transitionContext) guard let mainViewController = transitionContext.viewController(forKey: .to) as? MainViewController, - let homeScreen = mainViewController.homeController, + let homeScreen = mainViewController.newTabPageViewController, let tab = mainViewController.tabManager.model.currentTab, let rowIndex = tabSwitcherViewController.tabsModel.indexOf(tab: tab), let layoutAttr = tabSwitcherViewController.collectionView.layoutAttributesForItem(at: IndexPath(row: rowIndex, section: 0)) diff --git a/DuckDuckGo/MainViewController+KeyCommands.swift b/DuckDuckGo/MainViewController+KeyCommands.swift index 45f499a9b6..3e6bb42e7c 100644 --- a/DuckDuckGo/MainViewController+KeyCommands.swift +++ b/DuckDuckGo/MainViewController+KeyCommands.swift @@ -33,7 +33,7 @@ extension MainViewController { } var browsingCommands = [UIKeyCommand]() - if homeController == nil { + if newTabPageViewController == nil { browsingCommands = [ UIKeyCommand(title: "", action: #selector(keyboardFind), input: "f", modifierFlags: [.command], discoverabilityTitle: UserText.keyCommandFind), @@ -140,7 +140,7 @@ extension MainViewController { @objc func keyboardLocation() { guard tabSwitcherController == nil else { return } - if let controller = homeController { + if let controller = newTabPageViewController { controller.launchNewSearch() } else { showBars() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 7e90bf0129..78000f9d7b 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -77,12 +77,8 @@ class MainViewController: UIViewController { emailManager.requestDelegate = self return emailManager }() - - var homeViewController: HomeViewController? + var newTabPageViewController: NewTabPageViewController? - var homeController: (NewTabPage & HomeScreenTransitionSource)? { - homeViewController ?? newTabPageViewController - } var tabsBarController: TabsBarViewController? var suggestionTrayController: SuggestionTrayViewController? @@ -486,7 +482,7 @@ class MainViewController: UIViewController { @objc private func keyboardWillHide() { - if homeController?.isDragging == true, keyboardShowing { + if newTabPageViewController?.isDragging == true, keyboardShowing { Pixel.fire(pixel: .addressBarGestureDismiss) } } @@ -681,7 +677,6 @@ class MainViewController: UIViewController { } self.menuBookmarksViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode self.favoritesViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode - self.homeController?.reloadFavorites() WidgetCenter.shared.reloadAllTimelines() } } @@ -695,9 +690,6 @@ class MainViewController: UIViewController { syncUpdatesCancellable = syncDataProviders.bookmarksAdapter.syncDidCompletePublisher .sink { [weak self] _ in self?.favoritesViewModel.reloadData() - DispatchQueue.main.async { - self?.homeController?.reloadFavorites() - } } } @@ -796,53 +788,34 @@ class MainViewController: UIViewController { } let newTabDaxDialogFactory = NewTabDaxDialogFactory(delegate: self, contextualOnboardingLogic: DaxDialogs.shared, onboardingPixelReporter: contextualOnboardingPixelReporter) - if homeTabManager.isNewTabPageSectionsEnabled { - let controller = NewTabPageViewController(tab: tabModel, - interactionModel: favoritesViewModel, - syncService: syncService, - syncBookmarksAdapter: syncDataProviders.bookmarksAdapter, - homePageMessagesConfiguration: homePageConfiguration, - privacyProDataReporting: privacyProDataReporter, - variantManager: variantManager, - newTabDialogFactory: newTabDaxDialogFactory, - newTabDialogTypeProvider: DaxDialogs.shared, - faviconLoader: faviconLoader) - - controller.delegate = self - controller.shortcutsDelegate = self - controller.chromeDelegate = self - - newTabPageViewController = controller - addToContentContainer(controller: controller) - viewCoordinator.logoContainer.isHidden = true - adjustNewTabPageSafeAreaInsets(for: appSettings.currentAddressBarPosition) - } else { - let homePageDependencies = HomePageDependencies(homePageConfiguration: homePageConfiguration, - model: tabModel, - favoritesViewModel: favoritesViewModel, - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, - privacyProDataReporter: privacyProDataReporter, - variantManager: variantManager, - newTabDialogFactory: newTabDaxDialogFactory, - newTabDialogTypeProvider: DaxDialogs.shared) - let controller = HomeViewController.loadFromStoryboard(homePageDependecies: homePageDependencies) - - controller.delegate = self - controller.chromeDelegate = self - homeViewController = controller - addToContentContainer(controller: controller) - } + let controller = NewTabPageViewController(tab: tabModel, + isNewTabPageCustomizationEnabled: homeTabManager.isNewTabPageSectionsEnabled, + interactionModel: favoritesViewModel, + syncService: syncService, + syncBookmarksAdapter: syncDataProviders.bookmarksAdapter, + homePageMessagesConfiguration: homePageConfiguration, + privacyProDataReporting: privacyProDataReporter, + variantManager: variantManager, + newTabDialogFactory: newTabDaxDialogFactory, + newTabDialogTypeProvider: DaxDialogs.shared, + faviconLoader: faviconLoader) + + controller.delegate = self + controller.shortcutsDelegate = self + controller.chromeDelegate = self + + newTabPageViewController = controller + addToContentContainer(controller: controller) + viewCoordinator.logoContainer.isHidden = true + adjustNewTabPageSafeAreaInsets(for: appSettings.currentAddressBarPosition) refreshControls() syncService.scheduler.requestSyncImmediately() } fileprivate func removeHomeScreen() { - homeController?.willMove(toParent: nil) - homeController?.dismiss() - homeViewController = nil + newTabPageViewController?.willMove(toParent: nil) + newTabPageViewController?.dismiss() newTabPageViewController = nil } @@ -1193,7 +1166,7 @@ class MainViewController: UIViewController { func refreshMenuButtonState() { let expectedState: MenuButton.State - if homeViewController != nil { + if !homeTabManager.isNewTabPageSectionsEnabled && newTabPageViewController != nil { expectedState = .bookmarksImage viewCoordinator.lastToolbarButton.accessibilityLabel = UserText.bookmarksButtonHint viewCoordinator.omniBar.menuButton.accessibilityLabel = UserText.bookmarksButtonHint @@ -1432,18 +1405,7 @@ class MainViewController: UIViewController { attachHomeScreen() tabsBarController?.refresh(tabsModel: tabManager.model) swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) - homeController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) - } - - func animateLogoAppearance() { - viewCoordinator.logoContainer.alpha = 0 - viewCoordinator.logoContainer.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - UIView.animate(withDuration: 0.2) { - self.viewCoordinator.logoContainer.alpha = 1 - self.viewCoordinator.logoContainer.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) - } - } + newTabPageViewController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) } func updateFindInPage() { @@ -1753,7 +1715,7 @@ extension MainViewController: BrowserChromeDelegate { updateBlock() } } - + func setNavigationBarHidden(_ hidden: Bool) { if hidden { hideKeyboard() } @@ -1823,7 +1785,7 @@ extension MainViewController: OmniBarDelegate { func onOmniQueryUpdated(_ updatedQuery: String) { if updatedQuery.isEmpty { - if homeController != nil { + if newTabPageViewController != nil { hideSuggestionTray() } else { let didShow = tryToShowSuggestionTray(.favorites) @@ -1892,7 +1854,7 @@ extension MainViewController: OmniBarDelegate { let menuEntries: [BrowsingMenuEntry] let headerEntries: [BrowsingMenuEntry] - if isNewTabPageVisible { + if homeTabManager.isNewTabPageSectionsEnabled && newTabPageViewController != nil { menuEntries = tab.buildShortcutsMenu() headerEntries = [] } else { @@ -1933,7 +1895,7 @@ extension MainViewController: OmniBarDelegate { } func fireControllerAwarePixel(ntp: Pixel.Event, serp: Pixel.Event, website: Pixel.Event) { - if homeController != nil { + if newTabPageViewController != nil { Pixel.fire(pixel: ntp) } else if let currentTab { if currentTab.url?.isDuckDuckGoSearch == true { @@ -2005,7 +1967,7 @@ extension MainViewController: OmniBarDelegate { fireControllerAwarePixel(ntp: .addressBarClickOnNTP, serp: .addressBarClickOnSERP, website: .addressBarClickOnWebsite) } - guard homeController == nil else { return } + guard newTabPageViewController == nil else { return } if !skipSERPFlow, isSERPPresented, let query = omniBar.textField.text { tryToShowSuggestionTray(.autocomplete(query: query)) @@ -2022,10 +1984,10 @@ extension MainViewController: OmniBarDelegate { if !DaxDialogs.shared.shouldShowFireButtonPulse { ViewHighlighter.hideAll() } - guard let homeController = homeController else { + guard let newTabPageViewController = newTabPageViewController else { return selectQueryText } - homeController.launchNewSearch() + newTabPageViewController.launchNewSearch() return selectQueryText } @@ -2065,7 +2027,7 @@ extension MainViewController: FavoritesOverlayDelegate { func favoritesOverlay(_ overlay: FavoritesOverlay, didSelect favorite: BookmarkEntity) { guard let url = favorite.urlObject else { return } Pixel.fire(pixel: .favoriteLaunchedWebsite) - homeViewController?.chromeDelegate = nil + newTabPageViewController?.chromeDelegate = nil dismissOmniBar() Favicons.shared.loadFavicon(forDomain: url.host, intoCache: .fireproof, fromCache: .tabs) if url.isBookmarklet() { @@ -2088,7 +2050,7 @@ extension MainViewController: AutocompleteViewControllerDelegate { } func autocomplete(selectedSuggestion suggestion: Suggestion) { - homeViewController?.chromeDelegate = nil + newTabPageViewController?.chromeDelegate = nil dismissOmniBar() viewCoordinator.omniBar.cancel() switch suggestion { @@ -2113,7 +2075,7 @@ extension MainViewController: AutocompleteViewControllerDelegate { loadUrl(url) case .openTab(title: _, url: let url): - if homeViewController != nil, let tab = tabManager.model.currentTab { + if newTabPageViewController != nil, let tab = tabManager.model.currentTab { self.closeTab(tab) } loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: .noAttribution) @@ -2194,49 +2156,6 @@ extension MainViewController { } } -extension MainViewController: HomeControllerDelegate { - - func home(_ home: HomeViewController, didRequestQuery query: String) { - loadQueryInNewTab(query) - } - - func home(_ home: HomeViewController, didRequestUrl url: URL) { - handleRequestedURL(url) - } - - func home(_ home: HomeViewController, didRequestEdit favorite: BookmarkEntity) { - segueToEditBookmark(favorite) - } - - func home(_ home: HomeViewController, didRequestContentOverflow shouldOverflow: Bool) -> CGFloat { - allowContentUnderflow = shouldOverflow - return contentUnderflow - } - - func homeDidDeactivateOmniBar(home: HomeViewController) { - hideSuggestionTray() - dismissOmniBar() - } - - func showSettings(_ home: HomeViewController) { - segueToSettings() - } - - func home(_ home: HomeViewController, didRequestHideLogo hidden: Bool) { - viewCoordinator.logoContainer.isHidden = hidden - } - - func homeDidRequestLogoContainer(_ home: HomeViewController) -> UIView { - return viewCoordinator.logoContainer - } - - func home(_ home: HomeViewController, searchTransitionUpdated percent: CGFloat) { - viewCoordinator.statusBackground.alpha = percent - viewCoordinator.navigationBarContainer.alpha = percent - } - -} - extension MainViewController: NewTabPageControllerDelegate { func newTabPageDidOpenFavoriteURL(_ controller: NewTabPageViewController, url: URL) { handleRequestedURL(url) @@ -2512,17 +2431,19 @@ extension MainViewController: TabDelegate { extension MainViewController: TabSwitcherDelegate { + private func animateLogoAppearance() { + newTabPageViewController?.view.transform = CGAffineTransform().scaledBy(x: 0.5, y: 0.5) + newTabPageViewController?.view.alpha = 0.0 + UIView.animate(withDuration: 0.2, delay: 0.1, options: [.curveEaseInOut, .beginFromCurrentState]) { + self.newTabPageViewController?.view.transform = .identity + self.newTabPageViewController?.view.alpha = 1.0 + } + } + func tabSwitcherDidRequestNewTab(tabSwitcher: TabSwitcherViewController) { newTab() - if homeViewController != nil { + if newTabPageViewController != nil { animateLogoAppearance() - } else if newTabPageViewController != nil { - newTabPageViewController?.view.transform = CGAffineTransform().scaledBy(x: 0.5, y: 0.5) - newTabPageViewController?.view.alpha = 0.0 - UIView.animate(withDuration: 0.2, delay: 0.1, options: [.curveEaseInOut, .beginFromCurrentState]) { - self.newTabPageViewController?.view.transform = .identity - self.newTabPageViewController?.view.alpha = 1.0 - } } } @@ -2540,7 +2461,7 @@ extension MainViewController: TabSwitcherDelegate { // switcher is still presented. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { tabSwitcher.dismiss(animated: true) { - self.homeController?.viewDidAppear(true) + self.newTabPageViewController?.viewDidAppear(true) } } } @@ -2746,7 +2667,7 @@ extension MainViewController: AutoClearWorker { // Ideally this should happen once data clearing has finished AND the animation is finished if showNextDaxDialog { - self.homeController?.showNextDaxDialog() + self.newTabPageViewController?.showNextDaxDialog() } else if KeyboardSettings().onNewTab { let showKeyboardAfterFireButton = DispatchWorkItem { self.enterSearch() @@ -2840,7 +2761,7 @@ extension MainViewController: OnboardingDelegate { markOnboardingSeen() controller.modalTransitionStyle = .crossDissolve controller.dismiss(animated: true) - homeController?.onboardingCompleted() + newTabPageViewController?.onboardingCompleted() } func markOnboardingSeen() { diff --git a/DuckDuckGo/NewTabPage.swift b/DuckDuckGo/NewTabPage.swift index 7be2446e29..eb382f9b5e 100644 --- a/DuckDuckGo/NewTabPage.swift +++ b/DuckDuckGo/NewTabPage.swift @@ -21,8 +21,7 @@ import UIKit protocol NewTabPage: UIViewController { - var isDragging: Bool { get } // TODO: Mariusz, check if needed in both - func reloadFavorites() // TODO: Mariusz: check if needed with reactive approach + var isDragging: Bool { get } func launchNewSearch() func openedAsNewTab(allowingKeyboard: Bool) diff --git a/DuckDuckGo/NewTabPageView.swift b/DuckDuckGo/NewTabPageView.swift index aaed282ca5..219ddd9c27 100644 --- a/DuckDuckGo/NewTabPageView.swift +++ b/DuckDuckGo/NewTabPageView.swift @@ -34,6 +34,8 @@ struct NewTabPageView: View { @State private var customizeButtonShowedInline = false @State private var isAddingFavorite: Bool = false + @State var isDragging: Bool = false + init(viewModel: NewTabPageViewModel, messagesModel: NewTabPageMessagesModel, favoritesViewModel: FavoritesViewModel, @@ -71,6 +73,15 @@ struct NewTabPageView: View { sectionsSettingsModel: sectionsSettingsModel) } }) + .simultaneousGesture( + DragGesture() + .onChanged({ value in + if value.translation.height > 0 { + viewModel.beginDragging() + } + }) + .onEnded({ _ in viewModel.endDragging() }) + ) } } diff --git a/DuckDuckGo/NewTabPageViewController.swift b/DuckDuckGo/NewTabPageViewController.swift index ee3d0bb4a0..0f24599de6 100644 --- a/DuckDuckGo/NewTabPageViewController.swift +++ b/DuckDuckGo/NewTabPageViewController.swift @@ -23,7 +23,7 @@ import Bookmarks import BrowserServicesKit import Core -final class NewTabPageViewController: UIHostingController, NewTabPage { +final class NewTabPageViewController: UIHostingController, NewTabPage { private let syncService: DDGSyncing private let syncBookmarksAdapter: SyncBookmarksAdapter @@ -43,7 +43,15 @@ final class NewTabPageViewController: UIHostingController, NewTa private var hostingController: UIHostingController? + private weak var daxDialogViewController: DaxDialogViewController? + private var daxDialogHeightConstraint: NSLayoutConstraint? + + var isDaxDialogVisible: Bool { + daxDialogViewController?.view.isHidden == false + } + init(tab: Tab, + isNewTabPageCustomizationEnabled: Bool, interactionModel: FavoritesListInteracting, syncService: DDGSyncing, syncBookmarksAdapter: SyncBookmarksAdapter, @@ -64,23 +72,35 @@ final class NewTabPageViewController: UIHostingController, NewTa newTabPageViewModel = NewTabPageViewModel() shortcutsSettingsModel = NewTabPageShortcutsSettingsModel() sectionsSettingsModel = NewTabPageSectionsSettingsModel() - favoritesModel = FavoritesViewModel(favoriteDataSource: FavoritesListInteractingAdapter(favoritesListInteracting: interactionModel), faviconLoader: faviconLoader) + favoritesModel = FavoritesViewModel(isNewTabPageCustomizationEnabled: isNewTabPageCustomizationEnabled, + favoriteDataSource: FavoritesListInteractingAdapter(favoritesListInteracting: interactionModel), + faviconLoader: faviconLoader) shortcutsModel = ShortcutsModel() messagesModel = NewTabPageMessagesModel(homePageMessagesConfiguration: homePageMessagesConfiguration, privacyProDataReporter: privacyProDataReporting) - let newTabPageView = NewTabPageView(viewModel: newTabPageViewModel, - messagesModel: messagesModel, - favoritesViewModel: favoritesModel, - shortcutsModel: shortcutsModel, - shortcutsSettingsModel: shortcutsSettingsModel, - sectionsSettingsModel: sectionsSettingsModel) - - super.init(rootView: newTabPageView) + if isNewTabPageCustomizationEnabled { + super.init(rootView: AnyView(NewTabPageView(viewModel: self.newTabPageViewModel, + messagesModel: self.messagesModel, + favoritesViewModel: self.favoritesModel, + shortcutsModel: self.shortcutsModel, + shortcutsSettingsModel: self.shortcutsSettingsModel, + sectionsSettingsModel: self.sectionsSettingsModel))) + } else { + super.init(rootView: AnyView(SimpleNewTabPageView(viewModel: self.newTabPageViewModel, + messagesModel: self.messagesModel, + favoritesViewModel: self.favoritesModel))) + } assignFavoriteModelActions() assignShorcutsModelActions() } + override func viewDidLoad() { + super.viewDidLoad() + + setUpDaxDialog() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -90,6 +110,34 @@ final class NewTabPageViewController: UIHostingController, NewTa Pixel.fire(pixel: .homeScreenShown) sendDailyDisplayPixel() + + view.backgroundColor = UIColor(designSystemColor: .background) + } + + private func setUpDaxDialog() { + let daxDialogController = DaxDialogViewController.loadFromStoryboard() + guard let dialogView = daxDialogController.view else { return } + + self.addChild(daxDialogController) + self.view.addSubview(dialogView) + + dialogView.translatesAutoresizingMaskIntoConstraints = false + dialogView.isHidden = true + + let widthConstraint = dialogView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, multiplier: 1) + widthConstraint.priority = .defaultHigh + let heightConstraint = dialogView.heightAnchor.constraint(equalToConstant: 250) + daxDialogHeightConstraint = heightConstraint + NSLayoutConstraint.activate([ + dialogView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44.0), + dialogView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + dialogView.widthAnchor.constraint(lessThanOrEqualToConstant: 375), + heightConstraint, + widthConstraint + ]) + + daxDialogController.didMove(toParent: self) + daxDialogViewController = daxDialogController } // MARK: - Private @@ -140,7 +188,7 @@ final class NewTabPageViewController: UIHostingController, NewTa // MARK: - NewTabPage - let isDragging: Bool = false + var isDragging: Bool { newTabPageViewModel.isDragging } weak var chromeDelegate: BrowserChromeDelegate? weak var delegate: NewTabPageControllerDelegate? @@ -151,12 +199,18 @@ final class NewTabPageViewController: UIHostingController, NewTa } func openedAsNewTab(allowingKeyboard: Bool) { - guard allowingKeyboard && KeyboardSettings().onNewTab else { return } + if allowingKeyboard && KeyboardSettings().onNewTab { - // The omnibar is inside a collection view so this needs a chance to do its thing - // which might also be async. Not great. - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.launchNewSearch() + // The omnibar is inside a collection view so this needs a chance to do its thing + // which might also be async. Not great. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.launchNewSearch() + } + } + + if !variantManager.isContextualDaxDialogsEnabled { + // In the new onboarding this gets called twice (viewDidAppear in Tab) which then reset the spec to nil. + presentNextDaxDialog() } } @@ -165,7 +219,7 @@ final class NewTabPageViewController: UIHostingController, NewTa } func showNextDaxDialog() { - showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory) + presentNextDaxDialog() } func onboardingCompleted() { @@ -181,6 +235,8 @@ final class NewTabPageViewController: UIHostingController, NewTa private func presentNextDaxDialog() { if variantManager.isContextualDaxDialogsEnabled { showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory) + } else { + showNextDaxDialog(dialogProvider: newTabDialogTypeProvider) } } @@ -218,6 +274,37 @@ extension NewTabPageViewController: HomeScreenTransitionSource { extension NewTabPageViewController { + func showNextDaxDialog(dialogProvider: NewTabDialogSpecProvider) { + guard let spec = dialogProvider.nextHomeScreenMessage() else { return } + guard !isDaxDialogVisible else { return } + guard let daxDialogViewController = daxDialogViewController else { return } + + newTabPageViewModel.startOnboarding() + + daxDialogViewController.view.isHidden = false + daxDialogViewController.view.alpha = 0.0 + + daxDialogViewController.loadViewIfNeeded() + daxDialogViewController.message = spec.message + daxDialogViewController.accessibleMessage = spec.accessibilityLabel + + if spec == .initial { + UniquePixel.fire(pixel: .onboardingContextualTryVisitSiteUnique, includedParameters: [.appVersion, .atb]) + } + + view.addGestureRecognizer(daxDialogViewController.tapToCompleteGestureRecognizer) + + daxDialogHeightConstraint?.constant = daxDialogViewController.calculateHeight() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + UIView.animate(withDuration: 0.4, animations: { + daxDialogViewController.view.alpha = 1.0 + }, completion: { _ in + daxDialogViewController.start() + }) + } + } + func showNextDaxDialogNew(dialogProvider: NewTabDialogSpecProvider, factory: any NewTabDaxDialogProvider) { dismissHostingController(didFinishNTPOnboarding: false) diff --git a/DuckDuckGo/NewTabPageViewModel.swift b/DuckDuckGo/NewTabPageViewModel.swift index b53a7625ea..6cb387b402 100644 --- a/DuckDuckGo/NewTabPageViewModel.swift +++ b/DuckDuckGo/NewTabPageViewModel.swift @@ -26,6 +26,8 @@ final class NewTabPageViewModel: ObservableObject { @Published private(set) var isOnboarding: Bool @Published var isShowingSettings: Bool + private(set) var isDragging: Bool = false + private var introDataStorage: NewTabPageIntroDataStoring private let pixelFiring: PixelFiring.Type @@ -67,4 +69,12 @@ final class NewTabPageViewModel: ObservableObject { func finishOnboarding() { isOnboarding = false } + + func beginDragging() { + isDragging = true + } + + func endDragging() { + isDragging = false + } } diff --git a/DuckDuckGo/SimpleNewTabPageView.swift b/DuckDuckGo/SimpleNewTabPageView.swift new file mode 100644 index 0000000000..cf31697226 --- /dev/null +++ b/DuckDuckGo/SimpleNewTabPageView.swift @@ -0,0 +1,220 @@ +// +// SimpleNewTabPageView.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 +import DuckUI +import RemoteMessaging + +struct SimpleNewTabPageView: View { + @Environment(\.horizontalSizeClass) var horizontalSizeClass + + @ObservedObject private var viewModel: NewTabPageViewModel + @ObservedObject private var messagesModel: NewTabPageMessagesModel + @ObservedObject private var favoritesViewModel: FavoritesViewModel + + init(viewModel: NewTabPageViewModel, + messagesModel: NewTabPageMessagesModel, + favoritesViewModel: FavoritesViewModel) { + self.viewModel = viewModel + self.messagesModel = messagesModel + self.favoritesViewModel = favoritesViewModel + + self.messagesModel.load() + } + + private var isShowingSections: Bool { + !favoritesViewModel.allFavorites.isEmpty + } + + var body: some View { + if !viewModel.isOnboarding { + mainView + .background(Color(designSystemColor: .background)) + .simultaneousGesture( + DragGesture() + .onChanged({ value in + if value.translation.height > 0 { + viewModel.beginDragging() + } + }) + .onEnded({ _ in viewModel.endDragging() }) + ) + } + } + + @ViewBuilder + private var mainView: some View { + if isShowingSections { + sectionsView + } else { + emptyStateView + } + } +} + +private extension SimpleNewTabPageView { + // MARK: - Views + @ViewBuilder + private var sectionsView: some View { + GeometryReader { proxy in + ScrollView { + VStack(spacing: Metrics.sectionSpacing) { + + messagesSectionView + .padding(.top, Metrics.nonGridSectionTopPadding) + + favoritesSectionView(proxy: proxy) + } + .padding(Metrics.largePadding) + } + .withScrollKeyboardDismiss() + } + } + + @ViewBuilder + private var emptyStateView: some View { + ZStack { + NewTabPageDaxLogoView() + + VStack(spacing: Metrics.sectionSpacing) { + messagesSectionView + .padding(.top, Metrics.nonGridSectionTopPadding) + .frame(maxHeight: .infinity, alignment: .top) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + } + .padding(Metrics.largePadding) + } + + private var messagesSectionView: some View { + ForEach(messagesModel.homeMessageViewModels, id: \.messageId) { messageModel in + HomeMessageView(viewModel: messageModel) + .frame(maxWidth: horizontalSizeClass == .regular ? Metrics.messageMaximumWidthPad : Metrics.messageMaximumWidth) + .transition(.scale.combined(with: .opacity)) + } + } + + private func favoritesSectionView(proxy: GeometryProxy) -> some View { + FavoritesView(model: favoritesViewModel, + isAddingFavorite: .constant(false), + geometry: proxy) + } +} + +private extension View { + @ViewBuilder + func withScrollKeyboardDismiss() -> some View { + if #available(iOS 16, *) { + scrollDismissesKeyboard(.immediately) + } else { + self + } + } +} + +private struct Metrics { + + static let regularPadding = 16.0 + static let largePadding = 24.0 + static let sectionSpacing = 32.0 + static let nonGridSectionTopPadding = -8.0 + + static let messageMaximumWidth: CGFloat = 380 + static let messageMaximumWidthPad: CGFloat = 455 +} + +// MARK: - Preview + +#Preview("Regular") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [] + ) + ), + favoritesViewModel: FavoritesPreviewModel() + ) +} + +#Preview("With message") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [ + HomeMessage.remoteMessage( + remoteMessage: RemoteMessageModel( + id: "0", + content: .small(titleText: "Title", descriptionText: "Description"), + matchingRules: [], + exclusionRules: [], + isMetricsEnabled: false + ) + ) + ] + ) + ), + favoritesViewModel: FavoritesPreviewModel() + ) +} + +#Preview("No favorites") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [] + ) + ), + favoritesViewModel: FavoritesPreviewModel(favorites: []) + ) +} + +#Preview("Empty") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [] + ) + ), + favoritesViewModel: FavoritesPreviewModel() + ) +} + +private final class PreviewMessagesConfiguration: HomePageMessagesConfiguration { + private(set) var homeMessages: [HomeMessage] + + init(homeMessages: [HomeMessage]) { + self.homeMessages = homeMessages + } + + func refresh() { + + } + + func didAppear(_ homeMessage: HomeMessage) { + // no-op + } + + func dismissHomeMessage(_ homeMessage: HomeMessage) { + homeMessages = homeMessages.dropLast() + } +} diff --git a/DuckDuckGo/TabSwitcherTransition.swift b/DuckDuckGo/TabSwitcherTransition.swift index 5aebcad997..ea098cd149 100644 --- a/DuckDuckGo/TabSwitcherTransition.swift +++ b/DuckDuckGo/TabSwitcherTransition.swift @@ -86,7 +86,7 @@ class TabSwitcherTransitionDelegate: NSObject, UIViewControllerTransitioningDele return nil } - if mainVC.homeController != nil { + if mainVC.newTabPageViewController != nil { return FromHomeScreenTransition(mainViewController: mainVC, tabSwitcherViewController: tabSwitcherVC) } diff --git a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift index 12ef1e53ee..8e8d715052 100644 --- a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift @@ -84,12 +84,14 @@ extension TabViewController { entries.append(self.buildToggleProtectionEntry(forDomain: domain)) } - let name = UserText.actionReportBrokenSite - entries.append(BrowsingMenuEntry.regular(name: name, - image: UIImage(named: "Feedback-16")!, - action: { [weak self] in - self?.onReportBrokenSiteAction() - })) + if link != nil { + let name = UserText.actionReportBrokenSite + entries.append(BrowsingMenuEntry.regular(name: name, + image: UIImage(named: "Feedback-16")!, + action: { [weak self] in + self?.onReportBrokenSiteAction() + })) + } entries.append(.separator) diff --git a/DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift b/DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift new file mode 100644 index 0000000000..dcdebd766c --- /dev/null +++ b/DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift @@ -0,0 +1,91 @@ +// +// FavoritesListInteractingAdapterTests.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 XCTest +import Combine +import Bookmarks + +@testable import DuckDuckGo + +final class FavoritesListInteractingAdapterTests: XCTestCase { + + private var favoritesListInteracting: MockFavoritesListInteracting! + private var appSettings: AppSettingsMock! + + private var cancellables: Set = [] + + override func setUpWithError() throws { + favoritesListInteracting = MockFavoritesListInteracting() + appSettings = AppSettingsMock() + } + + override func tearDownWithError() throws { + cancellables.removeAll() + } + + func testPublishesUpdateWhenFavoritesDisplayModeChanges() { + let expectation = XCTestExpectation(description: #function) + let sut = createSUT() + + sut.externalUpdates.sink { + XCTAssertTrue(Thread.isMainThread) + expectation.fulfill() + } + .store(in: &cancellables) + + NotificationCenter.default.post(name: AppUserDefaults.Notifications.favoritesDisplayModeChange, object: nil) + + wait(for: [expectation], timeout: 0.1) + } + + func testPublishesUpdateOnExternalListUpdate() { + let expectation = XCTestExpectation(description: #function) + let publisher = PassthroughSubject() + favoritesListInteracting.externalUpdates = publisher.eraseToAnyPublisher() + + let sut = createSUT() + + sut.externalUpdates.sink { + XCTAssertTrue(Thread.isMainThread) + expectation.fulfill() + } + .store(in: &cancellables) + + publisher.send() + + wait(for: [expectation], timeout: 0.1) + } + + private func createSUT() -> FavoritesListInteractingAdapter { + return FavoritesListInteractingAdapter(favoritesListInteracting: favoritesListInteracting, appSettings: appSettings) + } +} + +private class FavoritesListInteractingMock: FavoritesListInteracting { + var favoritesDisplayMode: Bookmarks.FavoritesDisplayMode = .displayNative(.mobile) + var favorites: [Bookmarks.BookmarkEntity] = [] + func favorite(at index: Int) -> Bookmarks.BookmarkEntity? { + return nil + } + func removeFavorite(_ favorite: Bookmarks.BookmarkEntity) {} + func moveFavorite(_ favorite: Bookmarks.BookmarkEntity, fromIndex: Int, toIndex: Int) { } + var externalUpdates: AnyPublisher = PassthroughSubject().eraseToAnyPublisher() + var localUpdates: AnyPublisher = PassthroughSubject().eraseToAnyPublisher() + func reloadData() {} +} diff --git a/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift b/DuckDuckGoTests/NewTabPageControllerDaxDialogTests.swift similarity index 93% rename from DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift rename to DuckDuckGoTests/NewTabPageControllerDaxDialogTests.swift index 66c9506142..440a2934df 100644 --- a/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift +++ b/DuckDuckGoTests/NewTabPageControllerDaxDialogTests.swift @@ -1,5 +1,5 @@ // -// HomeViewControllerDaxDialogTests.swift +// NewTabPageControllerDaxDialogTests.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -26,12 +26,12 @@ import SwiftUI import Persistence import BrowserServicesKit -final class HomeViewControllerDaxDialogTests: XCTestCase { +final class NewTabPageControllerDaxDialogTests: XCTestCase { var variantManager: CapturingVariantManager! var dialogFactory: CapturingNewTabDaxDialogProvider! var specProvider: MockNewTabDialogSpecProvider! - var hvc: HomeViewController! + var hvc: NewTabPageViewController! override func setUpWithError() throws { let db = CoreDataDatabase.bookmarksMock @@ -56,19 +56,18 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { remoteMessagingAvailabilityProvider: MockRemoteMessagingAvailabilityProviding(), duckPlayerStorage: MockDuckPlayerStorage()) let homePageConfiguration = HomePageConfiguration(remoteMessagingClient: remoteMessagingClient, privacyProDataReporter: MockPrivacyProDataReporter()) - let dependencies = HomePageDependencies( - homePageConfiguration: homePageConfiguration, - model: Tab(), - favoritesViewModel: MockFavoritesListInteracting(), - appSettings: AppSettingsMock(), + hvc = NewTabPageViewController( + tab: Tab(), + isNewTabPageCustomizationEnabled: false, + interactionModel: MockFavoritesListInteracting(), syncService: MockDDGSyncing(authState: .active, isSyncInProgress: false), - syncDataProviders: dataProviders, - privacyProDataReporter: MockPrivacyProDataReporter(), + syncBookmarksAdapter: dataProviders.bookmarksAdapter, + homePageMessagesConfiguration: homePageConfiguration, variantManager: variantManager, newTabDialogFactory: dialogFactory, - newTabDialogTypeProvider: specProvider) - hvc = HomeViewController.loadFromStoryboard( - homePageDependecies: dependencies) + newTabDialogTypeProvider: specProvider, + faviconLoader: EmptyFaviconLoading() + ) let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() diff --git a/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift b/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift index 09a02148ca..758ddde88e 100644 --- a/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift +++ b/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift @@ -50,6 +50,13 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertEqual(PixelFiringMock.lastPixelName, Pixel.Event.newTabPageFavoritesSeeLess.name) } + func testReturnsAllFavoritesWhenCustomizationDisabled() { + favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + XCTAssertEqual(sut.prefixedFavorites(for: 1).items.count, 10) + } + func testFiresPixelsOnFavoriteSelected() { let sut = createSUT() @@ -99,6 +106,16 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertFalse(slice.isCollapsible) } + func testPrefixFavoritesDoesNotCreatePlaceholdersWhenCustomizationDisabled() { + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + let slice = sut.prefixedFavorites(for: 3) + + XCTAssertTrue(slice.items.filter(\.isPlaceholder).isEmpty) + XCTAssertTrue(slice.items.isEmpty) + XCTAssertFalse(slice.isCollapsible) + } + func testPrefixFavoritesLimitsToTwoRows() { favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) let sut = createSUT() @@ -109,6 +126,16 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertTrue(slice.isCollapsible) } + func testListNotCollapsibleWhenCustomizationDisabled() { + favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) + + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + let favorites = sut.prefixedFavorites(for: 1) + XCTAssertFalse(favorites.isCollapsible) + XCTAssertFalse(sut.isCollapsed) + } + func testAddItemIsLastWhenFavoritesPresent() throws { favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) let sut = createSUT() @@ -126,8 +153,19 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertTrue(firstItem == .addFavorite) } - private func createSUT() -> FavoritesViewModel { - FavoritesViewModel(favoriteDataSource: favoriteDataSource, + func testDoesNotAppendAddItemWhenCustomizationDisabled() { + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + XCTAssertNil(sut.allFavorites.first) + + favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) + + XCTAssertNil(sut.allFavorites.first(where: { $0 == .addFavorite })) + } + + private func createSUT(isNewTabPageCustomizationEnabled: Bool = true) -> FavoritesViewModel { + FavoritesViewModel(isNewTabPageCustomizationEnabled: isNewTabPageCustomizationEnabled, + favoriteDataSource: favoriteDataSource, faviconLoader: FavoritesFaviconLoader(), pixelFiring: PixelFiringMock.self, dailyPixelFiring: PixelFiringMock.self) From 7af9a58ab375054439e399ea1167aeaf9f8c348e Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Thu, 24 Oct 2024 11:13:07 +0200 Subject: [PATCH 08/26] Bump BSK to the version with newTabSearchField feature flag (#3449) Task/Issue URL: https://app.asana.com/0/72649045549333/1208262951113965/f Description: This change updates BSK to the version that will be used on macOS. It doesn't affect iOS at all. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c86d5611cf..96cbf96171 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10966,7 +10966,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 200.1.0; + version = 200.2.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index de02322be0..5f6afa28c4 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "65cfb8b60909be241d4b5d11c764c1eb3f174534", - "version" : "200.1.0" + "revision" : "a7de5bc2fe128444495e6e8345026c8974076ad7", + "version" : "200.2.0" } }, { From fc4d47316d48fccfaf22da2174af1329c91f42c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Thu, 24 Oct 2024 12:31:31 +0200 Subject: [PATCH 09/26] Set UserDefaults API access reason to 1C8F.1 (#3475) Task/Issue URL: https://app.asana.com/0/414709148257752/1208607157395789/f Tech Design URL: CC: **Description**: Updates UserDefaults API usage declaration `1C8F.1`. **Steps to test this PR**: 1. Verify tests pass. **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- DuckDuckGo/PrivacyInfo.xcprivacy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/PrivacyInfo.xcprivacy b/DuckDuckGo/PrivacyInfo.xcprivacy index 15d10330da..98e99b1d0f 100644 --- a/DuckDuckGo/PrivacyInfo.xcprivacy +++ b/DuckDuckGo/PrivacyInfo.xcprivacy @@ -22,7 +22,7 @@ NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons - CA92.1 + 1C8F.1 From a0be7f8f58e21c8586b0f81fdc216eb602f77499 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Thu, 24 Oct 2024 13:07:09 +0200 Subject: [PATCH 10/26] For BLOB downloads make sure the file is not preview-able before triggering the download (#3472) Task/Issue URL: https://app.asana.com/0/414709148257752/1207504349745342/f **Description**: When wallet passes are served as BLOB type downloads they just trigger the download prompt instead of directly open their preview allowing adding them to Wallet app. --- DuckDuckGo/TabViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 09490abf98..626d240649 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -2171,7 +2171,7 @@ extension TabViewController { return } - if self.shouldTriggerDownloadAction(for: navigationResponse) { + if self.shouldTriggerDownloadAction(for: navigationResponse) && !FilePreviewHelper.canAutoPreviewMIMEType(downloadMetadata.mimeType) { // Show alert to the file download self.presentSaveToDownloadsAlert(with: downloadMetadata) { callback(self.transfer(download, From 8127b00b7a74bc8991ef746439453f13d6057100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Thu, 24 Oct 2024 13:47:03 +0200 Subject: [PATCH 11/26] Fix updating OmniBar constraints on iOS18 (#3473) Task/Issue URL: https://app.asana.com/0/414709148257752/1208602903372806/f Tech Design URL: CC: **Description**: Addresses an issue when OmniBar was not sized properly on phones after rotating to landscape and back to portrait. Seems like root cause was some "optimization" in iOS 18 layout system which changed when `updateConstraints` is being called. Solution was to use `setNeedsUpdateConstraints()` while preparing cell for both "real" and "fake" OmniBar. Additionally call to `super.updateConstraints()` was moved to the end of the override as highlighted in [documentation](https://developer.apple.com/documentation/uikit/uiview/1622512-updateconstraints#discussion). **Steps to test this PR**: 1. Rotate to landscape and back to portrait with single and multiple tabs for both bar position settings (top and bottom). 2. Repeat for iPad **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Orientation Testing**: * [x] Portrait * [x] Landscape **Device Testing**: * [x] iPhone 8 * [x] iPhone 16 * [x] iPad **OS Testing**: * [x] iOS 15 * [x] iOS 17 * [x] iOS 18 --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- DuckDuckGo/SwipeTabsCoordinator.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/SwipeTabsCoordinator.swift b/DuckDuckGo/SwipeTabsCoordinator.swift index ea883d3dd7..8071d83ec9 100644 --- a/DuckDuckGo/SwipeTabsCoordinator.swift +++ b/DuckDuckGo/SwipeTabsCoordinator.swift @@ -314,8 +314,7 @@ extension SwipeTabsCoordinator: UICollectionViewDataSource { cell.omniBar = omniBar cell.omniBar?.translatesAutoresizingMaskIntoConstraints = false - cell.updateConstraints() - + cell.omniBar?.showSeparator() if self.appSettings.currentAddressBarPosition.isBottom { cell.omniBar?.moveSeparatorToTop() @@ -330,7 +329,9 @@ extension SwipeTabsCoordinator: UICollectionViewDataSource { } } - + + cell.setNeedsUpdateConstraints() + return cell } @@ -343,7 +344,7 @@ class OmniBarCell: UICollectionViewCell { subviews.forEach { $0.removeFromSuperview() } if let omniBar { addSubview(omniBar) - + NSLayoutConstraint.activate([ constrainView(omniBar, by: .leadingMargin), constrainView(omniBar, by: .trailingMargin), @@ -354,14 +355,14 @@ class OmniBarCell: UICollectionViewCell { } } } - + override func updateConstraints() { - super.updateConstraints() let left = superview?.safeAreaInsets.left ?? 0 let right = superview?.safeAreaInsets.right ?? 0 omniBar?.updateOmniBarPadding(left: left, right: right) + + super.updateConstraints() } - } extension TabsModel { From a0f4e64834921f07d647ca3145a5857242551143 Mon Sep 17 00:00:00 2001 From: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:38:27 +0200 Subject: [PATCH 12/26] Email parsing improved (#3436) Task/Issue URL: https://app.asana.com/0/1148564399326804/1207106552424205/f Tech Design URL: CC: **Description**: Email parsing improved on macOS. This PR doesn't affect iOS --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 96cbf96171..5ce5daacbe 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10966,7 +10966,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 200.2.0; + version = 200.2.1; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5f6afa28c4..c31d42aab9 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "a7de5bc2fe128444495e6e8345026c8974076ad7", - "version" : "200.2.0" + "revision" : "fdf6f75d570a5ef6058efa881e11f9467627fbf4", + "version" : "200.2.1" } }, { From 9634e7f846e527697dbd579afbf8300dd884c6f2 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Thu, 24 Oct 2024 19:50:06 +0200 Subject: [PATCH 13/26] Handle 'data' scheme downloads through WebKit's `decisionHandler(.download)` (#3478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/414709148257752/1208621179920770/f **Description:** When navigation should trigger download and is of data scheme handle it via decisionHandler(.download) **Steps to test this PR:** - test downloads on https://napuzba.com/data-url/ (ensure proper file name and extension) - test website from https://app.asana.com/0/1206777341262243/1208605967462508/f following repro steps in https://app.asana.com/0/0/1208605967462508/1208619416618129/f **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Copy Testing**: * [ ] Use of correct apostrophes in new copy, ie `’` rather than `'` **Orientation Testing**: * [ ] Portrait * [ ] Landscape **Device Testing**: * [ ] iPhone SE (1st Gen) * [ ] iPhone 8 * [ ] iPhone X * [ ] iPhone 14 Pro * [ ] iPad **OS Testing**: * [ ] iOS 15 * [ ] iOS 16 * [ ] iOS 17 **Theme Testing**: * [ ] Light theme * [ ] Dark theme --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- DuckDuckGo/TabViewController.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 8261602a70..bd0d3e1fb6 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1319,6 +1319,7 @@ extension TabViewController: WKNavigationDelegate { let mimeType = MIMEType(from: navigationResponse.response.mimeType, fileExtension: navigationResponse.response.url?.pathExtension) let urlSchemeType = navigationResponse.response.url.map { SchemeHandler.schemeType(for: $0) } ?? .unknown + let urlNavigationalScheme = navigationResponse.response.url?.scheme.map { URL.NavigationalScheme(rawValue: $0) } let httpResponse = navigationResponse.response as? HTTPURLResponse let isSuccessfulResponse = httpResponse?.isSuccessfulResponse ?? false @@ -1349,7 +1350,13 @@ extension TabViewController: WKNavigationDelegate { withAdditionalParameters: [PixelParameters.canAutoPreviewMIMEType: "1"]) } else if shouldTriggerDownloadAction(for: navigationResponse), let downloadMetadata = AppDependencyProvider.shared.downloadManager.downloadMetaData(for: navigationResponse.response) { - // 3. We know the response should trigger the file download prompt + // 3a. We know it is a download, but allow WebKit handle the "data" scheme natively + if urlNavigationalScheme == .data { + decisionHandler(.download) + return + } + + // 3b. We know the response should trigger the file download prompt self.presentSaveToDownloadsAlert(with: downloadMetadata) { self.startDownload(with: navigationResponse, decisionHandler: decisionHandler) } cancelHandler: { From 17e373e8d20355654a6768e4a100a9a3b9f739f7 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 24 Oct 2024 11:36:30 -0700 Subject: [PATCH 14/26] Release 7.141.1-0 (#3479) Please make sure all GH checks passed before merging. It can take around 20 minutes. Briefly review this PR to see if there are no issues or red flags and then merge it. --- Configuration/Version.xcconfig | 2 +- DuckDuckGo.xcodeproj/project.pbxproj | 56 +++++++++++++-------------- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 3b2b7dc2b3..8fc87c754d 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.141.0 +MARKETING_VERSION = 7.141.1 diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 34fa038c48..84196a4f04 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9161,7 +9161,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9198,7 +9198,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9288,7 +9288,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9315,7 +9315,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9464,7 +9464,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9489,7 +9489,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9558,7 +9558,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9592,7 +9592,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9625,7 +9625,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9655,7 +9655,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9965,7 +9965,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9996,7 +9996,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10024,7 +10024,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10057,7 +10057,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -10087,7 +10087,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10120,11 +10120,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10357,7 +10357,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10384,7 +10384,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10416,7 +10416,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10453,7 +10453,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10488,7 +10488,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10523,11 +10523,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10700,11 +10700,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10733,10 +10733,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 3d6a4bec95..7131e60936 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.141.0 + 7.141.1 Key version Title From 84bc3af87bc250cce0ac8e576fff9a89232a7b7a Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Fri, 25 Oct 2024 14:27:50 +0100 Subject: [PATCH 15/26] Merge Hotfix/7.141.1 into 7.142.0 release branch (#3481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1208593312489483/f Tech Design URL: CC: **Description**: Merges the hot fix into the latest release branch **Steps to test this PR**: 1. 2. **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Copy Testing**: * [ ] Use of correct apostrophes in new copy, ie `’` rather than `'` **Orientation Testing**: * [ ] Portrait * [ ] Landscape **Device Testing**: * [ ] iPhone SE (1st Gen) * [ ] iPhone 8 * [ ] iPhone X * [ ] iPhone 14 Pro * [ ] iPad **OS Testing**: * [ ] iOS 15 * [ ] iOS 16 * [ ] iOS 17 **Theme Testing**: * [ ] Light theme * [ ] Dark theme --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --------- Co-authored-by: Michal Smaga Co-authored-by: Sam Symons --- DuckDuckGo/TabViewController.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 772bc1e760..4ba66b9744 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1325,6 +1325,7 @@ extension TabViewController: WKNavigationDelegate { let mimeType = MIMEType(from: navigationResponse.response.mimeType, fileExtension: navigationResponse.response.url?.pathExtension) let urlSchemeType = navigationResponse.response.url.map { SchemeHandler.schemeType(for: $0) } ?? .unknown + let urlNavigationalScheme = navigationResponse.response.url?.scheme.map { URL.NavigationalScheme(rawValue: $0) } let httpResponse = navigationResponse.response as? HTTPURLResponse let isSuccessfulResponse = httpResponse?.isSuccessfulResponse ?? false @@ -1355,7 +1356,13 @@ extension TabViewController: WKNavigationDelegate { withAdditionalParameters: [PixelParameters.canAutoPreviewMIMEType: "1"]) } else if shouldTriggerDownloadAction(for: navigationResponse), let downloadMetadata = AppDependencyProvider.shared.downloadManager.downloadMetaData(for: navigationResponse.response) { - // 3. We know the response should trigger the file download prompt + // 3a. We know it is a download, but allow WebKit handle the "data" scheme natively + if urlNavigationalScheme == .data { + decisionHandler(.download) + return + } + + // 3b. We know the response should trigger the file download prompt self.presentSaveToDownloadsAlert(with: downloadMetadata) { self.startDownload(with: navigationResponse, decisionHandler: decisionHandler) } cancelHandler: { From 4e2a7ce65e025c8610d6dedff91bc9b70348fdda Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Fri, 25 Oct 2024 14:41:48 +0100 Subject: [PATCH 16/26] Release 7.142.0-1 (#3483) Please make sure all GH checks passed before merging. It can take around 20 minutes. Briefly review this PR to see if there are no issues or red flags and then merge it. --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cde6568b79..357751930e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9177,7 +9177,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9214,7 +9214,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9304,7 +9304,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9331,7 +9331,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9480,7 +9480,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9505,7 +9505,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9574,7 +9574,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9608,7 +9608,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9641,7 +9641,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9671,7 +9671,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9981,7 +9981,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10012,7 +10012,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10040,7 +10040,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10073,7 +10073,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -10103,7 +10103,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10136,11 +10136,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10373,7 +10373,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10400,7 +10400,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10432,7 +10432,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10469,7 +10469,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10504,7 +10504,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10539,11 +10539,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10716,11 +10716,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10749,10 +10749,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 2d09cb79307425adea5df6eed5ecd9f25cb823d9 Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Fri, 25 Oct 2024 17:24:03 +0200 Subject: [PATCH 17/26] Add pixel when the error page is shown (#3487) Task/Issue URL: https://app.asana.com/0/1176956903599313/1208602127384124/f **Description**: Fire a pixel when we show the error page in the WebView. --- Core/PixelEvent.swift | 5 +++++ DuckDuckGo/TabViewController.swift | 1 + 2 files changed, 6 insertions(+) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 4ba9c88b32..bd35b54b15 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -827,6 +827,9 @@ extension Pixel { case duckplayerExperimentDailySearch case duckplayerExperimentWeeklySearch case duckplayerExperimentYoutubePageView + + // MARK: WebView Error Page Shown + case webViewErrorPageShown } } @@ -1649,6 +1652,8 @@ extension Pixel.Event { case .duckplayerExperimentWeeklySearch: return "duckplayer_experiment_weekly_search_v2" case .duckplayerExperimentYoutubePageView: return "duckplayer_experiment_youtube_page_view_v2" + // MARK: - WebView Error Page shown + case .webViewErrorPageShown: return "m_errorpageshown" } } } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index a5207a62a3..0cb459b413 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -2063,6 +2063,7 @@ extension TabViewController: WKNavigationDelegate { if !(error.failedUrl?.isCustomURLScheme() ?? false) { url = error.failedUrl showError(message: error.localizedDescription) + Pixel.fire(pixel: .webViewErrorPageShown) } webpageDidFailToLoad() From cc0caf50572dc7749408418f2ecc83edf2ae5ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 25 Oct 2024 19:09:45 +0200 Subject: [PATCH 18/26] Make running the script more explicit (#3484) Task/Issue URL: https://app.asana.com/0/1203301625297703/1207921724970682/f **Description**: Make running the script more explicit, by providing additional argument if it is subsequent release. It will then fail on initial release if the release branch exists and fail on subsequent release if release branch does not. --- scripts/prepare_release.sh | 43 +++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index dca7095571..06aef4d0a8 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -3,6 +3,7 @@ set -eo pipefail mute=">/dev/null 2>&1" +is_subsequent_release=0 base_branch="main" build_number=0 @@ -78,24 +79,28 @@ read_command_line_arguments() { print_usage_and_exit "💥 Error: Missing argument" fi + shift 1 + + while [[ "$#" -gt 0 ]]; do + case "$1" in + -v) + mute= + ;; + -s|--subsequent) + is_subsequent_release=1 + ;; + *) + print_usage_and_exit "💥 Error: Unknown option '$1'" + ;; + esac + shift + done + if [[ $input =~ $version_regexp ]]; then process_release "$input" else process_hotfix "$input" fi - - shift 1 - - while getopts 'v' option; do - case "${option}" in - v) - mute= - ;; - *) - print_usage_and_exit "💥 Error: Unknown option '${option}'" - ;; - esac - done } process_release() { # expected input e.g. "1.72.0" @@ -104,9 +109,17 @@ process_release() { # expected input e.g. "1.72.0" echo "Processing version number: $version" - if release_branch_exists; then - is_subsequent_release=1 + if [[ "$is_subsequent_release" -eq 1 ]]; then + # check if the release branch exists (it must exist for a subsequent release) + if ! release_branch_exists; then + die "💥 Error: Release branch does not exist for a subsequent release!" + fi base_branch="$release_branch" + else + # check if the release branch does NOT exist (it must NOT exist for an initial release) + if release_branch_exists; then + die "💥 Error: Release branch already exists for an initial release!" + fi fi } From 44db773d0d296c994fe5475a2038080e5ccb60a2 Mon Sep 17 00:00:00 2001 From: Daisuke TONOSAKI Date: Mon, 28 Oct 2024 10:00:09 +0900 Subject: [PATCH 19/26] Fix image set warning (#3459) Task: https://app.asana.com/0/414235014887631/1208634590356693/f Description: This PR fixed the following warning: HomeMessage.xcassets:./WidgetEducation/WidgetEducationHomeScreen.imageset/(null)[2d][iphone.png] The image set "WidgetEducationHomeScreen" has an unassigned child. The iphone.png was no referenced, but the file remained and a warning was happened. --- .../iphone.png | Bin 175695 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 DuckDuckGo/HomeMessage.xcassets/WidgetEducation/WidgetEducationHomeScreen.imageset/iphone.png diff --git a/DuckDuckGo/HomeMessage.xcassets/WidgetEducation/WidgetEducationHomeScreen.imageset/iphone.png b/DuckDuckGo/HomeMessage.xcassets/WidgetEducation/WidgetEducationHomeScreen.imageset/iphone.png deleted file mode 100644 index 19ae85b0b96f0a87865d228533ddd51e018da109..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175695 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 z 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%0D@k9~cT57lsX! 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 LNkvXXu0mjfLP(zi From 958d556fd1aa4e3e7c710e29eea69d3821bb8a32 Mon Sep 17 00:00:00 2001 From: Daisuke TONOSAKI Date: Mon, 28 Oct 2024 10:00:54 +0900 Subject: [PATCH 20/26] Remove Recovered References (#3457) Task/Issue URL: https://app.asana.com/0/414235014887631/1208634590356697/f Tech Design URL: CC: Description: Hi, Removed unnecessary references from the project. Thanks. --- DuckDuckGo.xcodeproj/project.pbxproj | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d1edd0487d..d73a1c7242 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2729,7 +2729,6 @@ CB18F2712AF6D4E400A0F8FE /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; CB1AEFB02799AA940031AE3D /* SwiftUICollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICollectionViewCell.swift; sourceTree = ""; }; CB1FAE472AF6D59B003F452F /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = ""; }; - CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptView.swift; sourceTree = ""; }; CB24F70E29A3EB15006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfigurationURLProvider.swift; path = ../Core/AppConfigurationURLProvider.swift; sourceTree = ""; }; CB258D0C29A4CD0500DEBA24 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; CB258D0F29A4D0FD00DEBA24 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; @@ -2743,7 +2742,6 @@ CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitorTests.swift; sourceTree = ""; }; CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScript.swift; sourceTree = ""; }; CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptViewModel.swift; sourceTree = ""; }; CB6ABD002AF6D52B004A8224 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; CB6CE65B2AF6D4EE00119848 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; CB7407BC2AF6D56D0090A41C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -3195,15 +3193,6 @@ path = FingerprintingUITests; sourceTree = ""; }; - 0283A1F82C6E3C3100508FBD /* Recovered References */ = { - isa = PBXGroup; - children = ( - CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */, - CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; 0283A1F92C6E3D4200508FBD /* BrokenSitePrompt */ = { isa = PBXGroup; children = ( @@ -4182,7 +4171,6 @@ 83ED3B8D1FA8E63700B47556 /* README.md */, 83ED3B8C1FA8E61D00B47556 /* ManualTestsScript.md */, 85A313962028E78A00327D00 /* release_notes.txt */, - 0283A1F82C6E3C3100508FBD /* Recovered References */, ); sourceTree = ""; }; From 4384133d8114a8736c73d4b3798ad5de442bdfca Mon Sep 17 00:00:00 2001 From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:14:40 +0100 Subject: [PATCH 21/26] point to BSK branch (#3492) Task/Issue URL: https://app.asana.com/0/0/1208077416568652/f **Description**: Updates BSK dependency --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d73a1c7242..ebe3598311 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10954,7 +10954,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 200.2.1; + version = 200.3.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { From c41048cdade20042f88d27a5ea9432a8bb18a9db Mon Sep 17 00:00:00 2001 From: kshann Date: Tue, 29 Oct 2024 00:43:52 +1100 Subject: [PATCH 22/26] TestFlight only release and promotion (#3474) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1208234424227124/f Tech Design URL: CC: @ayoy **Description**: - Changes nightly builds to release to TestFlight only - Adds promote TestFlight to appstore (mirroring MacOS) which gets the latest test flight build and uploads the meta data for that version. Currently only tested with Alpha. **Steps to test this PR**: - [Initial test flow](https://github.com/duckduckgo/iOS/actions/runs/11491500989/job/31983979375) (without upload meta data). - This iteration of the flow only checked the version and build number to upload without doing the upload - For Alpha it found `Latest upload for version 7.142.0 on ios platform is build: 1` - For Stable it found `Latest upload for version 7.142.0 on ios platform is build: 0` - The nightly job ran (conveniently) to update the alpha build in TestFlight, and a rerun of the [promote flow](https://github.com/duckduckgo/iOS/actions/runs/11491747964/job/31984649252) showed it found updated build number of 2 which we expect - [Final run](https://github.com/duckduckgo/iOS/actions/runs/11492279658) failed due to Alpha not quite setup to accept submissions due to metadata conflicts with stable, but it did successfully update the version and some of the metadata, so confident this will work for stable builds. Before ![image](https://github.com/user-attachments/assets/a4e47883-f1bf-4256-9425-a3870a0d9be9) After promotion run ![image](https://github.com/user-attachments/assets/1d23ea75-e26e-41de-a976-6052068f6b15) **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Copy Testing**: * [ ] Use of correct apostrophes in new copy, ie `’` rather than `'` **Orientation Testing**: * [ ] Portrait * [ ] Landscape **Device Testing**: * [ ] iPhone SE (1st Gen) * [ ] iPhone 8 * [ ] iPhone X * [ ] iPhone 14 Pro * [ ] iPad **OS Testing**: * [ ] iOS 15 * [ ] iOS 16 * [ ] iOS 17 **Theme Testing**: * [ ] Light theme * [ ] Dark theme --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- .github/workflows/promote_testflight.yml | 33 ++++++++++++++++++++++++ .github/workflows/release.yml | 5 ++-- fastlane/Fastfile | 29 ++++++++++++++++++++- 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/promote_testflight.yml diff --git a/.github/workflows/promote_testflight.yml b/.github/workflows/promote_testflight.yml new file mode 100644 index 0000000000..641c945ad7 --- /dev/null +++ b/.github/workflows/promote_testflight.yml @@ -0,0 +1,33 @@ +name: Promote TestFlight to App Store + +on: + workflow_dispatch: {} + +jobs: + promote-testflight-to-appstore: + runs-on: macos-14 + + steps: + - name: Check out the code + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + sparse-checkout: | + .github + Gemfile + Gemfile.lock + fastlane + scripts + + - name: Set up fastlane + run: bundle install + + - name: Promote TestFlight to App Store + env: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + run: | + git config --global user.name "Dax the Duck" + git config --global user.email "dax@duckduckgo.com" + bundle exec fastlane promote_latest_testflight_to_appstore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 525a57ddcf..50a4fefb6f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: destination: description: "Upload destination (App Store or TestFlight)" required: true - default: appstore + default: testflight type: choice options: - appstore @@ -36,10 +36,9 @@ jobs: id: destination run: | INPUT_DESTINATION=${{ github.event.inputs.destination }} - echo "destination=${INPUT_DESTINATION:-"appstore"}" >> $GITHUB_OUTPUT + echo "destination=${INPUT_DESTINATION:-"testflight"}" >> $GITHUB_OUTPUT - name: Assert release branch - if: steps.destination.outputs.destination == 'appstore' run: | case "${{ github.ref }}" in *release/*) ;; diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f18657fd83..0cadad0296 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -168,6 +168,32 @@ lane :alpha_adhoc do |options| end end + + +desc 'Promotes the latest TestFlight build to App Store without submitting for review' +lane :promote_latest_testflight_to_appstore do |options| + + app_identifier = options[:alpha] ? "com.duckduckgo.mobile.ios.alpha" : "com.duckduckgo.mobile.ios" + + latest_testflight_build_number( + api_key: get_api_key, + username: get_username(options), + platform: 'ios', + app_identifier: app_identifier + ) + + latest_build_number = lane_context[SharedValues::LATEST_TESTFLIGHT_BUILD_NUMBER] + latest_build_version = lane_context[SharedValues::LATEST_TESTFLIGHT_VERSION] + + UI.message("The latest build number #{latest_build_number} of the latest version: #{latest_build_version} for app identifier: #{app_identifier}") + + upload_metadata({ + build_number: latest_build_number.to_s, + app_version: latest_build_version.to_s, + app_identifier: app_identifier + }) +end + desc 'Makes App Store release build and uploads it to App Store Connect' lane :release_appstore do |options| build_release(options) @@ -194,7 +220,8 @@ desc 'Updates App Store metadata' lane :upload_metadata do |options| deliver(common_deliver_arguments.merge(options).merge({ skip_binary_upload: true, - skip_metadata: false + skip_metadata: false, + version_check_wait_retry_limit: 0 })) end From dc67e004b602654e429c1fd8447f6c3e7ca18ecb Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 28 Oct 2024 15:02:49 +0100 Subject: [PATCH 23/26] Show VPN onboarding tips (#3429) Task/Issue URL: https://app.asana.com/0/1206580121312550/1208341440402810/f macOS PR: https://github.com/duckduckgo/macos-browser/pull/3410 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/1024 ## Description Shows VPN onboarding tips. --- Core/UserDefaultsPropertyWrapper.swift | 4 +- DuckDuckGo.xcodeproj/project.pbxproj | 52 ++++++ DuckDuckGo/AppDelegate.swift | 6 + DuckDuckGo/Debug.storyboard | 29 +-- DuckDuckGo/NetworkProtectionStatusView.swift | 168 ++++++++++++++++-- .../NetworkProtectionStatusViewModel.swift | 60 ++++++- DuckDuckGo/RootDebugViewController.swift | 23 ++- DuckDuckGo/TipKit/Logger+TipKit.swift | 28 +++ .../TipKit/TipKitAppEventHandling.swift | 50 ++++++ ...itController+ConvenienceInitializers.swift | 30 ++++ DuckDuckGo/TipKit/TipKitController.swift | 93 ++++++++++ .../TipKitDebugOptionsUIActionHandling.swift | 49 +++++ DuckDuckGo/VPN.xcassets/Contents.json | 6 + .../Contents.json | 12 ++ .../Widget-Add-32 2.pdf | Bin 0 -> 1819 bytes .../Contents.json | 12 ++ .../Location-32.pdf | Bin 0 -> 1570 bytes .../Contents.json | 12 ++ .../Moon-Snooze-32.pdf | Bin 0 -> 1938 bytes DuckDuckGo/VPNAddWidgetTip.swift | 78 ++++++++ DuckDuckGo/VPNGeoswitchingTip.swift | 58 ++++++ DuckDuckGo/VPNSnoozeTip.swift | 71 ++++++++ DuckDuckGo/ViewExtension.swift | 6 + 23 files changed, 812 insertions(+), 35 deletions(-) create mode 100644 DuckDuckGo/TipKit/Logger+TipKit.swift create mode 100644 DuckDuckGo/TipKit/TipKitAppEventHandling.swift create mode 100644 DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift create mode 100644 DuckDuckGo/TipKit/TipKitController.swift create mode 100644 DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift create mode 100644 DuckDuckGo/VPN.xcassets/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32 2.pdf create mode 100644 DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Location-32.pdf create mode 100644 DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-Snooze-32.pdf create mode 100644 DuckDuckGo/VPNAddWidgetTip.swift create mode 100644 DuckDuckGo/VPNGeoswitchingTip.swift create mode 100644 DuckDuckGo/VPNSnoozeTip.swift diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 2e119e09df..9fbffdcd53 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -180,7 +180,9 @@ public struct UserDefaultsWrapper { case duckPlayerPixelExperimentLastDayPixelFired = "com.duckduckgo.ios.duckplayer.pixel.experiment.last.day.pixel.fired.v2" case duckPlayerPixelExperimentLastVideoIDRendered = "com.duckduckgo.ios.duckplayer.pixel.experiment.last.videoID.rendered.v2" case duckPlayerPixelExperimentOverride = "com.duckduckgo.ios.duckplayer.pixel.experiment.override.v2" - + + // TipKit + case resetTipKitOnNextLaunch = "com.duckduckgo.ios.tipKit.resetOnNextLaunch" } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ebe3598311..73dc71bf7a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -365,8 +365,17 @@ 6FEC0B852C999352006B4F6E /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */; }; 6FEC0B882C999961006B4F6E /* FavoritesListInteractingAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; + 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */; }; + 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */; }; + 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */; }; + 7B8E0EC62CC81B4900B2B722 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; + 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */; }; + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; + 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; + 7BFD5FD72C9DB9D7000FF959 /* VPNGeoswitchingTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */; }; + 7BFD5FD92C9DBC24000FF959 /* VPNSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E872193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift */; }; @@ -1651,7 +1660,16 @@ 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = ""; }; 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapter.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; + 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; + 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; + 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; + 7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitController.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; + 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = VPN.xcassets; sourceTree = ""; }; + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; + 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; + 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNGeoswitchingTip.swift; sourceTree = ""; }; + 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSnoozeTip.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerBrowsingMenuExtension.swift; sourceTree = ""; }; @@ -3728,6 +3746,7 @@ children = ( 4B37E04F2B928CA6009E81CA /* vpn-light-mode.json */, 4B6ED9442B992FE4007F5CAA /* vpn-dark-mode.json */, + 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */, ); name = Resources; sourceTree = ""; @@ -4011,6 +4030,28 @@ name = AdAttribution; sourceTree = ""; }; + 7BF78E002CA2CC100026A1FC /* TipKit */ = { + isa = PBXGroup; + children = ( + 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */, + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */, + 7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */, + 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */, + 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */, + ); + path = TipKit; + sourceTree = ""; + }; + 7BFD5FD32C9DA235000FF959 /* TipKit */ = { + isa = PBXGroup; + children = ( + 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */, + 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */, + 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */, + ); + name = TipKit; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -4238,6 +4279,7 @@ F13B4BF41F18C74500814661 /* Tabs */, F1386BA21E6846320062FC3C /* TabSwitcher */, 98F3A1D6217B36EE0011A0D4 /* Themes */, + 7BF78E002CA2CC100026A1FC /* TipKit */, F11CEF581EBB66C80088E4D7 /* Tutorials */, CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */, F1D796ED1E7AE4090019D451 /* UserInterface */, @@ -5551,6 +5593,7 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7BFD5FD32C9DA235000FF959 /* TipKit */, 4BD96E072C4DCCD1003BC32C /* LiveActivity */, 4B37E04E2B928C91009E81CA /* Resources */, EE01EB412AFC1DE10096AAC9 /* PreferredLocation */, @@ -6957,6 +7000,7 @@ 85F98F98296F4CB100742F4A /* SyncAssets.xcassets in Resources */, 31BC5F412C2B0B540004DF37 /* DuckPlayer.xcassets in Resources */, AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, + 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, D65CEA702B6AC6C9008A759B /* Subscription.xcassets in Resources */, @@ -7340,9 +7384,12 @@ EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, + 7B8E0EC62CC81B4900B2B722 /* TipKitController.swift in Sources */, + 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */, 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, + 7BFD5FD72C9DB9D7000FF959 /* VPNGeoswitchingTip.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, @@ -7415,6 +7462,7 @@ 9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */, 310D091B2799F54900DC0060 /* DownloadManager.swift in Sources */, 98D98A7425ED88D100D8E3DF /* BrowsingMenuEntryViewCell.swift in Sources */, + 7BFD5FD92C9DBC24000FF959 /* VPNSnoozeTip.swift in Sources */, 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */, 4B2C79612C5B27AC00A240CC /* VPNSnoozeActivityAttributes.swift in Sources */, CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, @@ -7516,6 +7564,7 @@ 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */, + 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, @@ -7528,6 +7577,7 @@ D63FF8982C1B6A45006DE24D /* DuckPlayer.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, C1935A102C88D131001AD72D /* AutofillSurveyManager.swift in Sources */, + 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, 1DDF40202BA049FA006850D9 /* SettingsRootView.swift in Sources */, @@ -7671,6 +7721,7 @@ 31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, + 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, @@ -7818,6 +7869,7 @@ F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */, D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */, 1DEAADF62BA4809400E25A97 /* CookiePopUpProtectionView.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 0442bb02d9..cc09ced6bb 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -90,6 +90,10 @@ import os.log private(set) var subscriptionFeatureAvailability: SubscriptionFeatureAvailability! var privacyProDataReporter: PrivacyProDataReporting! + // MARK: - Feature specific app event handlers + + private let tipKitAppEventsHandler = TipKitAppEventHandler() + // MARK: lifecycle @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) @@ -394,6 +398,8 @@ import os.log didCrashDuringCrashHandlersSetUp = false } + tipKitAppEventsHandler.appDidFinishLaunching() + return true } diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index c66b2130dc..6f8d490805 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -383,9 +383,18 @@ - + + + + + + + + + + @@ -393,7 +402,7 @@ - + @@ -1049,34 +1058,34 @@ - + - + - + - + diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index f8508a2482..21e79aa654 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -19,11 +19,61 @@ import SwiftUI import NetworkProtection +import TipKit struct NetworkProtectionStatusView: View { + + static let defaultImageSize = CGSize(width: 32, height: 32) + @Environment(\.colorScheme) var colorScheme - @StateObject public var statusModel: NetworkProtectionStatusViewModel + @ObservedObject + public var statusModel: NetworkProtectionStatusViewModel + + // MARK: - Tips + + let geoswitchingTip: VPNGeoswitchingTip = { + let tip = VPNGeoswitchingTip() + + if #available(iOS 17.0, *) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNSnoozeTip.geolocationTipDismissedEvent.donate() + await VPNAddWidgetTip.geolocationTipDismissedEvent.donate() + } + } + } + } + } + + return tip + }() + + let snoozeTip: VPNSnoozeTip = { + let tip = VPNSnoozeTip() + + if #available(iOS 17.0, *) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAddWidgetTip.snoozeTipDismissedEvent.donate() + } + } + } + } + } + + return tip + }() + + let widgetTip: VPNAddWidgetTip = { + VPNAddWidgetTip() + }() + + // MARK: - View var body: some View { List { @@ -35,6 +85,7 @@ struct NetworkProtectionStatusView: View { } toggle() + locationDetails() if statusModel.isNetPEnabled && statusModel.hasServerInfo && !statusModel.isSnoozing { @@ -51,6 +102,9 @@ struct NetworkProtectionStatusView: View { .animation(.easeOut, value: statusModel.shouldShowError) }) .applyInsetGroupedListStyle() + .sheet(isPresented: $statusModel.showAddWidgetEducationView) { + widgetEducationSheet() + } } @ViewBuilder @@ -86,11 +140,27 @@ struct NetworkProtectionStatusView: View { .padding([.top, .bottom], 2) snooze() + } header: { header() } .increaseHeaderProminence() .listRowBackground(Color(designSystemColor: .surface)) + + Section { + if #available(iOS 17.0, *) { + widgetTipView() + .tipImageSize(Self.defaultImageSize) + .padding(.horizontal, 3) + } + + if #available(iOS 17.0, *) { + snoozeTipView() + .tipImageSize(Self.defaultImageSize) + .padding(.horizontal, 3) + } + } + .listRowBackground(Color(designSystemColor: .surface)) } @ViewBuilder @@ -151,8 +221,8 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func locationDetails() -> some View { - if !statusModel.isSnoozing, let location = statusModel.location { - Section { + Section { + if !statusModel.isSnoozing, let location = statusModel.location { var locationAttributedString: AttributedString { var attributedString = AttributedString( statusModel.preferredLocation.isNearest ? "\(location) \(UserText.netPVPNLocationNearest)" : location @@ -164,16 +234,10 @@ struct NetworkProtectionStatusView: View { return attributedString } - NavigationLink(destination: NetworkProtectionVPNLocationView()) { + NavigationLink(destination: locationView()) { NetworkProtectionLocationItemView(title: locationAttributedString, imageName: nil) } - } header: { - Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) - .foregroundColor(.init(designSystemColor: .textSecondary)) - } - .listRowBackground(Color(designSystemColor: .surface)) - } else { - Section { + } else { let imageName = statusModel.preferredLocation.isNearest ? "VPNLocation" : nil var nearestLocationAttributedString: AttributedString { var attributedString = AttributedString(statusModel.preferredLocation.title) @@ -181,15 +245,32 @@ struct NetworkProtectionStatusView: View { return attributedString } - NavigationLink(destination: NetworkProtectionVPNLocationView()) { + NavigationLink(destination: locationView()) { NetworkProtectionLocationItemView(title: nearestLocationAttributedString, imageName: imageName) } - } header: { - Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) - .foregroundColor(.init(designSystemColor: .textSecondary)) } - .listRowBackground(Color(designSystemColor: .surface)) + } header: { + Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) + .foregroundColor(.init(designSystemColor: .textSecondary)) + } + .listRowBackground(Color(designSystemColor: .surface)) + + Section { + if #available(iOS 17.0, *) { + geoswitchingTipView() + .tipImageSize(Self.defaultImageSize) + .padding(.horizontal, 3) + } } + .listRowBackground(Color(designSystemColor: .surface)) + } + + @ViewBuilder + private func locationView() -> some View { + NetworkProtectionVPNLocationView() + .onAppear { + statusModel.handleUserOpenedVPNLocations() + } } @ViewBuilder @@ -267,6 +348,61 @@ struct NetworkProtectionStatusView: View { isAnimating: $statusModel.isNetPEnabled ) } + + // MARK: - Tips + + @available(iOS 17.0, *) + @ViewBuilder + private func geoswitchingTipView() -> some View { + if statusModel.canShowTips { + + TipView(geoswitchingTip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + @available(iOS 17.0, *) + @ViewBuilder + private func snoozeTipView() -> some View { + if statusModel.canShowTips, + statusModel.hasServerInfo { + + TipView(snoozeTip, action: statusModel.snoozeActionHandler(action:)) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + @available(iOS 17.0, *) + @ViewBuilder + private func widgetTipView() -> some View { + if statusModel.canShowTips, + !statusModel.isNetPEnabled && !statusModel.isSnoozing { + + TipView(widgetTip, action: statusModel.widgetActionHandler(action:)) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + // MARK: - Sheets + + private func widgetEducationSheet() -> some View { + NavigationView { + WidgetEducationView() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.navigationTitleDone) { + statusModel.showAddWidgetEducationView = false + } + } + } + } + } } private struct NetworkProtectionErrorView: View { diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 7e7e5dc51f..becccb79aa 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -24,6 +24,7 @@ import WidgetKit import BrowserServicesKit import Core import Subscription +import TipKit struct NetworkProtectionLocationStatusModel { enum LocationIcon { @@ -94,12 +95,24 @@ final class NetworkProtectionStatusViewModel: ObservableObject { return formatter }() + private let featureFlagger = AppDependencyProvider.shared.featureFlagger private let tunnelController: (TunnelController & TunnelSessionProvider) private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver private let errorObserver: ConnectionErrorObserver private var cancellables: Set = [] + // MARK: - Tips + + var canShowTips: Bool { + featureFlagger.isFeatureOn(.networkProtectionUserTips) + } + + /// Whether the "Add Widget" education sheet should be presented to the user. + /// + @Published + var showAddWidgetEducationView: Bool = false + // MARK: Error struct ErrorItem { @@ -121,7 +134,19 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: Toggle Item - @Published public var isNetPEnabled = false + @Published public var isNetPEnabled = false { + didSet { + if #available(iOS 17.0, *) { + if isNetPEnabled { + VPNGeoswitchingTip.donateVPNConnectedEvent() + } + + VPNSnoozeTip.vpnEnabled = isNetPEnabled + VPNAddWidgetTip.vpnEnabled = isNetPEnabled + } + } + } + @Published public var isSnoozing = false { didSet { snoozeRequestPending = false @@ -452,6 +477,10 @@ final class NetworkProtectionStatusViewModel: ObservableObject { return } + if #available(iOS 17.0, *) { + VPNSnoozeTip().invalidate(reason: .actionPerformed) + } + let defaultDuration: TimeInterval = .minutes(20) snoozeRequestPending = true try? await activeSession.sendProviderMessage(.startSnooze(defaultDuration)) @@ -546,6 +575,35 @@ final class NetworkProtectionStatusViewModel: ObservableObject { self.downloadTotal = nil } + // MARK: - UI Events handling + + @available(iOS 17.0, *) + func snoozeActionHandler(action: Tips.Action) { + if action.id == VPNSnoozeTip.ActionIdentifiers.learnMore.rawValue { + let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + + @available(iOS 17.0, *) + @MainActor + func widgetActionHandler(action: Tips.Action) { + if action.id == VPNAddWidgetTip.ActionIdentifiers.addWidget.rawValue { + showAddWidgetEducationView = true + + VPNAddWidgetTip().invalidate(reason: .actionPerformed) + } + } + + /// The user opened the VPN locations view + /// + func handleUserOpenedVPNLocations() { + if #available(iOS 17.0, *) { + Task { @MainActor in + VPNGeoswitchingTip().invalidate(reason: .actionPerformed) + } + } + } } private extension ConnectionStatus { diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 23500ebf8c..08dea4d8ae 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -50,6 +50,7 @@ class RootDebugViewController: UITableViewController { case resetDuckPlayerExperiment = 678 case overrideDuckPlayerExperiment = 679 case overrideDuckPlayerExperimentControl = 680 + case resetTipKit = 681 } @IBOutlet weak var shareButton: UIBarButtonItem! @@ -64,6 +65,7 @@ class RootDebugViewController: UITableViewController { private var sync: DDGSyncing? private var internalUserDecider: DefaultInternalUserDecider? var tabManager: TabManager? + private var tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling? @UserDefaultsWrapper(key: .lastConfigurationRefreshDate, defaultValue: .distantPast) private var lastConfigurationRefreshDate: Date @@ -72,24 +74,29 @@ class RootDebugViewController: UITableViewController { sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, - tabManager: TabManager) { + tabManager: TabManager, + tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling = TipKitDebugOptionsUIActionHandler()) { self.sync = sync self.bookmarksDatabase = bookmarksDatabase self.internalUserDecider = internalUserDecider as? DefaultInternalUserDecider self.tabManager = tabManager + self.tipKitUIActionHandler = tipKitUIActionHandler + + super.init(coder: coder) + } + + required init?(coder: NSCoder) { super.init(coder: coder) } - - func configure(sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, tabManager: TabManager) { + + func configure(sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, tabManager: TabManager, tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling = TipKitDebugOptionsUIActionHandler()) { + self.sync = sync self.bookmarksDatabase = bookmarksDatabase self.internalUserDecider = internalUserDecider as? DefaultInternalUserDecider self.tabManager = tabManager - } - - required init?(coder: NSCoder) { - super.init(coder: coder) + self.tipKitUIActionHandler = tipKitUIActionHandler } @IBSegueAction func onCreateImageCacheDebugScreen(_ coder: NSCoder) -> ImageCacheDebugViewController? { @@ -187,6 +194,8 @@ class RootDebugViewController: UITableViewController { case .resetDuckPlayerExperiment: DuckPlayerLaunchExperiment().cleanup() ActionMessageView.present(message: "Experiment Settings deleted. You'll be assigned a random cohort") + case .resetTipKit: + tipKitUIActionHandler?.resetTipKitTapped() case .overrideDuckPlayerExperiment: DuckPlayerLaunchExperiment().override() ActionMessageView.present(message: "Overriding experiment. You are now in the 'experiment' group. Restart the app to complete") diff --git a/DuckDuckGo/TipKit/Logger+TipKit.swift b/DuckDuckGo/TipKit/Logger+TipKit.swift new file mode 100644 index 0000000000..1d791692b4 --- /dev/null +++ b/DuckDuckGo/TipKit/Logger+TipKit.swift @@ -0,0 +1,28 @@ +// +// Logger+TipKit.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 +import os.log + +extension Logger { + + static var tipKit: Logger = { + Logger(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "TipKit") + }() +} diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift new file mode 100644 index 0000000000..572427579a --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -0,0 +1,50 @@ +// +// TipKitAppEventHandling.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 Core +import Foundation +import os.log + +protocol TipKitAppEventHandling { + func appDidFinishLaunching() +} + +struct TipKitAppEventHandler: TipKitAppEventHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController = .make(), + logger: Logger = .tipKit) { + + self.controller = controller + self.logger = logger + } + + func appDidFinishLaunching() { + if #available(iOS 17.0, *) { + controller.configureTipKit([ + .displayFrequency(.immediate), + .datastoreLocation(.applicationDefault) + ]) + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift new file mode 100644 index 0000000000..8b39a4ea95 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift @@ -0,0 +1,30 @@ +// +// TipKitController+ConvenienceInitializers.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 +import os + +extension TipKitController { + + static func make(logger: Logger = .tipKit, + userDefaults: UserDefaults = .standard) -> Self { + + self.init(logger: logger, userDefaults: userDefaults) + } +} diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift new file mode 100644 index 0000000000..c91a0b534f --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController.swift @@ -0,0 +1,93 @@ +// +// TipKitController.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 +import os.log +import TipKit + +protocol TipKitControlling { + @available(iOS 17.0, *) + func configureTipKit() + + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() +} + +final class TipKitController { + + private let logger: Logger + private let userDefaults: UserDefaults + + private var resetTipKitOnNextLaunch: Bool { + get { + userDefaults.bool(forKey: "resetTipKitOnNextLaunch") + } + + set { + userDefaults.set(newValue, forKey: "resetTipKitOnNextLaunch") + } + } + + init(logger: Logger, + userDefaults: UserDefaults) { + + self.logger = logger + self.userDefaults = userDefaults + } + + @available(iOS 17.0, macOS 14.0, *) + func configureTipKit(_ configuration: [Tips.ConfigurationOption] = []) { + do { + if resetTipKitOnNextLaunch { + resetTipKit() + resetTipKitOnNextLaunch = false + } + + try Tips.configure(configuration) + + logger.debug("TipKit initialized") + } catch { + logger.error("Failed to initialize TipKit: \(error)") + } + } + + @available(iOS 17.0, macOS 14.0, *) + private func resetTipKit() { + do { + try Tips.resetDatastore() + + logger.debug("TipKit reset") + } catch { + logger.debug("Failed to reset TipKit: \(error)") + } + } + + /// Resets TipKit + /// + /// One thing that's not documented as of 2024-10-09 is that resetting TipKit must happen before it's configured. + /// When trying to reset it after it's configured we get `TipKit.TipKitError(value: TipKit.TipKitError.Value.tipsDatastoreAlreadyConfigured)`. + /// In order to make things work for us we set a user defaults value that ensures TipKit will be reset on next + /// app launch instead of directly trying to reset it here. + /// + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() { + resetTipKitOnNextLaunch = true + logger.debug("TipKit will reset on next app launch") + } +} diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift new file mode 100644 index 0000000000..1a12054a98 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -0,0 +1,49 @@ +// +// TipKitDebugOptionsUIActionHandling.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 +import os.log + +protocol TipKitDebugOptionsUIActionHandling { + /// Resets TipKit + func resetTipKitTapped() +} + +struct TipKitDebugOptionsUIActionHandler: TipKitDebugOptionsUIActionHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController = .make(), + logger: Logger = .tipKit) { + + self.controller = controller + self.logger = logger + } + + func resetTipKitTapped() { + if #available(iOS 17.0, *) { + controller.resetTipKitOnNextAppLaunch() + + ActionMessageView.present(message: "TipKit will reset on next app launch.") + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/VPN.xcassets/Contents.json b/DuckDuckGo/VPN.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..1360a4b9fb --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Widget-Add-32 2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32 2.pdf b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32 2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..be3e56356e0de1523b57d1a9c96c23a8f7d1dbf0 GIT binary patch literal 1819 zcmZXVe>9VO9LLLzG}^`G_;Kd;q^s7CH8We=v2+<~D4K3)aeFXpnLTVHzlsyWlFF?) zO{tivP$n#u6kR#x`ZYz3>5^1jWra*~H@~XW=a1)npZEEGe!M@=Ij_gs%h?r$*|-S? z!vHn_6nFpz03;IN-~gkugEg-(+i!KWE0e{ea{#m}3*^zA=|NC1U7ev0vg!asq^`i_ap+(eEP8Qw_WjI$I|G;T34ZaZ=fB7)ENF2$?)|b>;G`lW*|m3)IL2i^ zX}NgmtYyXPc6$l*$FnmJriLfS6Fx^(PrpfpI^G=)P&~UnC{s=v)!|v)>;aeD;ezn+ z`-W8x8)%m+q7w85a%JI_wUM+f{wuQ0$m*H=Agx9ZbBW538-+mJ}TI$2epUub&bj_Yv` z{EneLR5(LEjHHn2^!P z5M6He4P_9$FQ9}?_G5K2i?*kM?9H(;v3~CE0`56U=rgF}MG_tvR>a_WWolzYtc|L~ z^_6a)B%gj2*2*ZW^G2+KiaG_&w_Zs;-}+n@zxVKGX}3z)QlezayQriHWtEB5advl| zqA$cIzf@7Ol>B;kwtkugRW#9Fvb8f#TK2A)jjD#@ZyyYe=)M|TLQ84)3t9(%b-QBh zW3Qu~##lxs$lw&WGfpe^zx1%=g|a8>cklW6RDy$1k%Dn7>O5FnlYc#`#WKBcj-#n`j(PR zKCc}m&EHM&nHuEZF7C>zDW^3U4<3>^=}GPh#K;L>7nG=6>I~g#lZL+^va2P$Hc~a) zi{MjA_()KvDz8?BxYntv9d03#7;@Pq?C_jWtsuigo-w3%8aYjoRgn%ADBI(m9hYGV2t()B{} z>+Ac$-*-r+UX-TPGrI&07@O-@W{U7&i zqiGo#W*rTUqcpUlmYc?W@Ik&cOCgyec6YLCI4W3?z@mwrViHw6cH{S#&P2=V=@W4` zxBa;?$_P7_IP#A2=!w~8Qe(TNW7)AN)S^mG9s*y!$c&Z7af^5RO&5^_v{*Z@J5i6%@XPzXBUhLGfkUj!z7R}ysIp|tKb zo|SgRQtp*xYgLVM-grDLX!LDtsJOgWvXrG`%GKNCV(Dl|cK4`qAm!ve_bMn3GkvoL zvE){=@0J((%cKFaHVwz-Kr~M@4WedAhr!@%7C>jWAgck0c4V_59v7hb&*5!XOgJ4t zdx4C3-fgD`GeIXP8lV9-8*OId>j7&!FMU1DSEACnP$Vaa&IL5c)v!{7Pcp>j(b?*b z8s5%}ULZ%U0k}Eo8!Bt01zMxsnZfE&0gVGHFyDs^MXGZ#Kg`darStqY@ZVL^800QA z)C@LjoDA|n7R2~IHJU>Yf!P3?V6*l$PXLd@VR3+3o(qf(FmnRf3ldg6rzRF8j4ht< zgG5ws=lffB_#YB+*sqB^4#;HDIWW!Fqd2SAf#gDA^tAwz8N7CODQGGL@c@nCnFXW0 bnXz>B^fSLIki(m?gTWCu!K|%aJYD|>(gN^D literal 0 HcmV?d00001 diff --git a/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..cb612d2e7d --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Location-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Location-32.pdf b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Location-32.pdf new file mode 100644 index 0000000000000000000000000000000000000000..25b89dda779c25b09f31d0062a0dd1de4461d887 GIT binary patch literal 1570 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~f^q7U)9X}Cd0d}=6*YBJlC}2t zXY4fm!0CRMsWXpr>yhu9=dAK7YVf@NK<@tB9~0l^*8h0<__{j1@#i?2TmI)7cy_~~<6O>M1Z$-S$`f9z>AEYY?+si`V* zb)K~Vd(Cp6Y^n9i?RRF0#NJ4|vr)|`c%R1P_+S7iD~%HuXQ9yhJqBKxXnv`^YuN%rstwMoaQ^w;l3*w~94eEox!K z!p_@|t^eCxJ# z;k9>8)JKoAUD?aO-gt6FX3KFsrpl$APpek7uym^JST-R`Xt4;Vl4kmT^|hTfi741U3DT1LoLI8)l9u$H-uEpFbJBJ`v^vno7E_k=n;+;Z$a>$I&Cqg$bsc$TIes^v%%N36KUK@F@YBDcLH~qTLvE}2P z#jkjuRtmBSCUNZzdtwxEqOEAI*X9*WD=$p-T77h{YROuYYZvb&bw)7F4gB;$u(M5Q z!=^=AT%YX>joQpF1~U2iJHMQFAy>sYH~jwYLs6Vj7xizbPOyub>Z~cHeNW75oo%78 zZAPzPr@~&hr&r^vCzmCz%bmZvb@JqNYxxX2_SdezSFl_N%$&UVb;$3&+?+g(wG+;8 zRo8UfNINjI%IOCm|CK!l{@u^uzI93O!>OlVdHMUd?M+@5JHzVFYU;kd zm0v|4GdW}!E;(bdd*Pi&^VZMs&5+W`JeS<9Kjo$0v|8>rpI&d-ees8?{izkP-pfng zxYwP~((*6LKG`2SPf+XHr1;li-p6LY@UHVcwrOFPdtEQ*TF-;i{_r@a$Iq$=PJOlK z%JqcjroCax6=hn_&5xZ}|Fq1q|Iqa!>BuRuM?YWuy7kVU86FdIcLtWG@!X#tee&p} z`^hTDii*W%l`>aNlP=jBw6J;7_NUhrry0(=`}Cn;pF*wUX8-)tx~qQ2RUZw{F5&&5 z`|kd(83LBii)KA|^^<3tLerTg`Np^8zHgVDt08v%$DUZpvo9=IA5?emS~%rMN5Ji_ z6Nh%|Jt|s0Phj7N0^h4F*Wa842UEdS^TmOeW%;ICzdbakv)b)Hcng_HIRP4Y60}_jXVhW~E zA*8|yBm^(C^u04vfKF8a`5;IEW{-1zDNwH=hWm>lg%m6>%uq}Md9WC6A;@76k2@!p zBp}^2U0nAd!g9{l0-3bD4AwvTrbXA51 zCcuC|Rb^xd^fj80g(Y05q$n{nC$)$R6nma7z~Im*&d*KNRM5yw(S!tweo%gXi2}&O g-~iSS&a6rWx*l9&Cl-}}y<=!>V!@@V>gw+X0104XMgRZ+ literal 0 HcmV?d00001 diff --git a/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..492a76420b --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Moon-Snooze-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-Snooze-32.pdf b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-Snooze-32.pdf new file mode 100644 index 0000000000000000000000000000000000000000..60ce9c49012d140973421e6610a52799add4e524 GIT binary patch literal 1938 zcmZXVd05hS9LGxy7cnYk;IZmgNrg@U@jg()ux1U!V<~~6{4x|l0nMWxYioqJ1?oER zs?^d{rlwZbvUv{EtqZkmd8|2?W~O;;IncDVK7ah4@Av)uKIiv&ULQ2ojckTDw}C?O z01n^>g+c+q(GegLp&*Z~xk4@et7FL=t_TzWSTdI-0^Pu1J{wefs13UZne9m6jxukhQF@%u%Hq=MiK!d2Cng?Sq^+i# z5w4wBCa$s96?>m%Oj4Sg+h5oD&fJ@MX?J;xTaXQgQPsXn85CNQ7(3ic4+xD(+q(nNW8_@rWh4jL%U3C$UGhgsjpF=BW*scw2Yl6Fh)VecE6a>2 z|0#Z_CwObKQFvi)O3iFPTz>tB*&C-tdu2V*Bidw0XzygsI%0}Z^hi_S1YSc9+BTXd?PPg+AkcL#?>|k^yE5oH`YA8 z>66Tau_7cg?-fv&V2!$o~Sya_u`7Xdm_y(r1of3u7#(|>FRbC zKy)(PL+X5OSJ!pluzMVN@y)Z~zplXca{>YMStnA7Q}>UqQ6+%ynPRbmBKf5JWf|I7 zYjpv5?Gk54D0$^>Q~SnY<&~svi^!w*o@kHB4*Hj0KDwgPv7y8R48v+CdS=9g(VPUP z=e$d*wI3P?sR;Ry@A^x~^IDm4E|uOqu}sgTL>0Po@9*9M3_kvW+<{sV+DZ&)%TX2& zTZZOK=o$6FTUH+torn8<Y6`ue{$2p~S0Z5={EUYbbSbLk~W09LQQ*=7ji`S(e}E zu-`<&Zj!>?eQ+y%{5c*vr4{ixdBriXS9!LB7N3#oo9x#4)@^3JF^8X;edTBfe{v|| zZfp*H9M&yk%Ipg%p2h$|RN$ps!8b;wbf#|Dw-R#E&rok!`z^ZLg4MIE;zpMABlXf8qKaHlW7d@%N(LMkbk49VQN^H7iQ}S`?cCQyeBC;@%z! ziY%{s7-?7`E!Pr{*LL2*<<&iZVQ23ju0_r|xt;uWvQ%o$^>9mtoRWCPZ`;czrf37- z-O&$$rPuQGc}7lySIz&)Eg6U%HfqQ|HuoI^lRuSJWEx)T|J-aqw!S~D{^WDxvm@7V z$FmK;3P`f0m_2%C`8Dh>+DZSQ{dL~a=sI(pIqE}hJwjIp7doFqg6<*evL?Spa zr;66>e=+%2X$wykWjDp#Hh8d|}gNT6tolt(D+AP_;12ugA3wy94TfBTuUuu2Z zXyVr|=CZT;gsYzZB$*0Iuh&VECc}Lh%{gHkS`>`sodKvx$boy}0mjCC{rP#}114`d zsd(3|qXyW|A)`i4TPFfq{mw;>mA=$ndExG#bxAhJGF-zDy=!YFVT>z3(`Y(}%tjI* zHw_+z=r>$gd$ZzoLYe=W`b~!orUF!%h+2A1S(PGGUXxLDp!HD$&A@vDDuMNfa<^+) zUnV>7{#saywP(!LY2)01mp5F{X6aqWTi^^tJNZgccQvP|rl4!2DL5l_1G2JhDC=Bv zBBdSC_33qU-KVQO=F)xse%ca`J+2d2@q1y{+_JF?J~1s7 z3i(ljU{DBXNUuS(hT0@PPXzMR88w(+6satMS_7=!N&i^}8fj@sU_Cf&b*X@+0~)ZH zhs2Lk`{F;UU%0Ro7N=qTK_pFs!li_o!WJ4Qu|zB`KjcH#7y)ueaMx zJ&x@{emIB$wsY8+g`r?+e7*?KG(10GtQRK^R98R0Az1>^d^>m>yfqY!-bo?<3sRvI AF#rGn literal 0 HcmV?d00001 diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift new file mode 100644 index 0000000000..fd6f652415 --- /dev/null +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -0,0 +1,78 @@ +// +// VPNAddWidgetTip.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 TipKit + +/// A tip to suggest to the user that they add our VPN widget for quick access to the VPN +/// +struct VPNAddWidgetTip {} + +/// Necessary split to support older iOS versions. +/// +@available(iOS 17.0, *) +extension VPNAddWidgetTip: Tip { + + enum ActionIdentifiers: String { + case addWidget = "com.duckduckgo.vpn.tip.addWidget.action.addWidget" + } + + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.geolocationTipDismissedEvent") + + static let snoozeTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.geolocationTipDismissedEvent") + + @Parameter(.transient) + static var vpnEnabled: Bool = false + + private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.vpnDisconnectedEvent") + + var id: String { + "com.duckduckgo.vpn.tip.addWidget" + } + + var title: Text { + Text("Add VPN Widget") + } + + var message: Text? { + Text("Turn the VPN on and off right from the Home Screen.") + } + + var image: Image? { + Image(.vpnAddWidgetTipIcon) + } + + var actions: [Action] { + [Action(id: ActionIdentifiers.addWidget.rawValue) { + Text("Add widget") + .foregroundStyle(Color(designSystemColor: .accent)) + }] + } + + var rules: [Rule] { + #Rule(Self.geolocationTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.snoozeTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.$vpnEnabled) { + $0 == false + } + } +} diff --git a/DuckDuckGo/VPNGeoswitchingTip.swift b/DuckDuckGo/VPNGeoswitchingTip.swift new file mode 100644 index 0000000000..e8110fa18c --- /dev/null +++ b/DuckDuckGo/VPNGeoswitchingTip.swift @@ -0,0 +1,58 @@ +// +// VPNGeoswitchingTip.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 TipKit + +/// A tip to suggest to the user to change their location using geo-switching +/// +struct VPNGeoswitchingTip {} + +@available(iOS 17.0, *) +extension VPNGeoswitchingTip: Tip { + + private static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.geoswitching.vpnConnectedEvent") + + var id: String { + "com.duckduckgo.vpn.tip.geoswitching" + } + + var title: Text { + Text("Change Your Location") + } + + var message: Text? { + Text("You can customize your VPN location by connecting to any of our servers worldwide.") + } + + var image: Image? { + Image(.vpnChangeLocationTipIcon) + } + + var rules: [Rule] { + #Rule(Self.vpnConnectedEvent) { + $0.donations.donatedWithin(.week).count > 0 + } + } + + static func donateVPNConnectedEvent() { + Task { + await vpnConnectedEvent.donate() + } + } +} diff --git a/DuckDuckGo/VPNSnoozeTip.swift b/DuckDuckGo/VPNSnoozeTip.swift new file mode 100644 index 0000000000..9baa7bacba --- /dev/null +++ b/DuckDuckGo/VPNSnoozeTip.swift @@ -0,0 +1,71 @@ +// +// VPNSnoozeTip.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 TipKit + +/// A tip to suggest to the user to use the snooze feature to momentarily disable the VPN +/// +struct VPNSnoozeTip {} + +/// Necessary split to support older iOS versions. +/// +@available(iOS 17.0, *) +extension VPNSnoozeTip: Tip { + + enum ActionIdentifiers: String { + case learnMore = "com.duckduckgo.vpn.tip.snooze.learnMoreId" + } + + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.snooze.geolocationTipDismissedEvent") + + @Parameter(.transient) + static var vpnEnabled: Bool = false + + var id: String { + "com.duckduckgo.vpn.tip.snooze" + } + + var title: Text { + Text("Avoid VPN Conflicts") + } + + var message: Text? { + Text("You can use sites or apps that block VPN traffic by snoozing the VPN connection.") + } + + var image: Image? { + Image(.vpnUseSnoozeTipIcon) + } + + var actions: [Action] { + [Action(id: ActionIdentifiers.learnMore.rawValue) { + Text("Learn more") + .foregroundStyle(Color(designSystemColor: .accent)) + }] + } + + var rules: [Rule] { + #Rule(Self.geolocationTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.$vpnEnabled) { + $0 == true + } + } +} diff --git a/DuckDuckGo/ViewExtension.swift b/DuckDuckGo/ViewExtension.swift index 3c00ddf314..e322db6120 100644 --- a/DuckDuckGo/ViewExtension.swift +++ b/DuckDuckGo/ViewExtension.swift @@ -62,6 +62,12 @@ extension View { .applyBackground() } + /// Removes the grouped list style insets for a single row. + /// + func removeGroupedListStyleInsets() -> some View { + listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } + @ViewBuilder func applyBackground() -> some View { hideScrollContentBackground() From 41cf8e9fdbc704e692bd5019d01e811f95f137ad Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Mon, 28 Oct 2024 16:20:17 +0100 Subject: [PATCH 24/26] Use cookie to share subscription access token on DDG domains (#3488) Task/Issue URL: https://app.asana.com/0/1108686900785972/1208264562025859/f **Description**: Store and keep in sync the subscription access token for the duckduckgo.com domain cookie. For the implementation guidelines please see description of the linked task. --- Core/PixelEvent.swift | 10 ++++ DuckDuckGo.xcodeproj/project.pbxproj | 6 ++- .../xcshareddata/swiftpm/Package.resolved | 8 +-- DuckDuckGo/AppDelegate.swift | 21 +++++++- DuckDuckGo/MainViewController.swift | 8 ++- ...riptionCookieManageEventPixelMapping.swift | 52 +++++++++++++++++++ DuckDuckGo/TabManager.swift | 12 +++-- DuckDuckGo/TabViewController.swift | 14 +++-- ...ViewControllerLongPressMenuExtension.swift | 3 +- DuckDuckGoTests/MockTabDelegate.swift | 5 +- .../OnboardingDaxFavouritesTests.swift | 4 +- .../OnboardingNavigationDelegateTests.swift | 4 +- 12 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index bd35b54b15..0a021242ea 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -708,6 +708,11 @@ extension Pixel { case privacyProTransactionProgressNotHiddenAfter60s case privacyProSuccessfulSubscriptionAttribution case privacyProKeychainAccessError + case privacyProSubscriptionCookieMissingTokenOnSignIn + case privacyProSubscriptionCookieMissingCookieOnSignOut + case privacyProSubscriptionCookieRefreshedWithUpdate + case privacyProSubscriptionCookieRefreshedWithDelete + case privacyProSubscriptionCookieFailedToSetSubscriptionCookie // MARK: Pixel Experiment case pixelExperimentEnrollment @@ -1520,6 +1525,11 @@ extension Pixel.Event { case .privacyProTransactionProgressNotHiddenAfter60s: return "m_privacy-pro_progress_not_hidden_after_60s" case .privacyProSuccessfulSubscriptionAttribution: return "m_subscribe" case .privacyProKeychainAccessError: return "m_privacy-pro_keychain_access_error" + case .privacyProSubscriptionCookieMissingTokenOnSignIn: return "m_privacy-pro_subscription-cookie-missing_token_on_sign_in" + case .privacyProSubscriptionCookieMissingCookieOnSignOut: return "m_privacy-pro_subscription-cookie-missing_cookie_on_sign_out" + case .privacyProSubscriptionCookieRefreshedWithUpdate: return "m_privacy-pro_subscription-cookie-refreshed_with_update" + case .privacyProSubscriptionCookieRefreshedWithDelete: return "m_privacy-pro_subscription-cookie-refreshed_with_delete" + case .privacyProSubscriptionCookieFailedToSetSubscriptionCookie: return "m_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie" // MARK: Pixel Experiment case .pixelExperimentEnrollment: return "pixel_experiment_enrollment" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 73dc71bf7a..00bb90136d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ 1E1D8B6C29953CE300C96994 /* autoconsent-test-page-banner.html in Resources */ = {isa = PBXBuildFile; fileRef = 1E1D8B6929953CE300C96994 /* autoconsent-test-page-banner.html */; }; 1E24295E293F57FA00584836 /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E24295D293F57FA00584836 /* LottieView.swift */; }; 1E242960293F585300584836 /* cookie-icon-animated-40-light.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E24295F293F585300584836 /* cookie-icon-animated-40-light.json */; }; + 1E39BEB02CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */; }; 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4527B6A33600961E25 /* DownloadsListViewModel.swift */; }; 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4727B6A35400961E25 /* DownloadsListModel.swift */; }; 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4927B6A38000961E25 /* DownloadListRepresentable.swift */; }; @@ -1378,6 +1379,7 @@ 1E24295D293F57FA00584836 /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = ""; }; 1E24295F293F585300584836 /* cookie-icon-animated-40-light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-icon-animated-40-light.json"; sourceTree = ""; }; 1E25D5312C92126B004400F0 /* SubscriptionFeatureAvailabilityMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFeatureAvailabilityMock.swift; sourceTree = ""; }; + 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionCookieManageEventPixelMapping.swift; sourceTree = ""; }; 1E4DCF4527B6A33600961E25 /* DownloadsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListViewModel.swift; sourceTree = ""; }; 1E4DCF4727B6A35400961E25 /* DownloadsListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListModel.swift; sourceTree = ""; }; 1E4DCF4927B6A38000961E25 /* DownloadListRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadListRepresentable.swift; sourceTree = ""; }; @@ -5369,6 +5371,7 @@ D664C7962B289AA000CBFA76 /* Extensions */, D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */, + 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */, ); path = Subscription; sourceTree = ""; @@ -7576,6 +7579,7 @@ C1641EAF2BC2F5140012607A /* ImportPasswordsViewController.swift in Sources */, D63FF8982C1B6A45006DE24D /* DuckPlayer.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, + 1E39BEB02CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift in Sources */, C1935A102C88D131001AD72D /* AutofillSurveyManager.swift in Sources */, 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, @@ -11006,7 +11010,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 200.3.0; + version = 201.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c31d42aab9..21aa8e0ac9 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "fdf6f75d570a5ef6058efa881e11f9467627fbf4", - "version" : "200.2.1" + "revision" : "e5946eee6af859690cc1cc5e51daef3c8368981b", + "version" : "201.0.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "1ed569676555d493c9c5575eaed22aa02569aac9", - "version" : "6.19.0" + "revision" : "b74549bd869fdecc16fad851f2f608b1724764df", + "version" : "6.25.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index cc09ced6bb..d5f34b1a3c 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -88,6 +88,7 @@ import os.log private var autofillUsageMonitor = AutofillUsageMonitor() private(set) var subscriptionFeatureAvailability: SubscriptionFeatureAvailability! + private var subscriptionCookieManager: SubscriptionCookieManaging! var privacyProDataReporter: PrivacyProDataReporting! // MARK: - Feature specific app event handlers @@ -122,7 +123,7 @@ import os.log } } - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { #if targetEnvironment(simulator) @@ -312,6 +313,17 @@ import os.log subscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, purchasePlatform: .appStore) + + subscriptionCookieManager = SubscriptionCookieManager(subscriptionManager: AppDependencyProvider.shared.subscriptionManager, + currentCookieStore: { [weak self] in + guard self?.mainViewController?.tabManager.model.hasActiveTabs ?? false else { + // We shouldn't interact with WebKit's cookie store unless we have a WebView, + // eventually the subscription cookie will be refreshed on opening the first tab + return nil + } + + return WKWebsiteDataStore.current().httpCookieStore + }, eventMapping: SubscriptionCookieManageEventPixelMapping()) homePageConfiguration = HomePageConfiguration(variantManager: AppDependencyProvider.shared.variantManager, remoteMessagingClient: remoteMessagingClient, @@ -350,7 +362,8 @@ import os.log contextualOnboardingLogic: daxDialogs, contextualOnboardingPixelReporter: onboardingPixelReporter, subscriptionFeatureAvailability: subscriptionFeatureAvailability, - voiceSearchHelper: voiceSearchHelper) + voiceSearchHelper: voiceSearchHelper, + subscriptionCookieManager: subscriptionCookieManager) main.loadViewIfNeeded() syncErrorHandler.alertPresenter = main @@ -574,6 +587,10 @@ import os.log } } + Task { @MainActor in + await subscriptionCookieManager.refreshSubscriptionCookie() + } + let importPasswordsStatusHandler = ImportPasswordsStatusHandler(syncService: syncService) importPasswordsStatusHandler.checkSyncSuccessStatus() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 78000f9d7b..d1a2fd2e36 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -123,6 +123,7 @@ class MainViewController: UIViewController { private var feedbackCancellable: AnyCancellable? let subscriptionFeatureAvailability: SubscriptionFeatureAvailability + private let subscriptionCookieManager: SubscriptionCookieManaging let privacyProDataReporter: PrivacyProDataReporting private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger @@ -195,7 +196,8 @@ class MainViewController: UIViewController { tutorialSettings: TutorialSettings = DefaultTutorialSettings(), statisticsStore: StatisticsStore = StatisticsUserDefaults(), subscriptionFeatureAvailability: SubscriptionFeatureAvailability, - voiceSearchHelper: VoiceSearchHelperProtocol + voiceSearchHelper: VoiceSearchHelperProtocol, + subscriptionCookieManager: SubscriptionCookieManaging ) { self.bookmarksDatabase = bookmarksDatabase self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner @@ -217,7 +219,8 @@ class MainViewController: UIViewController { privacyProDataReporter: privacyProDataReporter, contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, - onboardingPixelReporter: contextualOnboardingPixelReporter) + onboardingPixelReporter: contextualOnboardingPixelReporter, + subscriptionCookieManager: subscriptionCookieManager) self.syncPausedStateManager = syncPausedStateManager self.privacyProDataReporter = privacyProDataReporter self.homeTabManager = NewTabPageManager() @@ -228,6 +231,7 @@ class MainViewController: UIViewController { self.statisticsStore = statisticsStore self.subscriptionFeatureAvailability = subscriptionFeatureAvailability self.voiceSearchHelper = voiceSearchHelper + self.subscriptionCookieManager = subscriptionCookieManager super.init(nibName: nil, bundle: nil) diff --git a/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift b/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift new file mode 100644 index 0000000000..19efdba9dd --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift @@ -0,0 +1,52 @@ +// +// SubscriptionCookieManageEventPixelMapping.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 +import Common +import Core +import Subscription + +public final class SubscriptionCookieManageEventPixelMapping: EventMapping { + + public init() { + super.init { event, _, _, _ in + let pixel: Pixel.Event = { + switch event { + case .errorHandlingAccountDidSignInTokenIsMissing: + return .privacyProSubscriptionCookieMissingTokenOnSignIn + case .errorHandlingAccountDidSignOutCookieIsMissing: + return .privacyProSubscriptionCookieMissingCookieOnSignOut + case .subscriptionCookieRefreshedWithUpdate: + return .privacyProSubscriptionCookieRefreshedWithUpdate + case .subscriptionCookieRefreshedWithDelete: + return .privacyProSubscriptionCookieRefreshedWithDelete + case .failedToSetSubscriptionCookie: + return .privacyProSubscriptionCookieFailedToSetSubscriptionCookie + } + }() + + Pixel.fire(pixel: pixel) + + } + } + + override init(mapping: @escaping EventMapping.Mapping) { + fatalError("Use init()") + } +} diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 24077c9454..ba67adae9b 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -24,6 +24,7 @@ import WebKit import BrowserServicesKit import Persistence import History +import Subscription import os.log class TabManager { @@ -41,6 +42,7 @@ class TabManager { private let contextualOnboardingPresenter: ContextualOnboardingPresenting private let contextualOnboardingLogic: ContextualOnboardingLogic private let onboardingPixelReporter: OnboardingPixelReporting + private let subscriptionCookieManager: SubscriptionCookieManaging weak var delegate: TabDelegate? @@ -57,7 +59,8 @@ class TabManager { privacyProDataReporter: PrivacyProDataReporting, contextualOnboardingPresenter: ContextualOnboardingPresenting, contextualOnboardingLogic: ContextualOnboardingLogic, - onboardingPixelReporter: OnboardingPixelReporting) { + onboardingPixelReporter: OnboardingPixelReporting, + subscriptionCookieManager: SubscriptionCookieManaging) { self.model = model self.previewsSource = previewsSource self.bookmarksDatabase = bookmarksDatabase @@ -68,6 +71,7 @@ class TabManager { self.contextualOnboardingPresenter = contextualOnboardingPresenter self.contextualOnboardingLogic = contextualOnboardingLogic self.onboardingPixelReporter = onboardingPixelReporter + self.subscriptionCookieManager = subscriptionCookieManager registerForNotifications() } @@ -89,7 +93,8 @@ class TabManager { contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, - featureFlagger: AppDependencyProvider.shared.featureFlagger) + featureFlagger: AppDependencyProvider.shared.featureFlagger, + subscriptionCookieManager: subscriptionCookieManager) controller.applyInheritedAttribution(inheritedAttribution) controller.attachWebView(configuration: configuration, andLoadRequest: url == nil ? nil : URLRequest.userInitiated(url!), @@ -167,7 +172,8 @@ class TabManager { contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, - featureFlagger: AppDependencyProvider.shared.featureFlagger) + featureFlagger: AppDependencyProvider.shared.featureFlagger, + subscriptionCookieManager: subscriptionCookieManager) controller.attachWebView(configuration: configCopy, andLoadRequest: request, consumeCookies: !model.hasActiveTabs, diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 0cb459b413..7f05eb30ba 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -41,6 +41,7 @@ import NetworkProtection import Onboarding import os.log import Navigation +import Subscription class TabViewController: UIViewController { @@ -92,6 +93,7 @@ class TabViewController: UIViewController { let appSettings: AppSettings var featureFlagger: FeatureFlagger + let subscriptionCookieManager: SubscriptionCookieManaging private lazy var internalUserDecider = AppDependencyProvider.shared.internalUserDecider private lazy var autofillNeverPromptWebsitesManager = AppDependencyProvider.shared.autofillNeverPromptWebsitesManager @@ -321,7 +323,8 @@ class TabViewController: UIViewController { contextualOnboardingLogic: ContextualOnboardingLogic, onboardingPixelReporter: OnboardingCustomInteractionPixelReporting, urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(), - featureFlagger: FeatureFlagger) -> TabViewController { + featureFlagger: FeatureFlagger, + subscriptionCookieManager: SubscriptionCookieManaging) -> TabViewController { let storyboard = UIStoryboard(name: "Tab", bundle: nil) let controller = storyboard.instantiateViewController(identifier: "TabViewController", creator: { coder in TabViewController(coder: coder, @@ -336,7 +339,8 @@ class TabViewController: UIViewController { contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, urlCredentialCreator: urlCredentialCreator, - featureFlagger: featureFlagger + featureFlagger: featureFlagger, + subscriptionCookieManager: subscriptionCookieManager ) }) return controller @@ -369,7 +373,8 @@ class TabViewController: UIViewController { contextualOnboardingLogic: ContextualOnboardingLogic, onboardingPixelReporter: OnboardingCustomInteractionPixelReporting, urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(), - featureFlagger: FeatureFlagger) { + featureFlagger: FeatureFlagger, + subscriptionCookieManager: SubscriptionCookieManaging) { self.tabModel = tabModel self.appSettings = appSettings self.bookmarksDatabase = bookmarksDatabase @@ -388,6 +393,7 @@ class TabViewController: UIViewController { self.onboardingPixelReporter = onboardingPixelReporter self.urlCredentialCreator = urlCredentialCreator self.featureFlagger = featureFlagger + self.subscriptionCookieManager = subscriptionCookieManager super.init(coder: aDecoder) } @@ -636,6 +642,8 @@ class TabViewController: UIViewController { await webView.configuration.websiteDataStore.dataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) let cookieStore = webView.configuration.websiteDataStore.httpCookieStore await WebCacheManager.shared.consumeCookies(httpCookieStore: cookieStore) + subscriptionCookieManager.resetLastRefreshDate() + await subscriptionCookieManager.refreshSubscriptionCookie() doLoad() } } diff --git a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift index 38770be4e7..8de6d3de6c 100644 --- a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift @@ -111,7 +111,8 @@ extension TabViewController { contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, - featureFlagger: AppDependencyProvider.shared.featureFlagger) + featureFlagger: AppDependencyProvider.shared.featureFlagger, + subscriptionCookieManager: subscriptionCookieManager) tabController.isLinkPreview = true let configuration = WKWebViewConfiguration.nonPersistent() tabController.attachWebView(configuration: configuration, andLoadRequest: URLRequest.userInitiated(url), consumeCookies: false) diff --git a/DuckDuckGoTests/MockTabDelegate.swift b/DuckDuckGoTests/MockTabDelegate.swift index 3cd9a7c74c..b37a63a332 100644 --- a/DuckDuckGoTests/MockTabDelegate.swift +++ b/DuckDuckGoTests/MockTabDelegate.swift @@ -24,6 +24,8 @@ import BrowserServicesKit import PrivacyDashboard import Core import Persistence +import Subscription +import SubscriptionTestingUtilities @testable import DuckDuckGo final class MockTabDelegate: TabDelegate { @@ -134,7 +136,8 @@ extension TabViewController { contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: contextualOnboardingPixelReporter, urlCredentialCreator: MockCredentialCreator(), - featureFlagger: featureFlagger + featureFlagger: featureFlagger, + subscriptionCookieManager: SubscriptionCookieManagerMock() ) tab.attachWebView(configuration: .nonPersistent(), andLoadRequest: nil, consumeCookies: false, customWebView: customWebView) return tab diff --git a/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift b/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift index ad3c3be669..ff93a30198 100644 --- a/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift +++ b/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift @@ -26,6 +26,7 @@ import BrowserServicesKit import RemoteMessaging import Configuration import Core +import SubscriptionTestingUtilities @testable import DuckDuckGo final class OnboardingDaxFavouritesTests: XCTestCase { @@ -78,7 +79,8 @@ final class OnboardingDaxFavouritesTests: XCTestCase { contextualOnboardingPixelReporter: OnboardingPixelReporterMock(), tutorialSettings: tutorialSettingsMock, subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock.enabled, - voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true) + voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true), + subscriptionCookieManager: SubscriptionCookieManagerMock() ) let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() diff --git a/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift b/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift index ca052c5717..7ccd6d49b9 100644 --- a/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift +++ b/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift @@ -26,6 +26,7 @@ import BrowserServicesKit import RemoteMessaging import Configuration import Combine +import SubscriptionTestingUtilities @testable import DuckDuckGo @testable import Core @@ -76,7 +77,8 @@ final class OnboardingNavigationDelegateTests: XCTestCase { contextualOnboardingLogic: ContextualOnboardingLogicMock(), contextualOnboardingPixelReporter: onboardingPixelReporter, subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock.enabled, - voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true)) + voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true), + subscriptionCookieManager: SubscriptionCookieManagerMock()) let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() window.makeKeyAndVisible() From 5baa4cb090a15a6e1431a23ef63ecebed0b6518e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Mon, 28 Oct 2024 17:13:16 +0100 Subject: [PATCH 25/26] Fix the condition (#3496) Task/Issue URL: https://app.asana.com/0/414235014887631/1207921724970682/f **Description**: Make sure the if condition actually is checked correctly. --- scripts/prepare_release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 06aef4d0a8..5fe9059474 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -255,7 +255,7 @@ main() { read_command_line_arguments "$@" checkout_base_branch - if [[ $is_subsequent_release ]]; then + if [[ "$is_subsequent_release" -eq 1 ]]; then create_build_branch elif [[ $is_hotfix ]]; then create_build_branch From c5aab0c38ea3fc08f7354b11b73c1652fe3fc5ee Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 28 Oct 2024 17:32:41 +0100 Subject: [PATCH 26/26] Release 7.143.0-0 (#3497) Please make sure all GH checks passed before merging. It can take around 20 minutes. Briefly review this PR to see if there are no issues or red flags and then merge it. --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 57 ++++++++++++++++--- DuckDuckGo.xcodeproj/project.pbxproj | 56 +++++++++--------- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 5 files changed, 80 insertions(+), 41 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 5a76c2767c..e99711a2c3 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.142.0 +MARKETING_VERSION = 7.143.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index c044c7c70a..15f2c36375 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 = "\"6098a36b63dcb92404871b8b6c92a46e\"" - public static let embeddedDataSHA = "a72538e90ef1aba77d30dde1379e7344fe9f3ca1655796c9b555bed412daa205" + public static let embeddedDataETag = "\"f8b9cfd5f1eb7b77c21d4476f85bd177\"" + public static let embeddedDataSHA = "c26c97714d73a9e1e99dbd341d5890da42b49d34a296672be3d3cea00bdd37a0" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index bd6b729093..edd16be36c 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1729265730687, + "version": 1730109523334, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -360,6 +360,9 @@ { "domain": "speedweek.com" }, + { + "domain": "la-becanerie.com" + }, { "domain": "marvel.com" }, @@ -404,7 +407,7 @@ } } }, - "hash": "fba0eeeda60051f6354cf5d26c1e1a63" + "hash": "8392e127a3bcaee5c2913df355a7d254" }, "autofillBreakageReporter": { "state": "enabled", @@ -499,12 +502,15 @@ "steps": [ { "percent": 10 + }, + { + "percent": 50 } ] } } }, - "hash": "75ff21cf9a4181783c06965edd6fe746" + "hash": "9de8e4b066aa23f7c20ca638ee0d9f1a" }, "bookmarks": { "state": "enabled", @@ -1333,6 +1339,9 @@ { "domain": "flexmls.com" }, + { + "domain": "humana.com" + }, { "domain": "instructure.com" }, @@ -1340,7 +1349,12 @@ "domain": "centerwellpharmacy.com" } ], - "hash": "b4eff737bff7f262ceb567b735e1cc41" + "hash": "980bf875526f3cc7892c001a7d2e5a74" + }, + "contextualOnboarding": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" }, "cookie": { "settings": { @@ -1428,6 +1442,10 @@ { "domain": "xfinity.com", "reason": "https://github.com/duckduckgo/privacy-configuration/pull/2149" + }, + { + "domain": "ihg.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/pull/2383" } ], "ddgDefaultSites": [ @@ -1448,13 +1466,16 @@ }, { "domain": "xfinity.com" + }, + { + "domain": "ihg.com" } ], "omitVersionSites": [] }, "exceptions": [], "state": "enabled", - "hash": "2ed8c3ccd40db2d9dca1e7ecc4231045" + "hash": "e577ccb473bdb7ada49c4d3c6e79cf01" }, "dbp": { "state": "disabled", @@ -1482,7 +1503,7 @@ "state": "disabled" }, "openInNewTab": { - "state": "disabled" + "state": "internal" }, "enableDuckPlayer": { "state": "enabled", @@ -1575,7 +1596,7 @@ ] }, "state": "enabled", - "hash": "7f82d68f07b3e2aaac1b89725c1d379e" + "hash": "c21895584fc5a38e4290c7941ec7d5f8" }, "elementHiding": { "exceptions": [ @@ -5324,6 +5345,9 @@ { "domain": "dollargeneral.com" }, + { + "domain": "milesplit.live" + }, { "domain": "monsterenergy.com" }, @@ -5361,7 +5385,7 @@ "privacy-test-pages.site" ] }, - "hash": "be142a65e913cf958af67e2cd5dd8cc4" + "hash": "37630ab090682ee7d004120a42031281" }, "harmfulApis": { "settings": { @@ -8844,6 +8868,16 @@ } ] }, + "svonm.com": { + "rules": [ + { + "rule": "hgc-cf-cache-1.svonm.com", + "domains": [ + "t-online.de" + ] + } + ] + }, "taboola.com": { "rules": [ { @@ -9334,7 +9368,7 @@ "domain": "centerwellpharmacy.com" } ], - "hash": "e61f68717bcb4a465182a7ddb7d5cc4d" + "hash": "434130223ee6493827d477d0171521da" }, "trackingCookies1p": { "settings": { @@ -9605,6 +9639,11 @@ "state": "disabled", "hash": "728493ef7a1488e4781656d3f9db84aa" }, + "windowsNewTabPageExperiment": { + "state": "disabled", + "exceptions": [], + "hash": "c292bb627849854515cebbded288ef5a" + }, "windowsPermissionUsage": { "exceptions": [], "state": "disabled", diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 00bb90136d..1e414812e4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9225,7 +9225,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9262,7 +9262,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9352,7 +9352,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9379,7 +9379,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9528,7 +9528,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9553,7 +9553,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9622,7 +9622,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9656,7 +9656,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9689,7 +9689,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -9719,7 +9719,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10029,7 +10029,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10060,7 +10060,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10088,7 +10088,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -10121,7 +10121,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -10151,7 +10151,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -10184,11 +10184,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10421,7 +10421,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10448,7 +10448,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10480,7 +10480,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10517,7 +10517,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -10552,7 +10552,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -10587,11 +10587,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10764,11 +10764,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -10797,10 +10797,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 549ce4019a..a7f84961db 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.142.0 + 7.143.0 Key version Title