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() } } }