Skip to content

Commit

Permalink
Adds pixels to VPN tips (#3629)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1206580121312550/1208856370909429/f

## Description

Add pixels for VPN tips in iOS.
  • Loading branch information
diegoreymendez authored Nov 28, 2024
1 parent f838e4d commit 038dd7a
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 82 deletions.
34 changes: 34 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,23 @@ extension Pixel {

case networkProtectionMalformedErrorDetected

// MARK: - VPN Tips

case networkProtectionGeoswitchingTipShown
case networkProtectionGeoswitchingTipActioned
case networkProtectionGeoswitchingTipDismissed
case networkProtectionGeoswitchingTipIgnored

case networkProtectionSnoozeTipShown
case networkProtectionSnoozeTipActioned
case networkProtectionSnoozeTipDismissed
case networkProtectionSnoozeTipIgnored

case networkProtectionWidgetTipShown
case networkProtectionWidgetTipActioned
case networkProtectionWidgetTipDismissed
case networkProtectionWidgetTipIgnored

// MARK: remote messaging pixels

case remoteMessageShown
Expand Down Expand Up @@ -1321,6 +1338,23 @@ extension Pixel.Event {

case .networkProtectionMalformedErrorDetected: return "m_netp_vpn_malformed_error_detected"

// MARK: VPN tips

case .networkProtectionGeoswitchingTipShown: return "m_vpn_tip_geoswitching_shown"
case .networkProtectionGeoswitchingTipActioned: return "m_vpn_tip_geoswitching_actioned"
case .networkProtectionGeoswitchingTipDismissed: return "m_vpn_tip_geoswitching_dismissed"
case .networkProtectionGeoswitchingTipIgnored: return "m_vpn_tip_geoswitching_ignored"

case .networkProtectionSnoozeTipShown: return "m_vpn_tip_snooze_shown"
case .networkProtectionSnoozeTipActioned: return "m_vpn_tip_snooze_actioned"
case .networkProtectionSnoozeTipDismissed: return "m_vpn_tip_snooze_dismissed"
case .networkProtectionSnoozeTipIgnored: return "m_vpn_tip_snooze_ignored"

case .networkProtectionWidgetTipShown: return "m_vpn_tip_widget_shown"
case .networkProtectionWidgetTipActioned: return "m_vpn_tip_widget_actioned"
case .networkProtectionWidgetTipDismissed: return "m_vpn_tip_widget_dismissed"
case .networkProtectionWidgetTipIgnored: return "m_vpn_tip_widget_ignored"

// MARK: remote messaging pixels

case .remoteMessageShown: return "m_remote_message_shown"
Expand Down
15 changes: 9 additions & 6 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,13 @@
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 */; };
6FF9AD452CE766F700C5A406 /* NewTabPageControllerPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */; };
6FF9AD3F2CE63DD800C5A406 /* TabSwitcherOpenDailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD3E2CE63DC200C5A406 /* TabSwitcherOpenDailyPixel.swift */; };
6FF9AD412CE6610F00C5A406 /* TabSwitcherDailyPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD402CE6610600C5A406 /* TabSwitcherDailyPixelTests.swift */; };
6FF9AD452CE766F700C5A406 /* NewTabPageControllerPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.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 */; };
7B1C892C2CF714AA0008224E /* VPNTipsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1C892B2CF714AA0008224E /* VPNTipsModel.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 */; };
Expand Down Expand Up @@ -470,7 +471,6 @@
853A717820F645FB00FE60BC /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853A717720F645FB00FE60BC /* PixelTests.swift */; };
853C5F6121C277C7001F7A05 /* global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853C5F6021C277C7001F7A05 /* global.swift */; };
8540BBA22440857A00017FE4 /* FireproofingWorking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BBA12440857A00017FE4 /* FireproofingWorking.swift */; };
8540BD5223D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift */; };
8540BD5423D8D5080057FDD2 /* FireproofingAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5323D8D5080057FDD2 /* FireproofingAlert.swift */; };
8540BD5623D9E9C20057FDD2 /* FireproofingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5523D9E9C20057FDD2 /* FireproofingSettingsViewController.swift */; };
85449EF523FDA02800512AAF /* KeyboardSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85449EF423FDA02800512AAF /* KeyboardSettingsViewController.swift */; };
Expand Down Expand Up @@ -651,7 +651,7 @@
98424AAD2CED4FF10071C7DB /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; };
98424AAE2CED4FF10071C7DB /* WebCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850559D123CF710C0055C0D5 /* WebCacheManagerTests.swift */; };
98424AAF2CED4FF10071C7DB /* UserAgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DF990248FDDF60075EA48 /* UserAgentTests.swift */; };
98424AB02CED4FF10071C7DB /* PreserveLoginsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */; };
98424AB02CED4FF10071C7DB /* UserDefaultsFireproofingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift */; };
98424AB22CEDD6150071C7DB /* BrowserServicesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 98424AB12CEDD6150071C7DB /* BrowserServicesKit */; };
98424AB42CEDD61C0071C7DB /* BrowserServicesKitTestsUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 98424AB32CEDD61C0071C7DB /* BrowserServicesKitTestsUtils */; };
9847C00027A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9847BFFF27A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift */; };
Expand Down Expand Up @@ -1698,12 +1698,13 @@
6FEC0B842C999352006B4F6E /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapter.swift; sourceTree = "<group>"; };
6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = "<group>"; };
6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerPixelTests.swift; sourceTree = "<group>"; };
6FF9AD3E2CE63DC200C5A406 /* TabSwitcherOpenDailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherOpenDailyPixel.swift; sourceTree = "<group>"; };
6FF9AD402CE6610600C5A406 /* TabSwitcherDailyPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherDailyPixelTests.swift; sourceTree = "<group>"; };
6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerPixelTests.swift; sourceTree = "<group>"; };
7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = "<group>"; };
7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = "<group>"; };
7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = "<group>"; };
7B1C892B2CF714AA0008224E /* VPNTipsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNTipsModel.swift; sourceTree = "<group>"; };
7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitController.swift; sourceTree = "<group>"; };
7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = "<group>"; };
7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = VPN.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4785,7 +4786,7 @@
isa = PBXGroup;
children = (
834DF990248FDDF60075EA48 /* UserAgentTests.swift */,
8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */,
8540BD5123D8C2220057FDD2 /* UserDefaultsFireproofingTests.swift */,
850559D123CF710C0055C0D5 /* WebCacheManagerTests.swift */,
981C49AF2C8FA61D00DF11E8 /* DataStoreIdManagerTests.swift */,
F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */,
Expand Down Expand Up @@ -5729,6 +5730,7 @@
children = (
EE4FB1852A28CE7200E5CBA7 /* NetworkProtectionStatusView.swift */,
EE4FB1872A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift */,
7B1C892B2CF714AA0008224E /* VPNTipsModel.swift */,
);
name = Status;
sourceTree = "<group>";
Expand Down Expand Up @@ -7966,6 +7968,7 @@
1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */,
1D200C972BA3157A00108701 /* SettingsNextStepsView.swift in Sources */,
4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */,
7B1C892C2CF714AA0008224E /* VPNTipsModel.swift in Sources */,
1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */,
D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */,
984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */,
Expand Down Expand Up @@ -8390,7 +8393,7 @@
98424AAD2CED4FF10071C7DB /* WKWebViewConfigurationExtensionTests.swift in Sources */,
98424AAE2CED4FF10071C7DB /* WebCacheManagerTests.swift in Sources */,
98424AAF2CED4FF10071C7DB /* UserAgentTests.swift in Sources */,
98424AB02CED4FF10071C7DB /* PreserveLoginsTests.swift in Sources */,
98424AB02CED4FF10071C7DB /* UserDefaultsFireproofingTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
110 changes: 64 additions & 46 deletions DuckDuckGo/NetworkProtectionStatusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,48 +31,9 @@ struct NetworkProtectionStatusView: View {
@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()
}()
var tipsModel: VPNTipsModel {
statusModel.tipsModel
}

// MARK: - View

Expand Down Expand Up @@ -106,6 +67,16 @@ struct NetworkProtectionStatusView: View {
.sheet(isPresented: $statusModel.showAddWidgetEducationView) {
widgetEducationSheet()
}
.onAppear {
if #available(iOS 18.0, *) {
tipsModel.handleStatusViewAppear()
}
}
.onDisappear {
if #available(iOS 18.0, *) {
tipsModel.handleStatusViewDisappear()
}
}
}

@ViewBuilder
Expand Down Expand Up @@ -359,11 +330,26 @@ struct NetworkProtectionStatusView: View {
@ViewBuilder
private func geoswitchingTipView() -> some View {
if statusModel.canShowTips {

TipView(geoswitchingTip)
TipView(tipsModel.geoswitchingTip)
.removeGroupedListStyleInsets()
.tipCornerRadius(0)
.tipBackground(Color(designSystemColor: .surface))
.onAppear {
tipsModel.handleGeoswitchingTipShown()
}
.task {
var previousStatus = tipsModel.geoswitchingTip.status

for await status in tipsModel.geoswitchingTip.statusUpdates {
if case .invalidated(let reason) = status {
if case .available = previousStatus {
tipsModel.handleGeoswitchingTipInvalidated(reason)
}
}

previousStatus = status
}
}
}
}

Expand All @@ -373,10 +359,26 @@ struct NetworkProtectionStatusView: View {
if statusModel.canShowTips,
statusModel.hasServerInfo {

TipView(snoozeTip, action: statusModel.snoozeActionHandler(action:))
TipView(tipsModel.snoozeTip, action: statusModel.snoozeActionHandler(action:))
.removeGroupedListStyleInsets()
.tipCornerRadius(0)
.tipBackground(Color(designSystemColor: .surface))
.onAppear {
tipsModel.handleSnoozeTipShown()
}
.task {
var previousStatus = tipsModel.snoozeTip.status

for await status in tipsModel.snoozeTip.statusUpdates {
if case .invalidated(let reason) = status {
if case .available = previousStatus {
tipsModel.handleSnoozeTipInvalidated(reason)
}
}

previousStatus = status
}
}
}
}

Expand All @@ -386,10 +388,26 @@ struct NetworkProtectionStatusView: View {
if statusModel.canShowTips,
!statusModel.isNetPEnabled && !statusModel.isSnoozing {

TipView(widgetTip, action: statusModel.widgetActionHandler(action:))
TipView(tipsModel.widgetTip, action: statusModel.widgetActionHandler(action:))
.removeGroupedListStyleInsets()
.tipCornerRadius(0)
.tipBackground(Color(designSystemColor: .surface))
.onAppear {
tipsModel.handleWidgetTipShown()
}
.task {
var previousStatus = tipsModel.widgetTip.status

for await status in tipsModel.widgetTip.statusUpdates {
if case .invalidated(let reason) = status {
if case .available = previousStatus {
tipsModel.handleWidgetTipInvalidated(reason)
}
}

previousStatus = status
}
}
}
}

Expand Down
24 changes: 16 additions & 8 deletions DuckDuckGo/NetworkProtectionStatusViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject {
@Published
var showAddWidgetEducationView: Bool = false

let tipsModel: VPNTipsModel

// MARK: Error

struct ErrorItem {
Expand All @@ -138,7 +140,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject {
didSet {
if #available(iOS 17.0, *) {
if isNetPEnabled {
VPNGeoswitchingTip.donateVPNConnectedEvent()
VPNGeoswitchingTip.vpnEnabledOnce = true
}

VPNSnoozeTip.vpnEnabled = isNetPEnabled
Expand Down Expand Up @@ -184,6 +186,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject {
errorObserver: ConnectionErrorObserver = ConnectionErrorObserverThroughSession(),
locationListRepository: NetworkProtectionLocationListRepository,
usesUnifiedFeedbackForm: Bool) {

self.tunnelController = tunnelController
self.settings = settings
self.statusObserver = statusObserver
Expand All @@ -199,6 +202,11 @@ final class NetworkProtectionStatusViewModel: ObservableObject {

self.dnsSettings = settings.dnsSettings

self.tipsModel = VPNTipsModel(
isTipFeatureEnabled: featureFlagger.isFeatureOn(.networkProtectionUserTips),
statusObserver: statusObserver,
vpnSettings: settings)

updateViewModel(withStatus: statusObserver.recentValue)

setUpIsConnectedStatePublishers()
Expand Down Expand Up @@ -477,8 +485,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject {
return
}

if #available(iOS 17.0, *) {
VPNSnoozeTip().invalidate(reason: .actionPerformed)
if #available(iOS 18.0, *) {
tipsModel.handleUserSnoozedVPN()
}

let defaultDuration: TimeInterval = .minutes(20)
Expand Down Expand Up @@ -577,30 +585,30 @@ final class NetworkProtectionStatusViewModel: ObservableObject {

// MARK: - UI Events handling

@available(iOS 17.0, *)
@available(iOS 18.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, *)
@available(iOS 18.0, *)
@MainActor
func widgetActionHandler(action: Tips.Action) {
if action.id == VPNAddWidgetTip.ActionIdentifiers.addWidget.rawValue {
showAddWidgetEducationView = true

VPNAddWidgetTip().invalidate(reason: .actionPerformed)
tipsModel.handleUserOpenedWidgetLearnMore()
}
}

/// The user opened the VPN locations view
///
func handleUserOpenedVPNLocations() {
if #available(iOS 17.0, *) {
if #available(iOS 18.0, *) {
Task { @MainActor in
VPNGeoswitchingTip().invalidate(reason: .actionPerformed)
tipsModel.handleUserOpenedLocations()
}
}
}
Expand Down
Loading

0 comments on commit 038dd7a

Please sign in to comment.