From 234b7d477e6cc222a49356f8f8ff043d8df21265 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Mon, 4 Nov 2024 11:26:25 +0100 Subject: [PATCH 01/12] Remove com.apple.security.device.usb entitlement (#3495) Task/Issue URL: https://app.asana.com/0/0/1208672609826736/f Description: This change removes unneeded com.apple.security.device.usb entitlement usage. --- DuckDuckGo/DuckDuckGoAppStore.entitlements | 2 -- DuckDuckGo/DuckDuckGoAppStoreCI.entitlements | 2 -- DuckDuckGo/DuckDuckGoAppStoreDebug.entitlements | 2 -- 3 files changed, 6 deletions(-) diff --git a/DuckDuckGo/DuckDuckGoAppStore.entitlements b/DuckDuckGo/DuckDuckGoAppStore.entitlements index 522e54bbf6..f8f7829a80 100644 --- a/DuckDuckGo/DuckDuckGoAppStore.entitlements +++ b/DuckDuckGo/DuckDuckGoAppStore.entitlements @@ -23,8 +23,6 @@ com.apple.security.device.camera - com.apple.security.device.usb - com.apple.security.files.downloads.read-write com.apple.security.files.user-selected.read-write diff --git a/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements b/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements index 789e45511c..f3cfd39790 100644 --- a/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements +++ b/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements @@ -16,8 +16,6 @@ com.apple.security.device.camera - com.apple.security.device.usb - com.apple.security.files.downloads.read-write com.apple.security.files.user-selected.read-write diff --git a/DuckDuckGo/DuckDuckGoAppStoreDebug.entitlements b/DuckDuckGo/DuckDuckGoAppStoreDebug.entitlements index 884910dc38..7502df54c6 100644 --- a/DuckDuckGo/DuckDuckGoAppStoreDebug.entitlements +++ b/DuckDuckGo/DuckDuckGoAppStoreDebug.entitlements @@ -21,8 +21,6 @@ com.apple.security.device.camera - com.apple.security.device.usb - com.apple.security.files.downloads.read-write com.apple.security.files.user-selected.read-write From 58cb27d7c6d098e2347287e568e4d4efc7651f93 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Tue, 5 Nov 2024 14:49:38 +0100 Subject: [PATCH 02/12] Send pixel on sync secure storage read failure (#3497) Task/Issue URL: https://app.asana.com/0/1201493110486074/1208686320819590/f Tech Design URL: CC: **Description**: On investigating a hard-to-reproduce issue with sync, I noticed there's a gap in error reporting when the secure storage (keychain) is not available. This adds a pixel for that case. **Steps to test this PR**: Just a pixel in an error case. Hard to test without altering code. But if you do want to do that: 1. Enable sync 2. Change `BSK.DDGSync.SecureStorage.account()` to throw every time 3. Go to the Settings -> Sync screen 4. You should see the `sync_secure_storage_read_error` Pixel in the debug console **Definition of Done**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- DuckDuckGo/Statistics/GeneralPixel.swift | 3 +++ DuckDuckGo/Sync/SyncErrorHandler.swift | 7 ++++++- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 731efbd4e7..bd9191be2a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14778,7 +14778,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 203.1.0; + version = 203.2.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6c7c3d5617..2fac7940db 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" : "19f1e5c945aa92562ad2d087e8d6c99801edf656", - "version" : "203.1.0" + "revision" : "56dbee74e34d37b6e699921a0b9bce2b8f22711d", + "version" : "203.2.0" } }, { @@ -75,7 +75,7 @@ { "identity" : "lottie-spm", "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm.git", + "location" : "https://github.com/airbnb/lottie-spm", "state" : { "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", "version" : "4.4.3" diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 51cf05406e..6dfef319bf 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -409,6 +409,7 @@ enum GeneralPixel: PixelKitEventV2 { case syncDeleteAccountError(error: Error) case syncLoginExistingAccountError(error: Error) case syncCannotCreateRecoveryPDF + case syncSecureStorageReadError(error: Error) case bookmarksCleanupFailed case bookmarksCleanupAttemptedWhileSyncWasEnabled @@ -1038,6 +1039,7 @@ enum GeneralPixel: PixelKitEventV2 { case .syncDeleteAccountError: return "sync_delete_account_error" case .syncLoginExistingAccountError: return "sync_login_existing_account_error" case .syncCannotCreateRecoveryPDF: return "sync_cannot_create_recovery_pdf" + case .syncSecureStorageReadError: return "sync_secure_storage_read_error" case .bookmarksCleanupFailed: return "bookmarks_cleanup_failed" case .bookmarksCleanupAttemptedWhileSyncWasEnabled: return "bookmarks_cleanup_attempted_while_sync_was_enabled" @@ -1093,6 +1095,7 @@ enum GeneralPixel: PixelKitEventV2 { .syncRefreshDevicesError(let error), .syncDeleteAccountError(let error), .syncLoginExistingAccountError(let error), + .syncSecureStorageReadError(let error), .bookmarksCouldNotLoadDatabase(let error?): return error default: return nil diff --git a/DuckDuckGo/Sync/SyncErrorHandler.swift b/DuckDuckGo/Sync/SyncErrorHandler.swift index f44cf0edf1..beca1a0e2c 100644 --- a/DuckDuckGo/Sync/SyncErrorHandler.swift +++ b/DuckDuckGo/Sync/SyncErrorHandler.swift @@ -91,7 +91,12 @@ public class SyncErrorHandler: EventMapping, ObservableObject { public init(alertPresenter: SyncAlertsPresenting = SyncAlertsPresenter()) { self.alertPresenter = alertPresenter super.init { event, _, _, _ in - PixelKit.fire(DebugEvent(GeneralPixel.syncSentUnauthenticatedRequest, error: event)) + switch event { + case .failedToReadSecureStore(let status): + PixelKit.fire(DebugEvent(GeneralPixel.syncSecureStorageReadError(error: event), error: event)) + default: + PixelKit.fire(DebugEvent(GeneralPixel.syncSentUnauthenticatedRequest, error: event)) + } } } diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 33cc4173f6..c6155ab696 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.2.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), .package(path: "../Freemium"), diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 4780108c7d..250be48599 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "VPNAppLauncher", targets: ["VPNAppLauncher"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.2.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), .package(path: "../UDSHelper"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 4948ae40a6..18ba3fb88f 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.2.0"), .package(path: "../SwiftUIExtensions") ], targets: [ From d7b8f76a4c71e0114cece654af677a46028c03be Mon Sep 17 00:00:00 2001 From: Pete Smith <5278441+aataraxiaa@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:04:56 +0000 Subject: [PATCH 03/12] Freemium PIR: Ship Review Changes - Updated New Tab Banner UI (#3501) Task/Issue URL: https://app.asana.com/0/1206488453854252/1208677018043704/f **Description**: Updates the Freemium PIR new tab page promotion banner as follows: 1. Changes the icon 2. Adds a title 3. Changes the description text and color 4. Adds optional bold text to the promotion description text --- .../Contents.json | 15 +++++++ .../Information-Remover-128.pdf | Bin 0 -> 10994 bytes DuckDuckGo/Common/Localizables/UserText.swift | 38 +++++++++++------- .../PromotionView+FreemiumDBP.swift | 31 ++++++++------ .../HomePage/Model/PromotionViewModel.swift | 8 ++-- DuckDuckGo/HomePage/View/PromotionView.swift | 32 ++++++++++++--- DuckDuckGo/Localizable.xcstrings | 3 ++ ...miumDBPPromotionViewCoordinatorTests.swift | 4 +- 8 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Information-Remover-128.pdf diff --git a/DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Contents.json new file mode 100644 index 0000000000..6b0b87b470 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Information-Remover-128.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Information-Remover-128.pdf b/DuckDuckGo/Assets.xcassets/Information-Remover-128.imageset/Information-Remover-128.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a770d6b848733efd3a27f5b226fe30cf0ddd272f GIT binary patch literal 10994 zcmeHNc~}$I7I!Q9<|%c-wNOU=EQ^w4X0lq8vWONHHF2RVzJ?G;FeH!!9tiqA6&18~ zsUV2Bp-8QDfwr_?rP`|1R#B@~D&T?^w3XshaH+EN-I>hXnLrSz^?mL4b^b{1{O&#H z-h0lu_ndp~jG5`@?}70o2!f#k)DRwxps1%O%AcJSt48@jGs2_QDl@>q!=I+sN0~J! z4jRg7XtL1|AIl-2-Ys<$jfGGF_!*UYQ>@ac)~k}N>L*U5St;Vf%~YrU24f73$<-!9 zyiuh#kzK%+K0by-Gz1m!1k}S8gi;1#OJRT&pfs6{YGn)(hhR(-NbUhkunzLU2&TCz z1`+VYLWw{m109OQQZX(81@ccQLQN_?sHVq*;Y=w8Az+zBbsQ8D=~#MO7?)auL?U`* zRtJ*-TBXOs&PK*S)NF*9Oe~OsNe}{&R0`C}li)HThFi}DN`Mpq%~8k`0||(PD8?g% z1SS-tVx|fJUa@h*}>(8zLx?iwz+c zlV#P32`O1mp#((9wMef!A!psmmHH?(cMU*gllBfCzMmmRsntKTiGRFaW!4(>)LIu> zt&?Pw6`49wr9mUWGKT_)GNZ{T1;-jxZw!dwb9(LyXyVX}GlGrY*KJbDTI4_(#^c+H%A61|(+n z$bWvv4<^g^1(88JM#hM#O(eoPa}Fu$t3bG~DpThiJqA||4xGAWVN$-4-oapX41z=7 z1wkDLgzK;)N6u`Hq+4KAjDqp0J>C_tDckl};`wRRLBfFMXcaokFKj)D|PTSoyRIga%HUycH23=J08 z2vo|{v4Ll2HyCDj`|J7%(wP7D6O0pJHbCaR-G62i`P29bOu#DgujDA$WymSP&7O4? zn21@Wz*P`T89D&YISFDMrvO(yII=LbwlEH@24TZS2*)58JlH`{#{oG^S#p~oha7YT zn?RWB{2<76evkx!unY@akVCFP+KxX45i&C2w1gOkC8V{5acDINJbXeFhz?Rg5Qq+f zu%Kh9ZDAZ@4T4D+*A8LO5!5jRK}QheFlEVYf*kU8;s$ryfdjP~Exu186vHDX@VZ>* zM{ORq^F1IT0QcoTU^z4U8sAo=X?5;`ExYWQ*%v4se>4kvSl-b&3Q= zhMc7@NgKLnXpig=Ap|;MCIc~G^_Dq<(_ou*(zXgR;AB)d z<9$X(g)&!*y$dBW*4}OAR7jOFF~>7vWjJE!4ca1foYsj$D?Axfi>`yj3+#x>hFHHn zBPj#3Gq7OQm`u|^Ubds}7u~+2G!Ps(EWiO%$g|#_nKYtwZZQyo;B1K2X(Re9g>etP z?H{J#VE#4`a31?(3OikYo6r$#O>LHip5NK?j5AObXmqFJuCiyfA8OS@1vx3v7a* zOBp*xj8s1_;4bHnF|01QnvQQ7ZRn8;F%s!Or&c7t?-s_E%4xPQ(y*v*!4Gq_i?OL_ z(%Ag9I&ifJ{&gT$@KdV{5o*gmz})&`j8_9Z=)2>@?sZwG_dAOEuXx<-@R5J`?z+Q< zkpn8by;7`k=yM?#opQXPA+xe&?|8yd8lD_|e*cq-__!w*ww~Vn_&?1Fi+(%y@9VjT zn+p&9dZ4!8?pofK#!&?a8hhtli!99DoUq7P+frg55=fj9JU%^s>%=gx1YCZ^C8{~e zF)KgYLG80-^tG_+ikgPhrS1u}D@WsnB0tsh|Cm|F+yqng*?>VRvfIv}-F}d{XoG zgiC_Hbqm@*ef_@&)Wp{1YWsZJbDaLVsE==<(fnw4tqnO(C2 zlI|3X0}J=Lxi3k(5>~i#lFyaO?qy9M{3b=t9UDH@-A#KwB$x8mupx+ z>Q_z64D$2G(vJ?>QILHlwO>oxOO-FTqz}$F7Y*EHUMfG@`{{sxo(LUc&gnDgr}HV} zX+|qAO}JcixG5vhu$b>RcAnkgFCPZ>tU5JcRajD>J0$_j>L5!eLHoSnY7(H}2&X8;3gH?J1SNnlh_rb$Zpg9WMF(7xvEc zcKmsDPHwq9FYsu&V@CZc|8p~rUzDayUvOBB)b{;g=jRi$`Ce=72fg;x@Po5cxbj3$ z@YEyu%F(4o4{LJ+)9>6{l{Nmd6Q(WoUjN3XPwwaLb)Mxl+%w?qfZp5k0!rPdul9`4 zH+!A>=ED1dYt$)^UjCrRO84kQ_q8);ByjF5t60=PCu;ADHm)ngzdg8@NN;|ITf}wuXa&rz3i9 zxtQ%T$=+_wuzJ0qchKPaitEc4xeir2mrY41&A1zy`XI2`sXXSBVX;pJ>4J@hHPzK4 zqOV-|y4Y>;jeA)snM6oZ)rY~Go__qWVSaUdSlxuzQyh)6o-R+|<6i4ZvUkSaRaCeZ zl|M*`80hlyG7sIx>w@4S)HJcu*^v(7q}H0zWn z1{65l^UP1WI^@yy;&&Z(uHWdQKA4!9Pt@d;b^qFP-`vZoF7ompomP!-nZ4oD$G$t9 z`zk zqg!CvmB=8)+>o1tD$&veXo^5~(+ zt_a)n{-Lyek1E2ZooXH{OH5LhJAJGp*qY*UM`j`7g`9wfs`@*^=8l^p{Pe z9K;7RYsan3I1`w&amj%h52xJ{x{m5QyRg@jto1pAyno~G>NeoMU+K2(IFi-RZOOON z`ET$HC+od#)kVB(%s9U+_&s4irqt5>UPIog)U?=L zo_Fr4jZ0o?vuExpIms)Wyiu{>q|@-$>kVe`6$q1ZvYaMoDx(j-K3busA!ojP0bJt& zIY#?Cb`mClaCx!cNRg8#r)gq4B}Z)`^;MdcIzyB-aiUQji3m`fK*-#x String { + static func homePagePromotionFreemiumDBPPostScanEngagementResultSingleBrokerDescription(resultCount: Int) -> String { String(format: "Your free personal info scan found %d records about you on 1 site.", resultCount) } - /// Generates Text for the Freemium DBP Home Page Post Scan Engagement Promotion when records are found on multiple broker sites. - /// Key: "home.page.promotion.freemium.dbp.post.scan.engagement.result.plural.text" + /// Generates Description for the Freemium DBP Home Page Post Scan Engagement Promotion when records are found on multiple broker sites. + /// Key: "home.page.promotion.freemium.dbp.post.scan.engagement.result.plural.description" /// /// - Parameters: /// - resultCount: The number of records found. /// - brokerCount: The number of broker sites where records were found. /// - Returns: A formatted string indicating the number of records found on multiple sites. - static func homePagePromotionFreemiumDBPPostScanEngagementResultPluralText(resultCount: Int, brokerCount: Int) -> String { + static func homePagePromotionFreemiumDBPPostScanEngagementResultPluralDescription(resultCount: Int, brokerCount: Int) -> String { String(format: "Your free personal info scan found %d records about you on %d different sites.", resultCount, brokerCount) } - // Key: "home.page.promotion.freemium.dbp.post.scan.engagement.no.results.text" - // Comment: "Text for the Freemium DBP Home Page Post Scan Engagement Promotion When There Are No Results" - static let homePagePromotionFreemiumDBPPostScanEngagementNoResultsText = "Good news, your free personal info scan didn't find any records about you. We'll keep checking periodically." + // Key: "home.page.promotion.freemium.dbp.post.scan.engagement.no.results.description" + // Comment: "Description for the Freemium DBP Home Page Post Scan Engagement Promotion When There Are No Results" + static let homePagePromotionFreemiumDBPPostScanEngagementNoResultsDescription = "Good news, your free personal info scan didn't find any records about you. We'll keep checking periodically." // Key: "home.page.promotion.freemium.dbp.post.scan.engagement.button.title" // Comment: "Title for the Freemium DBP Home Page Post Scan Engagement Promotion Button" diff --git a/DuckDuckGo/Freemium/DBP/Extensions/PromotionView+FreemiumDBP.swift b/DuckDuckGo/Freemium/DBP/Extensions/PromotionView+FreemiumDBP.swift index d744e99b4f..c9119ed6b1 100644 --- a/DuckDuckGo/Freemium/DBP/Extensions/PromotionView+FreemiumDBP.swift +++ b/DuckDuckGo/Freemium/DBP/Extensions/PromotionView+FreemiumDBP.swift @@ -22,11 +22,18 @@ extension PromotionViewModel { static func freemiumDBPPromotion(proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) -> PromotionViewModel { - let text = UserText.homePagePromotionFreemiumDBPText + let title = UserText.homePagePromotionFreemiumDBPTitle + var description = UserText.homePagePromotionFreemiumDBPDescription + + if #available(macOS 12.0, *) { + description = UserText.homePagePromotionFreemiumDBPDescriptionMarkdown + } + let actionButtonText = UserText.homePagePromotionFreemiumDBPButtonTitle - return PromotionViewModel(image: .radarCheck, - text: text, + return PromotionViewModel(image: .informationRemover128, + title: title, + description: description, proceedButtonText: actionButtonText, proceedAction: proceedAction, closeAction: closeAction) @@ -37,22 +44,22 @@ extension PromotionViewModel { proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) -> PromotionViewModel { - var text = "" + var description = "" switch (resultCount, brokerCount) { case (1, _): - text = UserText.homePagePromotionFreemiumDBPPostScanEngagementResultSingleMatchText + description = UserText.homePagePromotionFreemiumDBPPostScanEngagementResultSingleMatchDescription case (let resultCount, 1): - text = UserText.homePagePromotionFreemiumDBPPostScanEngagementResultSingleBrokerText(resultCount: resultCount) + description = UserText.homePagePromotionFreemiumDBPPostScanEngagementResultSingleBrokerDescription(resultCount: resultCount) default: - text = UserText.homePagePromotionFreemiumDBPPostScanEngagementResultPluralText(resultCount: resultCount, + description = UserText.homePagePromotionFreemiumDBPPostScanEngagementResultPluralDescription(resultCount: resultCount, brokerCount: brokerCount) } let actionButtonText = UserText.homePagePromotionFreemiumDBPPostScanEngagementButtonTitle - return PromotionViewModel(image: .radarCheck, - text: text, + return PromotionViewModel(image: .informationRemover128, + description: description, proceedButtonText: actionButtonText, proceedAction: proceedAction, closeAction: closeAction) @@ -61,11 +68,11 @@ extension PromotionViewModel { static func freemiumDBPPromotionScanEngagementNoResults(proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) -> PromotionViewModel { - let text = UserText.homePagePromotionFreemiumDBPPostScanEngagementNoResultsText + let description = UserText.homePagePromotionFreemiumDBPPostScanEngagementNoResultsDescription let actionButtonText = UserText.homePagePromotionFreemiumDBPPostScanEngagementButtonTitle - return PromotionViewModel(image: .radarCheck, - text: text, + return PromotionViewModel(image: .informationRemover128, + description: description, proceedButtonText: actionButtonText, proceedAction: proceedAction, closeAction: closeAction) diff --git a/DuckDuckGo/HomePage/Model/PromotionViewModel.swift b/DuckDuckGo/HomePage/Model/PromotionViewModel.swift index 069ec55a92..b0f57261cb 100644 --- a/DuckDuckGo/HomePage/Model/PromotionViewModel.swift +++ b/DuckDuckGo/HomePage/Model/PromotionViewModel.swift @@ -24,14 +24,16 @@ extension HomePage.Models { final class PromotionViewModel: ObservableObject { let image: ImageResource - let text: String + let title: String? + let description: String let proceedButtonText: String let proceedAction: () -> Void let closeAction: () -> Void - init(image: ImageResource, text: String, proceedButtonText: String, proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) { + init(image: ImageResource, title: String? = nil, description: String, proceedButtonText: String, proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) { self.image = image - self.text = text + self.title = title + self.description = description self.proceedButtonText = proceedButtonText self.proceedAction = proceedAction self.closeAction = closeAction diff --git a/DuckDuckGo/HomePage/View/PromotionView.swift b/DuckDuckGo/HomePage/View/PromotionView.swift index 1a6bec0437..0bf4d56f41 100644 --- a/DuckDuckGo/HomePage/View/PromotionView.swift +++ b/DuckDuckGo/HomePage/View/PromotionView.swift @@ -41,8 +41,7 @@ extension HomePage.Views { HStack(spacing: 8) { image - text - .padding(.leading, 0) + textContent Spacer(minLength: 4) @@ -85,14 +84,37 @@ extension HomePage.Views { } } - private var text: some View { - Text(viewModel.text) + private var textContent: some View { + VStack(alignment: .leading, spacing: viewModel.title == nil ? 0 : 8) { + title + description.foregroundColor(Color(.greyText)) + } + } + + private var title: some View { + Group { + if let title = viewModel.title { + Text(verbatim: title) + .font(.system(size: 13).bold()) + } else { + EmptyView() + } + } + } + + @ViewBuilder + private var description: some View { + if #available(macOS 12.0, *), let attributed = try? AttributedString(markdown: viewModel.description) { + Text(attributed) + } else { + Text(verbatim: viewModel.description) + } } private var button: some View { Group { Button(action: viewModel.proceedAction) { - Text(viewModel.proceedButtonText) + Text(verbatim: viewModel.proceedButtonText) } .controlSize(.large) } diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index ad91e2e3be..ed0e2f1c1d 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -44025,6 +44025,9 @@ } } } + }, + "Personal Information Removal" : { + }, "phishing-detection.enabled.checkbox" : { "comment" : "Checkbox that enables or disables the phishing detection feature in the browser", diff --git a/UnitTests/Freemium/DBP/FreemiumDBPPromotionViewCoordinatorTests.swift b/UnitTests/Freemium/DBP/FreemiumDBPPromotionViewCoordinatorTests.swift index 83cb849ee8..bb5329ba78 100644 --- a/UnitTests/Freemium/DBP/FreemiumDBPPromotionViewCoordinatorTests.swift +++ b/UnitTests/Freemium/DBP/FreemiumDBPPromotionViewCoordinatorTests.swift @@ -185,7 +185,7 @@ final class FreemiumDBPPromotionViewCoordinatorTests: XCTestCase { let viewModel = sut.viewModel // Then - XCTAssertEqual(viewModel.text, UserText.homePagePromotionFreemiumDBPPostScanEngagementResultPluralText(resultCount: 5, brokerCount: 2)) + XCTAssertEqual(viewModel.description, UserText.homePagePromotionFreemiumDBPPostScanEngagementResultPluralDescription(resultCount: 5, brokerCount: 2)) } @MainActor @@ -197,7 +197,7 @@ final class FreemiumDBPPromotionViewCoordinatorTests: XCTestCase { let viewModel = sut.viewModel // Then - XCTAssertEqual(viewModel.text, UserText.homePagePromotionFreemiumDBPText) + XCTAssertEqual(viewModel.description, UserText.homePagePromotionFreemiumDBPDescriptionMarkdown) } func testNotificationObservation_updatesPromotionVisibility() { From 0f911494c10e14572e2ad8ff7cc61b868a97c3cb Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Tue, 5 Nov 2024 17:39:26 +0100 Subject: [PATCH 04/12] Update C-S-S to 6.29.0 (#3515) Task/Issue URL: https://app.asana.com/0/1201048563534612/1208699974934565/f Description: This update provides new API for macOS New Tab Page rework. Current production version remains unchanged. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 731efbd4e7..5228a89b6b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14778,7 +14778,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 203.1.0; + version = 203.3.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6c7c3d5617..d8312e4987 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" : "19f1e5c945aa92562ad2d087e8d6c99801edf656", - "version" : "203.1.0" + "revision" : "64a5d8d1e19951fe397305a14e521713fb0eaa49", + "version" : "203.3.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "48fee2508995d4ac02d18b3d55424adedcb4ce4f", - "version" : "6.28.0" + "revision" : "6cab7bdb584653a5dc007cc1ae827ec41c5a91bc", + "version" : "6.29.0" } }, { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 33cc4173f6..93f0c75dfb 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.3.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), .package(path: "../Freemium"), diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 4780108c7d..1ba32a268d 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "VPNAppLauncher", targets: ["VPNAppLauncher"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.3.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), .package(path: "../UDSHelper"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 4948ae40a6..41c6b7dba2 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.3.0"), .package(path: "../SwiftUIExtensions") ], targets: [ From 09bfe14abfad617463bf91408b1119171c8b7d14 Mon Sep 17 00:00:00 2001 From: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:57:18 +0100 Subject: [PATCH 05/12] Hiding of tab previews for pinned tabs fixed (#3513) Task/Issue URL: https://app.asana.com/0/1148564399326804/1208347387296425/f **Description**: Tab preview fixed --- DuckDuckGo/Common/Logger+Multiple.swift | 1 + DuckDuckGo/MainWindow/MainViewController.swift | 1 + .../TabPreview/TabPreviewWindowController.swift | 13 +++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Common/Logger+Multiple.swift b/DuckDuckGo/Common/Logger+Multiple.swift index f29c3e8b7e..8c48febea8 100644 --- a/DuckDuckGo/Common/Logger+Multiple.swift +++ b/DuckDuckGo/Common/Logger+Multiple.swift @@ -28,4 +28,5 @@ extension Logger { static var tabSnapshots = { Logger(subsystem: "Tab Snapshots", category: "") }() static var tabLazyLoading = { Logger(subsystem: "Lazy Loading", category: "") }() static var updates = { Logger(subsystem: "Updates", category: "") }() + static var tabPreview = { Logger(subsystem: "Tab Preview", category: "") }() } diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 997c390578..ef6c352471 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -215,6 +215,7 @@ final class MainViewController: NSViewController { func windowDidResignKey() { browserTabViewController.windowDidResignKey() + tabBarViewController.hideTabPreview() } func showBookmarkPromptIfNeeded() { diff --git a/DuckDuckGo/TabPreview/TabPreviewWindowController.swift b/DuckDuckGo/TabPreview/TabPreviewWindowController.swift index b6df9be63d..0a240918fe 100644 --- a/DuckDuckGo/TabPreview/TabPreviewWindowController.swift +++ b/DuckDuckGo/TabPreview/TabPreviewWindowController.swift @@ -73,7 +73,11 @@ final class TabPreviewWindowController: NSWindowController { } func show(parentWindow: NSWindow, topLeftPointInWindow: CGPoint) { + Logger.tabPreview.log("Showing tab preview") + func presentPreview(tabPreviewWindow: NSWindow) { + Logger.tabPreview.log("Presenting tab preview") + parentWindow.addChildWindow(tabPreviewWindow, ordered: .above) self.layout(topLeftPoint: parentWindow.convertPoint(toScreen: topLeftPointInWindow)) } @@ -83,7 +87,7 @@ final class TabPreviewWindowController: NSWindowController { guard let childWindows = parentWindow.childWindows, let tabPreviewWindow = self.window else { - Logger.general.error("TabPreviewWindowController: Showing tab preview window failed") + Logger.general.error("Showing tab preview window failed") return } @@ -107,7 +111,11 @@ final class TabPreviewWindowController: NSWindowController { } func hide(allowQuickRedisplay: Bool = false, withDelay delay: Bool = false) { + Logger.tabPreview.log("Hiding tab preview allowQuickRedisplay:\(allowQuickRedisplay) delay:\(delay)") + func removePreview(allowQuickRedisplay: Bool) { + Logger.tabPreview.log("Removing tab preview allowQuickRedisplay:\(allowQuickRedisplay)") + guard let window = window else { lastHideTime = nil return @@ -117,11 +125,12 @@ final class TabPreviewWindowController: NSWindowController { if !allowQuickRedisplay { lastHideTime = nil } + window.orderOut(nil) return } parentWindow.removeChildWindow(window) - (window).orderOut(nil) + window.orderOut(nil) // Record the hide time lastHideTime = allowQuickRedisplay ? Date() : nil From a8de1e07b3b20287570b4043502f9867ce7af22b Mon Sep 17 00:00:00 2001 From: Juan Manuel Pereira Date: Tue, 5 Nov 2024 15:35:41 -0300 Subject: [PATCH 06/12] Fix add to favorites and open in new tabs action not working on manager (#3467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1201048563534612/1208523846260390/f Tech Design URL: CC: **Description**: Fix add to favorites and open in new tabs action not working on the bookmark manager when selecting multiple bookmarks. **Steps to test this PR**: 1. Open Bookmarks Manager 2. Select multiple bookmarks 3. Right tap, select `Open in New Tabs` 4. All the bookmarks should be opened in new tabs 5. Select the bookmarks again (they should not be favorites), right-tap, and select `Add to favorites` 6. The bookmarks should be added to favorites **Definition of Done**: * [x] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? — ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- .../Bookmarks/Extensions/Bookmarks+Tab.swift | 7 +++++ .../Services/BookmarksContextMenu.swift | 30 ++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift b/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift index 78c60f30ea..689cdc742e 100644 --- a/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift +++ b/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift @@ -28,6 +28,13 @@ extension Tab { } } + @MainActor + static func with(contentsOf bookmarks: [Bookmark], burnerMode: BurnerMode) -> [Tab] { + bookmarks.compactMap { bookmark -> Tab? in + guard let url = bookmark.urlObject else { return nil } + return Tab(content: .url(url, source: .bookmark), shouldLoadInBackground: true, burnerMode: burnerMode) + } + } } extension TabCollection { diff --git a/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift b/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift index 7fe6569ed6..17032a82e5 100644 --- a/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift +++ b/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift @@ -308,13 +308,17 @@ extension BookmarksContextMenu: BookmarkMenuItemSelectors { } @objc func toggleBookmarkAsFavorite(_ sender: NSMenuItem) { - guard let bookmark = sender.representedObject as? Bookmark else { + if let bookmark = sender.representedObject as? Bookmark{ + bookmark.isFavorite.toggle() + bookmarkManager.update(bookmark: bookmark) + } else if let bookmarks = sender.representedObject as? [Bookmark] { + bookmarks.forEach { bookmark in + bookmark.isFavorite.toggle() + bookmarkManager.update(bookmark: bookmark) + } + } else { assertionFailure("Failed to cast menu represented object to Bookmark") - return } - - bookmark.isFavorite.toggle() - bookmarkManager.update(bookmark: bookmark) } @MainActor @@ -424,16 +428,20 @@ extension BookmarksContextMenu: FolderMenuItemSelectors { @MainActor @objc func openInNewTabs(_ sender: NSMenuItem) { - guard let tabCollection = windowControllersManager.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel, - let folder = sender.representedObject as? BookmarkFolder - else { + guard let tabCollection = windowControllersManager.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel else { assertionFailure("Cannot open all in new tabs") return } - let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) - tabCollection.append(tabs: tabs) - PixelExperiment.fireOnboardingBookmarkUsed5to7Pixel() + if let folder = sender.representedObject as? BookmarkFolder { + let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) + tabCollection.append(tabs: tabs) + PixelExperiment.fireOnboardingBookmarkUsed5to7Pixel() + } else if let bookmarks = sender.representedObject as? [Bookmark] { + let tabs = Tab.with(contentsOf: bookmarks, burnerMode: tabCollection.burnerMode) + tabCollection.append(tabs: tabs) + PixelExperiment.fireOnboardingBookmarkUsed5to7Pixel() + } } @MainActor From 6d381c5b1b6f7a3495f2ebd5824d23662a74f866 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Tue, 5 Nov 2024 20:43:15 +0100 Subject: [PATCH 07/12] Update permission usage description strings in Info.plist (#3518) Task/Issue URL: https://app.asana.com/0/0/1208680818394052/f Description: This change updates Info.plist entries with usage description strings for location, camera, microphone and specific folders on disk, as required by the App Store app. --- Configuration/BuildNumber.xcconfig | 2 +- DuckDuckGo/Info.plist | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index ba0dd6986e..12a7f21af4 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 296 +CURRENT_PROJECT_VERSION = 299 diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index a6011aea11..68f7172023 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -521,21 +521,19 @@ NSCameraUsageDescription - Allows you to upload photographs and videos + Lets websites ask permission to use your camera for video calls, recording and uploading videos, or taking and uploading photos. NSHumanReadableCopyright Copyright © 2024 DuckDuckGo. All rights reserved. NSLocationUsageDescription - Allows you to share your geolocation - NSLocationWhenInUseUsageDescription - Allows you to share your location + Lets websites ask permission to use your location to improve search results, provide weather and directions, or show nearby map locations. NSMicrophoneUsageDescription - Allows you to share recordings + Lets websites ask permission to use your microphone for audio and video calls or for recording and uploading audio. NSDownloadsFolderUsageDescription - Allows you to save downloaded files to this folder. + Lets you save files to the Downloads folder on your computer. NSDesktopFolderUsageDescription - Allows you to save downloaded files to this folder. + Lets you save files to the Desktop folder on your computer. NSDocumentsFolderUsageDescription - Allows you to save downloaded files to this folder. + Lets you save files to the Documents folder on your computer. NSPrincipalClass $(INFOPLIST_KEY_NSPrincipalClass) NSSupportsAutomaticTermination From d6f051a955e7fa02e28ec143e5aa363484ddb113 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 5 Nov 2024 20:12:59 -0800 Subject: [PATCH 08/12] Deprecate PixelKit daily pixel suffixes (#3509) Task/Issue URL: https://app.asana.com/0/0/1208695427490034/f Tech Design URL: CC: Description: This PR updates the app to use the new legacyDailyAndCount PixelKit suffix type. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../DataBrokerProtectionPixelsHandler.swift | 6 +- DuckDuckGo/LoginItems/LoginItemsManager.swift | 2 +- .../EventMapping+NetworkProtectionError.swift | 4 +- .../NetworkProtectionTunnelController.swift | 8 +- .../VPNLocation/VPNLocationViewModel.swift | 6 +- ...NetworkProtectionIPCTunnelController.swift | 8 +- .../MacPacketTunnelProvider.swift | 66 ++++++------- .../View/PreferencesRootView.swift | 4 +- .../Preferences/View/PreferencesVPNView.swift | 6 +- .../SmarterEncryption/PrivacyFeatures.swift | 4 +- ...riptionManager+StandardConfiguration.swift | 2 +- DuckDuckGo/Sync/SyncDiagnosisHelper.swift | 2 +- ...workProtectionControllerTabExtension.swift | 2 +- .../SubscriptionAppStoreRestorer.swift | 6 +- .../SubscriptionErrorReporter.swift | 8 +- ...scriptionPagesUseSubscriptionFeature.swift | 14 +-- .../UnifiedFeedbackSender.swift | 6 +- DuckDuckGo/Waitlist/VPNUninstaller.swift | 8 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 2 +- DuckDuckGoVPN/VPNUninstaller.swift | 10 +- .../DataBrokerProtection/Package.swift | 2 +- .../Pixels/DataBrokerProtectionPixels.swift | 6 +- .../NetworkProtectionMac/Package.swift | 2 +- ...ansparentProxyControllerEventHandler.swift | 2 +- ...TransparentProxyProviderEventHandler.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- .../NetworkProtectionPixelEventTests.swift | 94 +++++++++---------- 29 files changed, 145 insertions(+), 145 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5228a89b6b..ed21f5ef87 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14778,7 +14778,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 203.3.0; + version = 204.0.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8312e4987..c1e6a56174 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" : "64a5d8d1e19951fe397305a14e521713fb0eaa49", - "version" : "203.3.0" + "revision" : "14594b6f3f3ddbea65be2818298e2e79305d8a26", + "version" : "204.0.0" } }, { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift index cc4c928d89..223a57ee63 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift @@ -40,7 +40,7 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .legacyDailyAndCount) guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") @@ -189,7 +189,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .legacyDailyAndCount) struct SubscriptionSelection: Decodable { let id: String } @@ -273,7 +273,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch completePurchaseResult { case .success(let purchaseUpdate): Logger.subscription.info("[Purchase] Purchase complete") - PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .legacyDailyAndCount) sendFreemiumSubscriptionPixelIfFreemiumActivated() saveSubscriptionUpgradeTimestampIfFreemiumActivated() PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) @@ -385,7 +385,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await stripePurchaseFlow.completeSubscriptionPurchase() await uiHandler.dismissProgressViewController() - PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .legacyDailyAndCount) sendFreemiumSubscriptionPixelIfFreemiumActivated() saveSubscriptionUpgradeTimestampIfFreemiumActivated() subscriptionSuccessPixelHandler.fireSuccessfulSubscriptionAttributionPixel() @@ -457,7 +457,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // MARK: - UI interactions func showSomethingWentWrongAlert() async { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .legacyDailyAndCount) switch await uiHandler.dismissProgressViewAndShow(alertType: .somethingWentWrong, text: nil) { case .alertFirstButtonReturn: let url = subscriptionManager.url(for: .purchase) @@ -478,7 +478,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { authEndpointService: subscriptionManager.authEndpointService) let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase() switch result { - case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) + case .success: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .legacyDailyAndCount) case .failure: break } Task { @MainActor in @@ -529,7 +529,7 @@ extension SubscriptionPagesUseSubscriptionFeature: SubscriptionAccessActionHandl func subscriptionAccessActionHandleAction(event: SubscriptionAccessActionHandlingEvent) { switch event { case .activateAddEmailClick: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .legacyDailyAndCount) default: break } } diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift index 79f86b8f68..98ba6100bc 100644 --- a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift @@ -111,7 +111,7 @@ struct DefaultFeedbackSender: UnifiedFeedbackSender { } func sendFormShowPixel() { - PixelKit.fire(GeneralPixel.pproFeedbackFormShow, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.pproFeedbackFormShow, frequency: .legacyDailyAndCount) } func sendSubmitScreenShowPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) { @@ -119,7 +119,7 @@ struct DefaultFeedbackSender: UnifiedFeedbackSender { reportType: ReportType.from(reportType), category: Category.from(category), subcategory: Subcategory.from(subcategory)), - frequency: .dailyAndCount) + frequency: .legacyDailyAndCount) } func sendSubmitScreenFAQClickPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) { @@ -127,6 +127,6 @@ struct DefaultFeedbackSender: UnifiedFeedbackSender { reportType: ReportType.from(reportType), category: Category.from(category), subcategory: Subcategory.from(subcategory)), - frequency: .dailyAndCount) + frequency: .legacyDailyAndCount) } } diff --git a/DuckDuckGo/Waitlist/VPNUninstaller.swift b/DuckDuckGo/Waitlist/VPNUninstaller.swift index d164cf21b3..084f31e106 100644 --- a/DuckDuckGo/Waitlist/VPNUninstaller.swift +++ b/DuckDuckGo/Waitlist/VPNUninstaller.swift @@ -176,15 +176,15 @@ final class VPNUninstaller: VPNUninstalling { return } - pixelKit?.fire(IPCUninstallAttempt.begin, frequency: .dailyAndCount) + pixelKit?.fire(IPCUninstallAttempt.begin, frequency: .legacyDailyAndCount) do { try await executeUninstallSequence(removeSystemExtension: removeSystemExtension) - pixelKit?.fire(IPCUninstallAttempt.success, frequency: .dailyAndCount) + pixelKit?.fire(IPCUninstallAttempt.success, frequency: .legacyDailyAndCount) } catch UninstallError.cancelled(let reason) { - pixelKit?.fire(IPCUninstallAttempt.cancelled(reason), frequency: .dailyAndCount) + pixelKit?.fire(IPCUninstallAttempt.cancelled(reason), frequency: .legacyDailyAndCount) } catch { - pixelKit?.fire(IPCUninstallAttempt.failure(error), frequency: .dailyAndCount) + pixelKit?.fire(IPCUninstallAttempt.failure(error), frequency: .legacyDailyAndCount) } } diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 40ecf9650b..b183d84e51 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -468,6 +468,6 @@ extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), - frequency: .dailyAndCount) + frequency: .legacyDailyAndCount) } } diff --git a/DuckDuckGoVPN/VPNUninstaller.swift b/DuckDuckGoVPN/VPNUninstaller.swift index 1ae6a706cb..b6efdda308 100644 --- a/DuckDuckGoVPN/VPNUninstaller.swift +++ b/DuckDuckGoVPN/VPNUninstaller.swift @@ -48,7 +48,7 @@ final class VPNUninstaller: VPNUninstalling { } func uninstall(includingSystemExtension: Bool) async throws { - pixelKit?.fire(VPNUninstallAttempt.begin, frequency: .dailyAndCount) + pixelKit?.fire(VPNUninstallAttempt.begin, frequency: .legacyDailyAndCount) do { try await removeSystemExtension() @@ -59,15 +59,15 @@ final class VPNUninstaller: VPNUninstalling { } defaults.networkProtectionShouldShowVPNUninstalledMessage = true - pixelKit?.fire(VPNUninstallAttempt.success, frequency: .dailyAndCount) + pixelKit?.fire(VPNUninstallAttempt.success, frequency: .legacyDailyAndCount) } catch { switch error { case OSSystemExtensionError.requestCanceled: - pixelKit?.fire(VPNUninstallAttempt.cancelled(.sysexInstallationCancelled), frequency: .dailyAndCount) + pixelKit?.fire(VPNUninstallAttempt.cancelled(.sysexInstallationCancelled), frequency: .legacyDailyAndCount) case OSSystemExtensionError.authorizationRequired: - pixelKit?.fire(VPNUninstallAttempt.cancelled(.sysexInstallationRequiresAuthorization), frequency: .dailyAndCount) + pixelKit?.fire(VPNUninstallAttempt.cancelled(.sysexInstallationRequiresAuthorization), frequency: .legacyDailyAndCount) default: - pixelKit?.fire(VPNUninstallAttempt.failure(error), frequency: .dailyAndCount) + pixelKit?.fire(VPNUninstallAttempt.failure(error), frequency: .legacyDailyAndCount) } throw error diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 93f0c75dfb..1bdbfe2119 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "203.3.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "204.0.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), .package(path: "../Freemium"), diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift index f5fdcd9ce4..44f111e928 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift @@ -550,7 +550,7 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Date: Wed, 6 Nov 2024 07:29:34 +0000 Subject: [PATCH 09/12] Bump version to 1.113.0 (300) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 12a7f21af4..03d9d44d8b 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 299 +CURRENT_PROJECT_VERSION = 300 From f734693ba7de909e3318a84bef099ef554ffda93 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Wed, 6 Nov 2024 10:36:06 +0100 Subject: [PATCH 10/12] Add menu option to set as default browser (#3508) Task/Issue URL: https://app.asana.com/0/0/1208628594863072/f Description: This change adds "Set DuckDuckGo As Default Browser" menu option to More Options Menu and to Main Menu. Menu option is hidden when the browser is set as default. --- DuckDuckGo/Application/AppDelegate.swift | 2 + .../Contents.json | 16 +++++ .../Set-as-Default-16D.svg | 4 ++ DuckDuckGo/Common/Localizables/UserText.swift | 1 + DuckDuckGo/Localizable.xcstrings | 60 +++++++++++++++++++ DuckDuckGo/Menus/MainMenu.swift | 10 ++++ DuckDuckGo/Menus/MainMenuActions.swift | 6 ++ .../NavigationBar/View/MoreOptionsMenu.swift | 20 ++++++- DuckDuckGo/Statistics/GeneralPixel.swift | 8 +++ UnitTests/Menus/MainMenuTests.swift | 44 ++++++++++++++ UnitTests/Menus/MoreOptionsMenuTests.swift | 25 ++++++++ 11 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Set-as-Default-16D.svg diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 8bce0390f3..14ae61f423 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -471,6 +471,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { freemiumDBPScanResultPolling = DefaultFreemiumDBPScanResultPolling(dataManager: DataBrokerProtectionManager.shared.dataManager, freemiumDBPUserStateManager: freemiumDBPUserStateManager) freemiumDBPScanResultPolling?.startPollingOrObserving() + + PixelKit.fire(NonStandardEvent(GeneralPixel.launch(isDefault: DefaultBrowserPreferences().isDefault))) } private func fireFailedCompilationsPixelIfNeeded() { diff --git a/DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Contents.json new file mode 100644 index 0000000000..6ad6fd43b8 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "Set-as-Default-16D.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Set-as-Default-16D.svg b/DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Set-as-Default-16D.svg new file mode 100644 index 0000000000..b7cd82425a --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/DefaultBrowserMenuItem.imageset/Set-as-Default-16D.svg @@ -0,0 +1,4 @@ + + + + diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 86270c6b0b..dc024a5a30 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -710,6 +710,7 @@ struct UserText { static let aboutDuckDuckGo = NSLocalizedString("preferences.about.about-duckduckgo", value: "About DuckDuckGo", comment: "About screen") static let duckduckgoTagline = NSLocalizedString("preferences.about.duckduckgo-tagline", value: "Your protection, our priority.", comment: "About screen") + static let setAsDefaultBrowser = NSLocalizedString("preferences.set-as-default", value: "Set DuckDuckGo As Default Browser", comment: "Menu option to set the browser as default") static let aboutUnsupportedDeviceInfo1 = NSLocalizedString("preferences.about.unsupported-device-info1", value: "DuckDuckGo is no longer providing browser updates for your version of macOS.", comment: "This string represents a message informing the user that DuckDuckGo is no longer providing browser updates for their version of macOS") static func aboutUnsupportedDeviceInfo2(version: String) -> String { let localized = NSLocalizedString("preferences.about.unsupported-device-info2", value: "Please update to macOS %@ or later to use the most recent version of DuckDuckGo. You can also keep using your current version of the browser, but it will not receive further updates.", comment: "Copy in section that tells the user to update their macOS version since their current version is unsupported") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index ed0e2f1c1d..7e7a19cd25 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -53585,6 +53585,66 @@ } } }, + "preferences.set-as-default" : { + "comment" : "Menu option to set the browser as default", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo als Standard-Browser festlegen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Set DuckDuckGo As Default Browser" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Establece DuckDuckGo como tu navegador predeterminado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Définir DuckDuckGo comme navigateur par défaut" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Imposta DuckDuckGo come browser predefinito" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo instellen als standaardbrowser" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ustaw DuckDuckGo jako domyślną przeglądarkę" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definir o DuckDuckGo como navegador predefinido" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сделать DuckDuckGo браузером по умолчанию" + } + } + } + }, "preferences.shortcuts" : { "comment" : "Name of the preferences section related to shortcuts", "extractionState" : "extracted_with_value", diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 4b89758f67..ce940baf61 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -101,9 +101,12 @@ final class MainMenu: NSMenu { let helpMenu = NSMenu(title: UserText.mainMenuHelp) let aboutMenuItem = NSMenuItem(title: UserText.about, action: #selector(AppDelegate.showAbout)) + let setAsDefaultMenuItem = NSMenuItem(title: UserText.setAsDefaultBrowser + "…", action: #selector(AppDelegate.setAsDefault)) let releaseNotesMenuItem = NSMenuItem(title: UserText.releaseNotesMenuItem, action: #selector(AppDelegate.showReleaseNotes)) let whatIsNewMenuItem = NSMenuItem(title: UserText.whatsNewMenuItem, action: #selector(AppDelegate.showWhatIsNew)) let sendFeedbackMenuItem = NSMenuItem(title: UserText.sendFeedback, action: #selector(AppDelegate.openFeedback)) + + private let defaultBrowserPreferences: DefaultBrowserPreferences private let aiChatMenuConfig: AIChatMenuVisibilityConfigurable // MARK: - Initialization @@ -112,8 +115,10 @@ final class MainMenu: NSMenu { init(featureFlagger: FeatureFlagger, bookmarkManager: BookmarkManager, faviconManager: FaviconManagement, + defaultBrowserPreferences: DefaultBrowserPreferences = .shared, aiChatMenuConfig: AIChatMenuVisibilityConfigurable) { + self.defaultBrowserPreferences = defaultBrowserPreferences self.aiChatMenuConfig = aiChatMenuConfig super.init(title: UserText.duckDuckGo) @@ -142,6 +147,7 @@ final class MainMenu: NSMenu { NSMenuItem.separator() preferencesMenuItem + setAsDefaultMenuItem NSMenuItem.separator() @@ -163,6 +169,7 @@ final class MainMenu: NSMenu { } } + @MainActor func buildFileMenu() -> NSMenuItem { NSMenuItem(title: UserText.mainMenuFile) { newTabMenuItem @@ -300,6 +307,7 @@ final class MainMenu: NSMenu { } } + @MainActor func buildHistoryMenu() -> NSMenuItem { NSMenuItem(title: UserText.mainMenuHistory) .submenu(historyMenu) @@ -426,6 +434,8 @@ final class MainMenu: NSMenu { override func update() { super.update() + setAsDefaultMenuItem.isHidden = defaultBrowserPreferences.isDefault + // To be safe, hide the NetP shortcut menu item by default. toggleNetworkProtectionShortcutMenuItem.isHidden = true toggleAIChatShortcutMenuItem.isHidden = true diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 41b56eccbe..b97665c7ec 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -159,6 +159,12 @@ extension AppDelegate { WindowControllersManager.shared.showTab(with: .settings(pane: .about)) } + @MainActor + @objc func setAsDefault(_ sender: Any?) { + PixelKit.fire(GeneralPixel.defaultRequestedFromMainMenu) + DefaultBrowserPreferences.shared.becomeDefault() + } + @MainActor @objc func showReleaseNotes(_ sender: Any?) { WindowControllersManager.shared.showTab(with: .releaseNotes) diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index c0625778b8..d419d26dc7 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -65,6 +65,7 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { private let freemiumDBPFeature: FreemiumDBPFeature private let freemiumDBPPresenter: FreemiumDBPPresenter private let appearancePreferences: AppearancePreferences + private let defaultBrowserPreferences: DefaultBrowserPreferences private let notificationCenter: NotificationCenter @@ -92,6 +93,7 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { freemiumDBPFeature: FreemiumDBPFeature, freemiumDBPPresenter: FreemiumDBPPresenter = DefaultFreemiumDBPPresenter(), appearancePreferences: AppearancePreferences = .shared, + defaultBrowserPreferences: DefaultBrowserPreferences = .shared, notificationCenter: NotificationCenter = .default, freemiumDBPExperimentPixelHandler: EventMapping = FreemiumDBPExperimentPixelHandler(), aiChatMenuConfiguration: AIChatMenuVisibilityConfigurable = AIChatMenuConfiguration()) { @@ -107,6 +109,7 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { self.freemiumDBPFeature = freemiumDBPFeature self.freemiumDBPPresenter = freemiumDBPPresenter self.appearancePreferences = appearancePreferences + self.defaultBrowserPreferences = defaultBrowserPreferences self.notificationCenter = notificationCenter self.freemiumDBPExperimentPixelHandler = freemiumDBPExperimentPixelHandler self.aiChatMenuConfiguration = aiChatMenuConfiguration @@ -145,10 +148,17 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { accountManager: accountManager) addItem(feedbackMenuItem) - addItem(NSMenuItem.separator()) - #endif // FEEDBACK + if !defaultBrowserPreferences.isDefault { + let setAsDefaultMenuItem = NSMenuItem(title: UserText.setAsDefaultBrowser, action: #selector(setAsDefault(_:))) + .targetting(self) + .withImage(.defaultBrowserMenuItem) + addItem(setAsDefaultMenuItem) + } + + addItem(NSMenuItem.separator()) + addWindowItems() zoomMenuItem.submenu = ZoomSubMenu(targetting: self, tabCollectionViewModel: tabCollectionViewModel) @@ -186,6 +196,12 @@ final class MoreOptionsMenu: NSMenu, NSMenuDelegate { actionDelegate?.optionsButtonMenuRequestedNetworkProtectionPopover(self) } + @MainActor + @objc func setAsDefault(_ sender: NSMenuItem) { + PixelKit.fire(GeneralPixel.defaultRequestedFromMoreOptionsMenu) + defaultBrowserPreferences.becomeDefault() + } + @MainActor @objc func newTab(_ sender: NSMenuItem) { tabCollectionViewModel.appendNewTab() diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 6dfef319bf..21bf3f4687 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -28,6 +28,7 @@ enum GeneralPixel: PixelKitEventV2 { case crashOnCrashHandlersSetUp case compileRulesWait(onboardingShown: OnboardingShown, waitTime: CompileRulesWaitTime, result: WaitResult) case launchInitial(cohort: String) + case launch(isDefault: Bool) case serp(cohort: String?) case serpInitial(cohort: String) @@ -212,6 +213,8 @@ enum GeneralPixel: PixelKitEventV2 { case defaultRequestedFromHomepageSetupView case defaultRequestedFromSettings case defaultRequestedFromOnboarding + case defaultRequestedFromMainMenu + case defaultRequestedFromMoreOptionsMenu // Adding to the Dock case addToDockOnboardingStepPresented @@ -451,6 +454,9 @@ enum GeneralPixel: PixelKitEventV2 { case .compileRulesWait(onboardingShown: let onboardingShown, waitTime: let waitTime, result: let result): return "m_mac_cbr-wait_\(onboardingShown)_\(waitTime)_\(result)" + case .launch(let isDefault): + return isDefault ? "ml_mac_app-launch_as-default" : "ml_mac_app-launch_as-nondefault" + case .serp: return "m_mac_navigation_search" @@ -755,6 +761,8 @@ enum GeneralPixel: PixelKitEventV2 { case .defaultRequestedFromHomepageSetupView: return "m_mac_default_requested_from_homepage_setup_view" case .defaultRequestedFromSettings: return "m_mac_default_requested_from_settings" case .defaultRequestedFromOnboarding: return "m_mac_default_requested_from_onboarding" + case .defaultRequestedFromMainMenu: return "m_mac_default_requested_from_main_menu" + case .defaultRequestedFromMoreOptionsMenu: return "m_mac_default_requested_from_more_options_menu" case .addToDockOnboardingStepPresented: return "m_mac_add_to_dock_onboarding_step_presented" case .userAddedToDockDuringOnboarding: return "m_mac_user_added_to_dock_during_onboarding" diff --git a/UnitTests/Menus/MainMenuTests.swift b/UnitTests/Menus/MainMenuTests.swift index 3765302ff0..56042fbf87 100644 --- a/UnitTests/Menus/MainMenuTests.swift +++ b/UnitTests/Menus/MainMenuTests.swift @@ -89,6 +89,50 @@ class MainMenuTests: XCTestCase { XCTAssertEqual(manager.reopenLastClosedMenuItem?.keyEquivalentModifierMask, ReopenMenuItemKeyEquivalentManager.Const.modifierMask) } + // MARK: - Default Browser Action + + @MainActor + func testWhenBrowserIsDefaultThenSetAsDefaultBrowserMenuItemIsHidden() throws { + let defaultBrowserProvider = DefaultBrowserProviderMock() + defaultBrowserProvider.isDefault = true + + let sut = MainMenu( + featureFlagger: DummyFeatureFlagger(), + bookmarkManager: MockBookmarkManager(), + faviconManager: FaviconManagerMock(), + defaultBrowserPreferences: .init(defaultBrowserProvider: defaultBrowserProvider), + aiChatMenuConfig: DummyAIChatConfig() + ) + + sut.update() + + let duckDuckGoMenu = try XCTUnwrap(sut.items.first?.submenu) + + XCTAssertEqual(duckDuckGoMenu.items[3].title, UserText.setAsDefaultBrowser + "…") + XCTAssertTrue(duckDuckGoMenu.items[3].isHidden) + } + + @MainActor + func testWhenBrowserIsNotDefaultThenSetAsDefaultBrowserMenuItemIsShown() throws { + let defaultBrowserProvider = DefaultBrowserProviderMock() + defaultBrowserProvider.isDefault = false + + let sut = MainMenu( + featureFlagger: DummyFeatureFlagger(), + bookmarkManager: MockBookmarkManager(), + faviconManager: FaviconManagerMock(), + defaultBrowserPreferences: .init(defaultBrowserProvider: defaultBrowserProvider), + aiChatMenuConfig: DummyAIChatConfig() + ) + + sut.update() + + let duckDuckGoMenu = try XCTUnwrap(sut.items.first?.submenu) + + XCTAssertEqual(duckDuckGoMenu.items[3].title, UserText.setAsDefaultBrowser + "…") + XCTAssertFalse(duckDuckGoMenu.items[3].isHidden) + } + // MARK: - Bookmarks @MainActor diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index 6b61ac2d29..f622fe79cc 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -34,6 +34,7 @@ final class MoreOptionsMenuTests: XCTestCase { var networkProtectionVisibilityMock: NetworkProtectionVisibilityMock! var capturingActionDelegate: CapturingOptionsButtonMenuDelegate! var internalUserDecider: InternalUserDeciderMock! + var defaultBrowserProvider: DefaultBrowserProviderMock! var storePurchaseManager: StorePurchaseManager! @@ -55,6 +56,8 @@ final class MoreOptionsMenuTests: XCTestCase { networkProtectionVisibilityMock = NetworkProtectionVisibilityMock(isInstalled: false, visible: false) capturingActionDelegate = CapturingOptionsButtonMenuDelegate() internalUserDecider = InternalUserDeciderMock() + defaultBrowserProvider = DefaultBrowserProviderMock() + defaultBrowserProvider.isDefault = true storePurchaseManager = StorePurchaseManagerMock() @@ -97,6 +100,7 @@ final class MoreOptionsMenuTests: XCTestCase { freemiumDBPUserStateManager: mockFreemiumDBPUserStateManager, freemiumDBPFeature: mockFreemiumDBPFeature, freemiumDBPPresenter: mockFreemiumDBPPresenter, + defaultBrowserPreferences: .init(defaultBrowserProvider: defaultBrowserProvider), notificationCenter: mockNotificationCenter, freemiumDBPExperimentPixelHandler: mockPixelHandler) @@ -294,6 +298,27 @@ final class MoreOptionsMenuTests: XCTestCase { XCTAssertTrue(capturingActionDelegate.optionsButtonMenuRequestedBookmarkAllOpenTabsCalled) } + // MARK: - Default Browser Action + + @MainActor + func testWhenBrowserIsDefaultThenSetAsDefaultBrowserMenuItemIsHidden() { + defaultBrowserProvider.isDefault = true + + setupMoreOptionsMenu() + moreOptionsMenu.update() + + XCTAssertNotEqual(moreOptionsMenu.items[1].title, UserText.setAsDefaultBrowser) + } + + @MainActor + func testWhenBrowserIsNotDefaultThenSetAsDefaultBrowserMenuItemIsShown() { + defaultBrowserProvider.isDefault = false + + setupMoreOptionsMenu() + moreOptionsMenu.update() + + XCTAssertEqual(moreOptionsMenu.items[1].title, UserText.setAsDefaultBrowser) + } } final class NetworkProtectionVisibilityMock: VPNFeatureGatekeeper { From a2f2769922e75988caffbe034a1f9d5dfb83de2b Mon Sep 17 00:00:00 2001 From: Juan Manuel Pereira Date: Wed, 6 Nov 2024 09:26:55 -0300 Subject: [PATCH 11/12] Fix UI tests failing (#3517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1201037661562251/1208683961540871/f Tech Design URL: CC: **Description**: Fixes onboarding testing failing and pinned tabs tests failing on macOS 13. ✅ https://github.com/duckduckgo/macos-browser/actions/runs/11689899520 **Definition of Done**: * [x] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- UITests/OnboardingUITests.swift | 7 ++++--- UITests/PinnedTabsTests.swift | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/UITests/OnboardingUITests.swift b/UITests/OnboardingUITests.swift index 9afa760a0a..5898096fb8 100644 --- a/UITests/OnboardingUITests.swift +++ b/UITests/OnboardingUITests.swift @@ -101,9 +101,10 @@ final class OnboardingUITests: XCTestCase { startBrowsingButton.click() // AfterOnboarding - let duckduckgoPrivacySimplifiedWindow = app.windows["DuckDuckGo — Your protection, our priority."] - XCTAssertTrue(duckduckgoPrivacySimplifiedWindow.webViews["DuckDuckGo — Your protection, our priority."].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(duckduckgoPrivacySimplifiedWindow.buttons["NavigationBarViewController.optionsButton"].isEnabled) + let ddgLogo = app.windows.webViews.groups.containing(.image, identifier: "DuckDuckGo Logo").element + XCTAssertTrue(ddgLogo.waitForExistence(timeout: UITests.Timeouts.elementExistence)) + let homePageSubTitle = app.windows.webViews.groups.containing(.staticText, identifier: "Your protection, our priority.").element + XCTAssertTrue(homePageSubTitle.waitForExistence(timeout: UITests.Timeouts.elementExistence)) } func resetApplicationData() throws { diff --git a/UITests/PinnedTabsTests.swift b/UITests/PinnedTabsTests.swift index a3fe7297f9..252df1b8cf 100644 --- a/UITests/PinnedTabsTests.swift +++ b/UITests/PinnedTabsTests.swift @@ -157,8 +157,8 @@ class PinnedTabsTests: XCTestCase { private func assertPinnedTabsRestoredState() { let newApp = XCUIApplication() newApp.launch() - - sleep(2) + newApp.typeKey("n", modifierFlags: .command) + sleep(10) // This was increased from two to ten, because slower VMs needed more time to re-launch the app. /// Goes to Page #2 to check the state newApp.typeKey("[", modifierFlags: [.command, .shift]) From b5d8e523068af9d903f166755d276d2c34f4b2cf Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 6 Nov 2024 11:25:56 -0800 Subject: [PATCH 12/12] Update Ruby to 3.3.4 (#3519) Task/Issue URL: https://app.asana.com/0/414235014887631/1208702695612235/f Tech Design URL: CC: Description: This PR updates ruby-version to 3.3.4, replacing the deprecated 3.0 version of Ruby that we were using previously. --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index b0f2dcb32f..a0891f563f 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.4 +3.3.4