-
Notifications
You must be signed in to change notification settings - Fork 35
Show CPM Stats on new tab page #4 - Feature discovery dialog #2697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 111 commits
Commits
Show all changes
138 commits
Select commit
Hold shift + click to select a range
6bc640a
Localizable.xcstrings update
miasma13 9059a7c
Add newTabPageAutoconsentStats feature flag
miasma13 3d83895
Add AutoconsentTabExtension
miasma13 a84fddf
Initial implementation of the AutoconsentStats
miasma13 58a8585
Add publisher for AutoconsentManagedEvent
miasma13 4911cf2
Merge branch 'main' into michal/cpm-on-ntp-1-stats-collection
miasma13 e3b3aff
Don't introduce new type but rather reuse one
miasma13 1a90c43
Remove comment
miasma13 ba78000
Pass autoconsentStats into AutoconsentTabExtension
miasma13 a307e78
Record autoconsent action
miasma13 891d75d
Add new pixel
miasma13 aa4fe17
Add AutoconsentStatsTests
miasma13 7368272
Add missing implementation
miasma13 93a16fa
Merge branch 'main' into michal/cpm-on-ntp-1-stats-collection
miasma13 494bb02
Fix
miasma13 78ed612
Fire daily pixel
miasma13 240d218
Fix naming and tests
miasma13 a4b6cc8
Clear autoconsent stats on fire button
miasma13 b6e3383
Fix formatting
miasma13 06f7783
Update SharedPackages/BrowserServicesKit/Sources/AutoconsentStats/Aut…
miasma13 38ef0e2
Add feature flag gating logic
miasma13 48e3c86
Update pixel definitions
miasma13 46eec3c
Remove whitespace
ayoy 4eecec2
Adjust range bucketing to avoid gaps
miasma13 c2b48e7
Update pixel definitions
miasma13 8c82149
Clean up
miasma13 347fc3b
Merge branch 'main' into michal/cpm-on-ntp-1-stats-collection
miasma13 168d406
Fix pixel definition
miasma13 d339086
Fix tests and lint errors
miasma13 f4c4926
Fix tests
miasma13 db56e70
Fix lint
miasma13 e9f23d2
Add missing framework
miasma13 ba9bf1a
Update pixel trigger
miasma13 fd71dba
Merge branch 'main' into michal/cpm-on-ntp-1-stats-collection
miasma13 2b221ef
Remove unused code
miasma13 b976ab7
Update public API and tests
miasma13 a03cecf
Add proper error handling
miasma13 952c9d8
Fix lint errors
miasma13 87cf995
Add cookiePopupBlocked property to HistoryEntry
miasma13 648acbe
BrowsingHistory changes to support new cookiePopupBlocked atribute in…
miasma13 cc104c0
macOS History changes to support new cookiePopupBlocked atribute in C…
miasma13 8fd72a3
Add cookiePopupBlocked API to HistoryCoordinator
miasma13 656c368
Update cookiePopupBlocked when blocked by CPM
miasma13 1c9d11e
Reset flag on fire button use
miasma13 77b5c9f
Add tests
miasma13 5726382
Fix missing protocol func
miasma13 25599da
Fix post-rebase issues
miasma13 7969743
Pass along the autoconsentStats
miasma13 d17f792
Pass the autoconsent stats value when feature is enabled
miasma13 40e67f7
Add publisher for stats changes
miasma13 9b3d3f7
Refresh NTP stats widget on autoconsent changes
miasma13 0ee091c
Pass cookiePopUpBlocked state for the domain
miasma13 03e4549
Clear autoconsent states only on full burn
miasma13 412557b
To Be Reverted: Temporary cusom CSS build
miasma13 f895048
Pass nil when CPM is disabled
miasma13 55b36be
Expand tests
miasma13 1a90c08
Fix tests
miasma13 7bd1f64
Revert "To Be Reverted: Temporary cusom CSS build"
miasma13 cbc09af
Merge branch 'main' into michal/cpm-on-ntp-2-cookie-popup-blocked
miasma13 7d5e72e
Undo changes
miasma13 8a0bd84
Merge branch 'main' into michal/cpm-on-ntp-2-cookie-popup-blocked
miasma13 7b9d773
Remove double extension registration
miasma13 7378cbf
Lint fixes
miasma13 39f8580
Fix isolation
miasma13 a4a7005
Fix tests
miasma13 30bf68b
Remove tmporary pixel and AutoconsentDailyStats
miasma13 2bef7ce
Revert string change
miasma13 355b0b7
Add debug menu entries
miasma13 50a0fab
Add AutoconsentStatsPopoverCoordinator
miasma13 8ac0bdf
Update to how the popover is presented
miasma13 758eedf
Add check for not being on NTP
miasma13 04e6117
Add check for CPM being enabled
miasma13 40972de
Add check for feature flag
miasma13 26c4f60
Add check for privacy report being enabled
miasma13 87b7f3e
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 bbb4281
Enhance debug menu
miasma13 f6a6f60
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 d5bd8a9
Update popover icon
miasma13 346bb27
Dismiss the popover on a new tab
miasma13 6cdf60e
Temp C-S-S bump from a PR branch
miasma13 0eb7587
Fix serializing ProtectionsData
miasma13 fbde91c
Don't do click in ClickableView just propagate the click further
miasma13 483109a
Make PopoverMessageViewController generic
miasma13 50e8c87
Temp refactoring of popover
miasma13 254f779
Revert "Temp refactoring of popover"
miasma13 7e26452
Revert "Make PopoverMessageViewController generic"
miasma13 440ae68
Add featureDiscovery style for the popover
miasma13 41a8077
Resolve issues with handling click action on the popover view
miasma13 d21365e
Move actions to view model
miasma13 b49da7b
Renaming
miasma13 da4360b
Further reordering
miasma13 2b322e9
Add AutoconsentStatsPopoverPresenter
miasma13 8e29d47
Clean up
miasma13 82a6bb4
Fit the popover
miasma13 8e46f8b
Add new strings into UserText
miasma13 3dc0c8e
Add popover pixels
miasma13 756c77a
Revert "Temp C-S-S bump from a PR branch"
miasma13 7ff2b60
Temp C-S-S bump from draft branch
miasma13 6fa2623
Fix confusion with onClose and onDismiss callbacks
miasma13 5c819b7
Don't clear autoconsent stats on entity burn
miasma13 027c850
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 eb29623
Translations import
miasma13 638d02b
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 d2df467
Upate C-S-S
miasma13 e655e74
Reset C-S-S
miasma13 ea13289
Fix pixel definition triggers
miasma13 8cd3c78
Swift lint fixes
miasma13 e70a066
Add missing criteria for the popover
miasma13 33ddd09
String
miasma13 f9ab61f
Set popover seen flag when dissmissed via new tab
miasma13 b6db454
Fix onDismiss not being called
miasma13 736a3b3
Swift lint fixes
miasma13 3056948
Reset strings translation
miasma13 e587ada
Add translated strings
miasma13 4b23003
Reset unwanted changes
miasma13 705e586
Fix test to updated logic
miasma13 8dc61cf
Temp animation branch of C-S-S
miasma13 7534734
Add new message for requesting widget scrolling
miasma13 64e7efd
Expose scroller via app delegate
miasma13 616113e
Update tests
miasma13 7dbe493
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 4da4e00
Temp C-S-S bump
miasma13 37ed7c9
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 c342521
Add protocols
miasma13 60fd650
Temp C-S-S bump
miasma13 1abacdd
Add tests
miasma13 57214be
Fix swiftlint
miasma13 efef76c
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 692c911
Fix pixel params
miasma13 6fe2700
Adjust the timing
miasma13 dcaf63e
Update C-S-S to 12.4.0
miasma13 1b9b0fc
Fix view modifiers order
miasma13 199e863
Add weak capture
miasma13 75c9179
Add check for the onboarding being completed
miasma13 681e6ed
Add error logging
miasma13 0252c85
Update tests
miasma13 c8f49d8
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 e5c1dad
Merge branch 'main' into michal/cpm-on-ntp-4-dialog
miasma13 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
macOS/DuckDuckGo/Assets.xcassets/Images/Cookies-Blocked-Color-24.imageset/Contents.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "Cookies-Blocked-Color-24.pdf", | ||
| "idiom" : "universal" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
Binary file added
BIN
+13.3 KB
...kGo/Assets.xcassets/Images/Cookies-Blocked-Color-24.imageset/Cookies-Blocked-Color-24.pdf
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
macOS/DuckDuckGo/Autoconsent/AutoconsentStatsPopoverCoordinator.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| // | ||
| // AutoconsentStatsPopoverCoordinator.swift | ||
| // | ||
| // Copyright © 2025 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 AppKit | ||
| import AppKitExtensions | ||
| import AutoconsentStats | ||
| import Persistence | ||
| import Common | ||
| import SwiftUIExtensions | ||
| import FeatureFlags | ||
| import BrowserServicesKit | ||
| import PixelKit | ||
|
|
||
| @MainActor | ||
| final class AutoconsentStatsPopoverCoordinator { | ||
|
|
||
| private let keyValueStore: ThrowingKeyValueStoring | ||
| private let windowControllersManager: WindowControllersManagerProtocol | ||
| private let cookiePopupProtectionPreferences: CookiePopupProtectionPreferences | ||
| private let appearancePreferences: AppearancePreferences | ||
| private let featureFlagger: FeatureFlagger | ||
| private let autoconsentStats: AutoconsentStatsCollecting | ||
| private let presenter: AutoconsentStatsPopoverPresenter | ||
|
|
||
| private enum StorageKey { | ||
| static let blockedCookiesPopoverSeen = "com.duckduckgo.autoconsent.blocked.cookies.popover.seen" | ||
| } | ||
|
|
||
| private enum Constants { | ||
| static let threshold = 5 | ||
| static let minimumDaysSinceInstallation = 2 | ||
| } | ||
|
|
||
| init(autoconsentStats: AutoconsentStatsCollecting, | ||
| keyValueStore: ThrowingKeyValueStoring, | ||
| windowControllersManager: WindowControllersManagerProtocol, | ||
| cookiePopupProtectionPreferences: CookiePopupProtectionPreferences, | ||
| appearancePreferences: AppearancePreferences, | ||
| featureFlagger: FeatureFlagger) { | ||
| self.autoconsentStats = autoconsentStats | ||
| self.keyValueStore = keyValueStore | ||
| self.windowControllersManager = windowControllersManager | ||
| self.cookiePopupProtectionPreferences = cookiePopupProtectionPreferences | ||
| self.appearancePreferences = appearancePreferences | ||
| self.featureFlagger = featureFlagger | ||
| self.presenter = AutoconsentStatsPopoverPresenter( | ||
| autoconsentStats: autoconsentStats, | ||
| windowControllersManager: windowControllersManager | ||
| ) | ||
| } | ||
|
|
||
| func checkAndShowDialogIfNeeded() async { | ||
| guard | ||
| isFeatureFlagEnabled(), | ||
| !presenter.isPopoverBeingPresented(), | ||
| isCPMEnabled(), | ||
| isNotOnNTP(), | ||
| isProtectionsReportEnabledOnNTP(), | ||
| !hasBeenPresented(), | ||
| hasBeenEnoughDaysSinceInstallation(), | ||
| await hasBlockedEnoughCookiePopups() | ||
| else { | ||
| return | ||
| } | ||
|
|
||
| await showDialog() | ||
| } | ||
miasma13 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // MARK: - Dialog Gatekeeping Checks | ||
|
|
||
| private func isFeatureFlagEnabled() -> Bool { | ||
| return featureFlagger.isFeatureOn(FeatureFlag.newTabPageAutoconsentStats) | ||
| } | ||
|
|
||
| private func isCPMEnabled() -> Bool { | ||
| return cookiePopupProtectionPreferences.isAutoconsentEnabled | ||
| } | ||
|
|
||
| private func isNotOnNTP() -> Bool { | ||
| guard let selectedTab = windowControllersManager.selectedTab else { | ||
| return true | ||
| } | ||
| return selectedTab.content != .newtab | ||
| } | ||
|
|
||
| private func isProtectionsReportEnabledOnNTP() -> Bool { | ||
| return appearancePreferences.isProtectionsReportVisible | ||
| } | ||
|
|
||
| private func hasBeenPresented() -> Bool { | ||
| return (try? keyValueStore.object(forKey: StorageKey.blockedCookiesPopoverSeen)) as? Bool ?? false | ||
| } | ||
|
|
||
| private func hasBeenEnoughDaysSinceInstallation() -> Bool { | ||
| return AppDelegate.firstLaunchDate.daysSinceNow() >= Constants.minimumDaysSinceInstallation | ||
| } | ||
|
|
||
| private func hasBlockedEnoughCookiePopups() async -> Bool { | ||
| let blockedCount = await autoconsentStats.fetchTotalCookiePopUpsBlocked() | ||
| return blockedCount >= Constants.threshold | ||
| } | ||
|
|
||
| private func showDialog() async { | ||
| let onClose: () -> Void = { [weak self] in | ||
| PixelKit.fire(AutoconsentPixel.popoverClosed, frequency: .daily) | ||
| do { | ||
| try self?.keyValueStore.set(true, forKey: StorageKey.blockedCookiesPopoverSeen) | ||
| } catch { | ||
| // Log error if needed | ||
|
||
| } | ||
| } | ||
|
|
||
| let onClick: () -> Void = { [weak self] in | ||
| PixelKit.fire(AutoconsentPixel.popoverClicked, frequency: .daily) | ||
| self?.openNewTabWithSpecialAction() | ||
| do { | ||
| try self?.keyValueStore.set(true, forKey: StorageKey.blockedCookiesPopoverSeen) | ||
| } catch { | ||
| // Log error if needed | ||
| } | ||
| } | ||
|
|
||
| let onAutoDismiss: () -> Void = { [weak self] in | ||
| PixelKit.fire(AutoconsentPixel.popoverAutoDismissed, frequency: .daily) | ||
| do { | ||
| try self?.keyValueStore.set(true, forKey: StorageKey.blockedCookiesPopoverSeen) | ||
| } catch { | ||
| // Log error if needed | ||
| } | ||
| } | ||
|
|
||
| await presenter.showPopover(onClose: onClose, onClick: onClick, onAutoDismiss: onAutoDismiss) | ||
| } | ||
|
|
||
| private func openNewTabWithSpecialAction() { | ||
| windowControllersManager.showTab(with: .newtab) | ||
|
|
||
| // if let newTabPageViewModel = windowControllersManager.mainWindowController?.mainViewController.browserTabViewController.newTabPageWebViewModel { | ||
| // NSApp.delegateTyped.newTabPageCustomizationModel.customizerOpener.openSettings(for: newTabPageViewModel.webView) | ||
| // } | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| func dismissDialogDueToNewTabBeingShown() { | ||
| guard presenter.isPopoverBeingPresented() else { | ||
| return | ||
| } | ||
| PixelKit.fire(AutoconsentPixel.popoverNewTabOpened, frequency: .daily) | ||
| do { | ||
| try self.keyValueStore.set(true, forKey: StorageKey.blockedCookiesPopoverSeen) | ||
| } catch { | ||
| // Log error if needed | ||
| } | ||
| presenter.dismissPopover() | ||
| } | ||
miasma13 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // MARK: - Debug | ||
|
|
||
| func showDialogForDebug() async { | ||
| guard !presenter.isPopoverBeingPresented() else { | ||
| return | ||
| } | ||
|
|
||
| await showDialog() | ||
| } | ||
|
|
||
| func clearBlockedCookiesPopoverSeenFlag() { | ||
| do { | ||
| try keyValueStore.removeObject(forKey: StorageKey.blockedCookiesPopoverSeen) | ||
| } catch { | ||
| // Log error if needed | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check whether a user has completed the contextual onboarding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will add that.