diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift
index 89b435c3af..892d8936d8 100644
--- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift
+++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift
@@ -24,11 +24,12 @@ import Foundation
import LoginItems
import NetworkProtection
import NetworkProtectionIPC
+import NetworkProtectionProxy
import NetworkProtectionUI
+import os.log
import Subscription
-import VPNAppLauncher
import SwiftUI
-import NetworkProtectionProxy
+import VPNAppLauncher
protocol NetworkProtectionIPCClient {
var ipcStatusObserver: ConnectionStatusObserver { get }
@@ -167,7 +168,8 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager {
statusObserver: statusReporter.statusObserver,
activeSitePublisher: activeSitePublisher,
forMenuApp: false,
- vpnSettings: vpnSettings)
+ vpnSettings: vpnSettings,
+ logger: Logger(subsystem: "DuckDuckGo", category: "TipKit"))
let popover = NetworkProtectionPopover(
statusViewModel: statusViewModel,
diff --git a/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements b/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements
index 2bb2f334b0..0b0df415dc 100644
--- a/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements
+++ b/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements
@@ -14,6 +14,7 @@
$(NETP_APP_GROUP)
$(IPC_APP_GROUP)
$(SUBSCRIPTION_APP_GROUP)
+ $(APP_CONFIGURATION_APP_GROUP)
keychain-access-groups
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift
index 90f4f27a82..73087cd3fd 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift
@@ -20,9 +20,10 @@ import AppKit
import Foundation
import Combine
import Common
-import SwiftUI
-import NetworkProtection
import LoginItems
+import NetworkProtection
+import os.log
+import SwiftUI
import TipKitUtils
/// Abstraction of the the VPN status bar menu with a simple interface.
@@ -150,7 +151,8 @@ public final class StatusBarMenu: NSObject {
statusObserver: statusReporter.statusObserver,
activeSitePublisher: activeSitePublisher,
forMenuApp: true,
- vpnSettings: VPNSettings(defaults: userDefaults))
+ vpnSettings: VPNSettings(defaults: userDefaults),
+ logger: Logger.init(subsystem: "DuckDuckGo", category: "TipKit"))
let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed)
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift
index 51ca3cb0b0..8881f62dee 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift
@@ -21,12 +21,8 @@ import TipKit
/// A tip to suggest to the user to use the autoconnect option for the VPN.
///
-struct VPNAutoconnectTip {}
-
-/// Necessary split to support older iOS versions.
-///
@available(macOS 14.0, *)
-extension VPNAutoconnectTip: Tip {
+struct VPNAutoconnectTip: Tip {
enum ActionIdentifiers: String {
case enable = "com.duckduckgo.vpn.tip.autoconnect.action.enable"
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift
index 026524c062..82dab28166 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift
@@ -21,12 +21,8 @@ import TipKit
/// A tip to suggest using domain exclusions when a site doesn't work.
///
-struct VPNDomainExclusionsTip {}
-
-/// Necessary split to support older iOS versions.
-///
@available(macOS 14.0, *)
-extension VPNDomainExclusionsTip: Tip {
+struct VPNDomainExclusionsTip: Tip {
@Parameter(.transient)
static var vpnEnabled: Bool = false
@@ -39,7 +35,7 @@ extension VPNDomainExclusionsTip: Tip {
/// This condition may be indicative that the user is struggling, so they might want
/// to exclude a site.
///
- static let viewOpenedWhehVPNAlreadyConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.popoverOpenedWhileAlreadyConnected")
+ static let viewOpenedWhenVPNAlreadyConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.popoverOpenedWhileAlreadyConnected")
var id: String {
"com.duckduckgo.vpn.tip.domainExclusions"
@@ -64,7 +60,7 @@ extension VPNDomainExclusionsTip: Tip {
#Rule(Self.$vpnEnabled) {
$0 == true
}
- #Rule(Self.viewOpenedWhehVPNAlreadyConnectedEvent) {
+ #Rule(Self.viewOpenedWhenVPNAlreadyConnectedEvent) {
$0.donations.count > 1
}
}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift
index 9d609f39c8..27aac17ebb 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift
@@ -21,10 +21,8 @@ import TipKit
/// A tip to suggest to the user to change their location using geo-switching
///
-struct VPNGeoswitchingTip {}
-
@available(macOS 14.0, *)
-extension VPNGeoswitchingTip: Tip {
+struct VPNGeoswitchingTip: Tip {
static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.geoswitching.vpnConnectedEvent")
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift
index 27f9057a0c..a976e5a188 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift
@@ -20,9 +20,11 @@ import AppKit
import Combine
import Common
import NetworkProtection
+import os.log
import TipKit
import TipKitUtils
+@MainActor
public final class VPNTipsModel: ObservableObject {
@Published
@@ -45,11 +47,18 @@ public final class VPNTipsModel: ObservableObject {
switch connectionStatus {
case .connected:
+ if case oldValue = .connecting {
+ Task {
+ await VPNGeoswitchingTip.vpnConnectedEvent.donate()
+ }
+ }
+
VPNAutoconnectTip.vpnEnabled = true
VPNDomainExclusionsTip.vpnEnabled = true
default:
VPNAutoconnectTip.vpnEnabled = false
VPNDomainExclusionsTip.vpnEnabled = false
+ break
}
}
}
@@ -59,41 +68,55 @@ public final class VPNTipsModel: ObservableObject {
let tips: TipGrouping
private let vpnSettings: VPNSettings
+ private let logger: Logger
private var cancellables = Set()
- static func makeTips(forMenuApp isMenuApp: Bool) -> TipGrouping {
+ static func makeTips(forMenuApp isMenuApp: Bool, logger: Logger) -> TipGrouping {
+
+ guard #available(macOS 14.0, *) else {
+ return EmptyTipGroup()
+ }
+
+ let autoconnectTip = VPNAutoconnectTip()
+ let domainExclusionsTip = VPNDomainExclusionsTip()
+ let geoswitchingTip = VPNGeoswitchingTip()
+ let tips: [any Tip] = {
+ if isMenuApp {
+ return [
+ geoswitchingTip,
+ autoconnectTip
+ ]
+ } else {
+ return [
+ geoswitchingTip,
+ domainExclusionsTip,
+ autoconnectTip
+ ]
+ }
+ }()
+
+ Task {
+ for await statusUpdate in geoswitchingTip.statusUpdates {
+ logger.debug("🪙 VPNGeoswitchingTip status updated: \(String(describing: statusUpdate), privacy: .public)")
+ }
+ }
+
+ Task {
+ for await statusUpdate in domainExclusionsTip.statusUpdates {
+ logger.debug("🪙 VPNDomainExclusionsTip status updated: \(String(describing: statusUpdate), privacy: .public)")
+ }
+ }
+
// This is temporarily disabled until Xcode 16 is available.
// Ref: https://app.asana.com/0/414235014887631/1208528787265444/f
//
// if #available(macOS 15.0, *) {
- // if isMenuApp {
- // return TipGroup(.ordered) {
- // VPNGeoswitchingTip()
- // VPNAutoconnectTip()
- // }
- // } else {
- // return TipGroup(.ordered) {
- // VPNGeoswitchingTip()
- // VPNDomainExclusionsTip()
- // VPNAutoconnectTip()
- // }
+ // return TipGroup(.ordered) {
+ // tips
// }
- // }
- if #available(macOS 14, *) {
- if isMenuApp {
- return LegacyTipGroup(.ordered) {
- VPNGeoswitchingTip()
- VPNAutoconnectTip()
- }
- } else {
- return LegacyTipGroup(.ordered) {
- VPNGeoswitchingTip()
- VPNDomainExclusionsTip()
- VPNAutoconnectTip()
- }
- }
- } else {
- return EmptyTipGroup()
+ // } else { ... what's below
+ return LegacyTipGroup(.ordered) {
+ tips
}
}
@@ -101,12 +124,14 @@ public final class VPNTipsModel: ObservableObject {
statusObserver: ConnectionStatusObserver,
activeSitePublisher: CurrentValuePublisher,
forMenuApp isMenuApp: Bool,
- vpnSettings: VPNSettings) {
+ vpnSettings: VPNSettings,
+ logger: Logger) {
self.activeSiteInfo = activeSitePublisher.value
self.connectionStatus = statusObserver.recentValue
self.featureFlag = featureFlagPublisher.value
- self.tips = Self.makeTips(forMenuApp: isMenuApp)
+ self.logger = logger
+ self.tips = Self.makeTips(forMenuApp: isMenuApp, logger: logger)
self.vpnSettings = vpnSettings
if #available(macOS 14.0, *) {
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift
index 7ebb3c8a1d..b50384ee55 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift
@@ -41,6 +41,9 @@ public struct TunnelControllerView: View {
self.model = model
}
+ @EnvironmentObject
+ private var tipsModel: VPNTipsModel
+
// MARK: - View Contents
public var body: some View {
@@ -60,9 +63,17 @@ public struct TunnelControllerView: View {
.padding(.top, 5)
if #available(macOS 15.0, *) {
- VPNDomainExclusionsTipView()
- .padding(.horizontal, 9)
- .padding(.vertical, 6)
+ //VPNDomainExclusionsTipView()
+ //.padding(.horizontal, 9)
+ //.padding(.vertical, 6)
+
+ if tipsModel.featureFlag,
+ let tip = tipsModel.tips.currentTip as? VPNDomainExclusionsTip {
+
+ TipView(tip)
+ .padding(.horizontal, 9)
+ .padding(.vertical, 6)
+ }
}
Divider()
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift
index 35a5bc317a..1b8354bf00 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift
@@ -503,10 +503,6 @@ public final class TunnelControllerViewModel: ObservableObject {
}
Task { @MainActor in
- if #available(macOS 14.0, *) {
- await VPNGeoswitchingTip.vpnConnectedEvent.donate()
- }
-
await tunnelController.start()
refreshInternalIsRunning()
}
@@ -541,7 +537,7 @@ public final class TunnelControllerViewModel: ObservableObject {
func handleTunnelControllerShown() async {
if #available(macOS 14.0, *) {
- await VPNDomainExclusionsTip.viewOpenedWhehVPNAlreadyConnectedEvent.donate()
+ await VPNDomainExclusionsTip.viewOpenedWhenVPNAlreadyConnectedEvent.donate()
}
}
}