From 1beb4c97359c8f10ace51252dd1c4269a3ce2188 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 15 Oct 2024 17:32:09 +0200 Subject: [PATCH 01/34] WIP --- DuckDuckGo.xcodeproj/project.pbxproj | 2 + .../xcshareddata/swiftpm/Package.resolved | 11 +-- DuckDuckGo/Localizable.xcstrings | 4 +- .../TipKit/VPNAddWidgetTip.swift | 68 +++++++++++++++++++ .../TipKit/VPNChangeLocationTip.swift | 58 ++++++++++++++++ .../TipKit/VPNUseSnoozeTip.swift | 66 ++++++++++++++++++ 6 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 48d2f3b408..c97e0a9b37 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3814,6 +3814,7 @@ 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAppEvents.swift; sourceTree = ""; }; 7B2E52242A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAgentNotificationsPresenter.swift; sourceTree = ""; }; 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNavBarPopoverManager.swift; sourceTree = ""; }; + 7B3930002CBEA86A004C48E2 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionSimulateFailureMenu.swift; sourceTree = ""; }; 7B4C5CF42BE51D640007A164 /* VPNUninstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUninstallerTests.swift; sourceTree = ""; }; 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -5499,6 +5500,7 @@ 378E279C2970217400FCADA2 /* LocalPackages */ = { isa = PBXGroup; children = ( + 7B3930002CBEA86A004C48E2 /* BrowserServicesKit */, 9D9DE5712C63A96400D20B15 /* AppKitExtensions */, 7B9167A82C09E88800322310 /* AppLauncher */, 378E279D2970217400FCADA2 /* BuildToolPlugins */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 528948b014..a8520eb162 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,15 +27,6 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "revision" : "e0c0c85c18372f73fb97c5cf070f1de70c906a1f", - "version" : "199.1.0" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -75,7 +66,7 @@ { "identity" : "lottie-spm", "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm", + "location" : "https://github.com/airbnb/lottie-spm.git", "state" : { "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", "version" : "4.4.3" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 5bfb975e0b..48d1c3f89d 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -37402,7 +37402,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "You’re all set! You can find me hanging out in the Dock anytime.\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" + "value" : "You’re all set! You can find me hanging out in the Dock anytime.\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possible 🔒" } }, "es" : { @@ -37462,7 +37462,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "You’re all set!\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" + "value" : "You’re all set!\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possible 🔒" } }, "es" : { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift new file mode 100644 index 0000000000..839bbb9052 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift @@ -0,0 +1,68 @@ +// +// VPNAddWidgetTip.swift +// DuckDuckGo +// +// Copyright © 2024 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 TipKit + +/// A tip to suggest to the user that they add our VPN widget for quick access to the VPN +/// +struct VPNAddWidgetTip {} + +/// Necessary split to support older iOS versions. +/// +@available(macOS 14.0, *) +extension VPNAddWidgetTip: Tip { + + enum ActionIdentifiers: String { + case addWidget = "com.duckduckgo.tipkit.VPNAddWidgetTip.addWidget" + } + + @Parameter(.transient) + static var vpnEnabled: Bool = false + + private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNAddWidgetTip.vpnDisconnectedEvent") + + var id: String { + "com.duckduckgo.tipkit.VPNAddWidgetTip" + } + + var title: Text { + Text("Add VPN Widget") + } + + var message: Text? { + Text("Turn the VPN on and off right from the Home Screen.") + } + + var image: Image? { + Image(systemName: "rectangle.and.hand.point.up.left.fill") + } + + var actions: [Action] { + [Action(id: ActionIdentifiers.addWidget.rawValue) { + Text("Add widget") + .foregroundStyle(Color(.linkColor)) + }] + } + + var rules: [Rule] { + #Rule(Self.$vpnEnabled) { + $0 == false + } + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift new file mode 100644 index 0000000000..ec6ad13651 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift @@ -0,0 +1,58 @@ +// +// VPNChangeLocationTip.swift +// DuckDuckGo +// +// Copyright © 2024 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 TipKit + +/// A tip to suggest to the user to change their location using geo-switching +/// +struct VPNChangeLocationTip {} + +@available(macOS 14.0, *) +extension VPNChangeLocationTip: Tip { + + private static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNChangeLocationTip.vpnConnectedEvent") + + var id: String { + "com.duckduckgo.tipkit.VPNChangeLocationTip" + } + + var title: Text { + Text("Change Your Location") + } + + var message: Text? { + Text("Connect to any of our servers worldwide to customize the VPN location.") + } + + var image: Image? { + Image(systemName: "globe.americas.fill") + } + + var rules: [Rule] { + #Rule(Self.vpnConnectedEvent) { + $0.donations.donatedWithin(.week).count > 0 + } + } + + static func donateVPNConnectedEvent() { + Task { + await vpnConnectedEvent.donate() + } + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift new file mode 100644 index 0000000000..c26a8f7b0c --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift @@ -0,0 +1,66 @@ +// +// VPNUseSnoozeTip.swift +// DuckDuckGo +// +// Copyright © 2024 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 TipKit + +/// A tip to suggest to the user to use the snooze feature to momentarily disable the VPN +/// +struct VPNUseSnoozeTip {} + +/// Necessary split to support older iOS versions. +/// +@available(macOS 14.0, *) +extension VPNUseSnoozeTip: Tip { + + enum ActionIdentifiers: String { + case learnMore = "com.duckduckgo.tipkit.VPNUseSnoozeTip.learnMoreId" + } + + @Parameter(.transient) + static var vpnEnabled: Bool = false + + var id: String { + "com.duckduckgo.tipkit.VPNUseSnoozeTip" + } + + var title: Text { + Text("Avoid VPN Conflicts") + } + + var message: Text? { + Text("Snooze briefly disconnects the VPN so you can use sites or apps that block VPN traffic.") + } + + var image: Image? { + Image(systemName: "powersleep") + } + + var actions: [Action] { + [Action(id: ActionIdentifiers.learnMore.rawValue) { + Text("Learn more") + .foregroundStyle(Color(.linkColor)) + }] + } + + var rules: [Rule] { + #Rule(Self.$vpnEnabled) { + $0 == true + } + } +} From 12c6d00c0a08ee4f60501f6a0d33b6dd4cd55c0e Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 09:29:47 +0200 Subject: [PATCH 02/34] WIP --- DuckDuckGo.xcodeproj/project.pbxproj | 50 +++++++++++ .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/Application/AppDelegate.swift | 2 + DuckDuckGo/InfoPlist.xcstrings | 18 ++-- DuckDuckGo/Localizable.xcstrings | 10 +-- DuckDuckGo/Menus/MainMenu.swift | 5 ++ DuckDuckGo/Menus/MainMenuActions.swift | 5 ++ ...etworkProtectionNavBarPopoverManager.swift | 8 ++ DuckDuckGo/TipKit/Logger+TipKit.swift | 28 +++++++ .../TipKit/TipKitAppEventHandling.swift | 60 +++++++++++++ ...itController+ConvenienceInitializers.swift | 31 +++++++ .../TipKitDebugOptionsUIActionHandling.swift | 48 +++++++++++ DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 2 + .../CombineExtensions/.gitignore | 8 ++ .../CombineExtensions/Package.swift | 24 ++++++ .../CombineExtensions/CombineExtensions.swift | 2 + .../CombineExtensionsTests.swift | 6 ++ LocalPackages/CombineExtensions/.gitignore | 8 ++ LocalPackages/CombineExtensions/Package.swift | 27 ++++++ .../CurrentValuePublisher.swift | 36 ++++++++ .../CombineExtensionsTests.swift | 6 ++ .../NetworkProtectionMac/Package.swift | 3 + .../Extensions/LottieView+withIntro.swift | 6 +- .../Menu/StatusBarMenu.swift | 9 ++ .../NetworkProtectionPopover.swift | 4 + .../TipViews/Model/VPNAutoconnectTip.swift} | 24 +++--- .../Model/VPNDomainExclusionsTip.swift} | 37 ++++---- .../TipViews/Model/VPNGeoswitchingTip.swift} | 16 ++-- .../Views/TipViews/Model/VPNTipsModel.swift | 84 +++++++++++++++++++ .../TipViews/VPNAutoconnectTipView.swift | 45 ++++++++++ .../TipViews/VPNDomainExclusionsTipView.swift | 45 ++++++++++ .../TipViews/VPNGeoswitchingTipView.swift | 44 ++++++++++ .../TunnelControllerView.swift | 20 ++++- .../TunnelControllerViewModel.swift | 71 ++++++++-------- 34 files changed, 696 insertions(+), 98 deletions(-) create mode 100644 DuckDuckGo/TipKit/Logger+TipKit.swift create mode 100644 DuckDuckGo/TipKit/TipKitAppEventHandling.swift create mode 100644 DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift create mode 100644 DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift create mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore create mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift create mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift create mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift create mode 100644 LocalPackages/CombineExtensions/.gitignore create mode 100644 LocalPackages/CombineExtensions/Package.swift create mode 100644 LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift create mode 100644 LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/{TipKit/VPNUseSnoozeTip.swift => Views/TipViews/Model/VPNAutoconnectTip.swift} (73%) rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/{TipKit/VPNAddWidgetTip.swift => Views/TipViews/Model/VPNDomainExclusionsTip.swift} (53%) rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/{TipKit/VPNChangeLocationTip.swift => Views/TipViews/Model/VPNGeoswitchingTip.swift} (75%) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c97e0a9b37..3a07413cd4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1771,6 +1771,22 @@ 7BD7B0042C19D3830039D20A /* VPNIPCResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD7B0002C19D3830039D20A /* VPNIPCResources.swift */; }; 7BDA36E62B7E037100AD5388 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B4D603E2A0B290200BCD287 /* NetworkExtension.framework */; }; 7BDA36F52B7E055800AD5388 /* MacTransparentProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */; }; + 7BDBAD142CBFF633000379B7 /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */; }; + 7BDBAD152CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */; }; + 7BDBAD162CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */; }; + 7BDBAD172CBFF633000379B7 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */; }; + 7BDBAD182CBFF633000379B7 /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */; }; + 7BDBAD192CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */; }; + 7BDBAD1A2CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */; }; + 7BDBAD1B2CBFF633000379B7 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */; }; + 7BDBAD1C2CBFF95F000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */; }; + 7BDBAD1D2CBFF95F000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */; }; + 7BDBAD1E2CBFF96A000379B7 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */; }; + 7BDBAD1F2CBFF96A000379B7 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */; }; + 7BDBAD202CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */; }; + 7BDBAD212CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */; }; + 7BDBAD222CBFF97E000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */; }; + 7BDBAD232CBFF97E000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */; }; 7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; 7BE146082A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; 7BEC182F2AD5D8DC00D30536 /* SystemExtensionManager in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */; }; @@ -3866,6 +3882,11 @@ 7BDA36EA2B7E037200AD5388 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7BDA36EB2B7E037200AD5388 /* VPNProxyExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VPNProxyExtension.entitlements; sourceTree = ""; }; 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = VPNProxyExtension.xcconfig; sourceTree = ""; }; + 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; + 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; + 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; + 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; + 7BDBAD252CC02612000379B7 /* CombineExtensions */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = CombineExtensions; sourceTree = ""; }; 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugMenu.swift; sourceTree = ""; }; 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SystemExtensionManager; sourceTree = ""; }; 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopoverView.swift; sourceTree = ""; }; @@ -5514,6 +5535,7 @@ 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */, 7B8594172B5B25FB0007EB3E /* UDSHelper */, 7B76E6852AD5D77600186A84 /* XPCHelper */, + 7BDBAD252CC02612000379B7 /* CombineExtensions */, ); path = LocalPackages; sourceTree = ""; @@ -6710,6 +6732,17 @@ path = VPNProxyExtension; sourceTree = ""; }; + 7BDBAD0F2CBFF60F000379B7 /* TipKit */ = { + isa = PBXGroup; + children = ( + 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */, + 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */, + 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */, + 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */, + ); + path = TipKit; + sourceTree = ""; + }; 84537A072C99C1EF008723BC /* App */ = { isa = PBXGroup; children = ( @@ -7439,6 +7472,7 @@ AA86491B24D837DE001BABEE /* Tab */, AA86491124D8318F001BABEE /* TabBar */, AAE8B0FD258A416F00E81239 /* TabPreview */, + 7BDBAD0F2CBFF60F000379B7 /* TipKit */, B6040859274B8C5200680351 /* UnprotectedDomains */, 1D72D5902BFF361700AEDE36 /* Updates */, AACF6FD426BC35C200CF09F9 /* UserAgent */, @@ -10525,6 +10559,10 @@ EEC4A66E2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift in Sources */, 3706FA83293F65D500E42796 /* LazyLoadable.swift in Sources */, 3199AF7C2C80734A003AEBDC /* DuckPlayerOnboardingLocationValidator.swift in Sources */, + 7BDBAD142CBFF633000379B7 /* TipKitAppEventHandling.swift in Sources */, + 7BDBAD152CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */, + 7BDBAD162CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, + 7BDBAD172CBFF633000379B7 /* Logger+TipKit.swift in Sources */, 3706FA85293F65D500E42796 /* KeyedCodingExtension.swift in Sources */, 3706FA87293F65D500E42796 /* DownloadListStore.swift in Sources */, 37197EAB2942443D00394917 /* WebViewContainerView.swift in Sources */, @@ -11879,14 +11917,18 @@ F1C70D802BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, 02FDA65B2C764C200024CD8B /* ConfigurationStore.swift in Sources */, + 7BDBAD202CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */, 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */, + 7BDBAD1E2CBFF96A000379B7 /* Logger+TipKit.swift in Sources */, BDA764842BC49E3F00D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, + 7BDBAD1C2CBFF95F000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */, 7B60AFFA2C511B65008E32A3 /* VPNUIActionHandler.swift in Sources */, B65DA5EF2A77CC3A00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA7647F2BC4998900D0400C /* DefaultVPNLocationFormatter.swift in Sources */, 4BF0E5072AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, F1DA518A2BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, 7BA7CC442AD11E490042E5CE /* UserText.swift in Sources */, + 7BDBAD222CBFF97E000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, 4BF0E5142AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, F1FDC93C2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, B65DA5F12A77D2BC00CBEE8D /* BundleExtension.swift in Sources */, @@ -11922,14 +11964,18 @@ B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, BDA764852BC49E4000D0400C /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, 02FDA65D2C764CB30024CD8B /* ConfigurationStore.swift in Sources */, + 7BDBAD212CBFF977000379B7 /* TipKitAppEventHandling.swift in Sources */, 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, + 7BDBAD1F2CBFF96A000379B7 /* Logger+TipKit.swift in Sources */, 7B60AFFB2C511C68008E32A3 /* VPNUIActionHandler.swift in Sources */, + 7BDBAD1D2CBFF95F000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */, 7BA7CC392AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */, BDA764802BC4998A00D0400C /* DefaultVPNLocationFormatter.swift in Sources */, 7BA7CC552AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, F1DA518B2BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, 7BA7CC3D2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, 4BA7C4DA2B3F639800AFE511 /* NetworkProtectionTunnelController.swift in Sources */, + 7BDBAD232CBFF97E000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, F1FDC93D2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 7BA7CC432AD11E480042E5CE /* UserText.swift in Sources */, 7BA7CC542AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */, @@ -12187,6 +12233,10 @@ 1DDF076428F815AD00EDFBE3 /* BWManager.swift in Sources */, 9833912F27AAA3CE00DAF119 /* AppTrackerDataSetProvider.swift in Sources */, B65211252B29A42C00B30633 /* BookmarkStoreMock.swift in Sources */, + 7BDBAD182CBFF633000379B7 /* TipKitAppEventHandling.swift in Sources */, + 7BDBAD192CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift in Sources */, + 7BDBAD1A2CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, + 7BDBAD1B2CBFF633000379B7 /* Logger+TipKit.swift in Sources */, 4BA1A6B3258B080A00F6F690 /* EncryptionKeyGeneration.swift in Sources */, 37B11B3928095E6600CBB621 /* TabLazyLoader.swift in Sources */, 4B9DB03B2A983B24000927DB /* InvitedToWaitlistView.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a8520eb162..06c52ecb68 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -66,7 +66,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/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index b2aaac1dd1..c8826a85ef 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -388,6 +388,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: subscriptionManager.accountManager)).applicationDidFinishLaunching() + TipKitAppEventHandler().appDidFinishLaunching() + setUpAutoClearHandler() setUpAutofillPixelReporter() diff --git a/DuckDuckGo/InfoPlist.xcstrings b/DuckDuckGo/InfoPlist.xcstrings index ef75ed805d..30bc043fde 100644 --- a/DuckDuckGo/InfoPlist.xcstrings +++ b/DuckDuckGo/InfoPlist.xcstrings @@ -7,55 +7,55 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo" + "value" : "DuckDuckGo App Store" } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "DuckDuckGo" } } diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 48d1c3f89d..5944f0dfc3 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -30600,7 +30600,7 @@ }, "letsmove.alert.message" : { "comment" : "Message of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -30660,7 +30660,7 @@ }, "letsmove.alert.title" : { "comment" : "Title of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -30720,7 +30720,7 @@ }, "letsmove.could.not.move" : { "comment" : "Error message when moving the app to the /Applications folder failed", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -30780,7 +30780,7 @@ }, "letsmove.dont.move.button" : { "comment" : "Do Not Move to the /Applications folder button title", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -30840,7 +30840,7 @@ }, "letsmove.move.button" : { "comment" : "Move the /Applications folder button title", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index c7e454208b..81589c3136 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -673,6 +673,11 @@ final class MainMenu: NSMenu { openSubscriptionTab: { WindowControllersManager.shared.showTab(with: .subscription($0)) }, subscriptionManager: Application.appDelegate.subscriptionManager) + NSMenuItem(title: "TipKit") { + NSMenuItem(title: "Reset", action: #selector(MainViewController.resetTipKit)) + NSMenuItem(title: "⚠️ App restart required.", action: nil, target: nil) + } + NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) } debugMenu.addItem(internalUserItem) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index cbf32d7dcc..9addd60d5b 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -27,6 +27,7 @@ import Subscription import WebKit import os.log import SwiftUI +import TipKitUtils // Actions are sent to objects of responder chain @@ -852,6 +853,10 @@ extension MainViewController { SyncPromoManager().resetPromos() } + @objc func resetTipKit(_ sender: Any?) { + TipKitDebugOptionsUIActionHandler().resetTipKitTapped() + } + @objc func internalUserState(_ sender: Any?) { guard let internalUserDecider = NSApp.delegateTyped.internalUserDecider as? DefaultInternalUserDecider else { return } let state = internalUserDecider.isInternalUser diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index c8656a8ae0..e56de36b69 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -19,6 +19,7 @@ import AppLauncher import AppKit import Combine +import CombineExtensions import Common import Foundation import LoginItems @@ -156,10 +157,17 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { _ = try? await self?.vpnUninstaller.uninstall(removeSystemExtension: true) }) + // TODO: replace with access to actual feature flag + let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) + + let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, + forMenuApp: false) + let popover = NetworkProtectionPopover( statusViewModel: statusViewModel, statusReporter: statusReporter, siteTroubleshootingViewModel: siteTroubleshootingViewModel, + tipsModel: tipsModel, debugInformationViewModel: DebugInformationViewModel(showDebugInformation: false)) popover.delegate = delegate diff --git a/DuckDuckGo/TipKit/Logger+TipKit.swift b/DuckDuckGo/TipKit/Logger+TipKit.swift new file mode 100644 index 0000000000..1d791692b4 --- /dev/null +++ b/DuckDuckGo/TipKit/Logger+TipKit.swift @@ -0,0 +1,28 @@ +// +// Logger+TipKit.swift +// DuckDuckGo +// +// Copyright © 2024 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 os.log + +extension Logger { + + static var tipKit: Logger = { + Logger(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "TipKit") + }() +} diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift new file mode 100644 index 0000000000..57b2ff0500 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -0,0 +1,60 @@ +// +// TipKitAppEventHandling.swift +// DuckDuckGo +// +// Copyright © 2024 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 os.log +import TipKitUtils +import TipKit + +protocol TipKitAppEventHandling { + func appDidFinishLaunching() +} + +struct TipKitAppEventHandler: TipKitAppEventHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController = .make(), + logger: Logger = .tipKit) { + + self.controller = controller + self.logger = logger + } + + func appDidFinishLaunching() { + if #available(macOS 14.0, *) { + typealias DataStoreLocation = Tips.ConfigurationOption.DatastoreLocation + + let appConfigurationGroupIdentifier = Bundle.main.appGroup(bundle: .appConfiguration) + + guard let dataStoreLocation = try? DataStoreLocation.groupContainer(identifier: appConfigurationGroupIdentifier) else { + + fatalError() + } + + controller.configureTipKit([ + .displayFrequency(.immediate), + .datastoreLocation(dataStoreLocation) + ]) + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift new file mode 100644 index 0000000000..93884bff37 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift @@ -0,0 +1,31 @@ +// +// TipKitController+ConvenienceInitializers.swift +// DuckDuckGo +// +// Copyright © 2024 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 TipKitUtils +import os + +extension TipKitController { + + static func make(logger: Logger = .tipKit, + userDefaults: UserDefaults = .appConfiguration) -> Self { + + self.init(logger: logger, userDefaults: userDefaults) + } +} diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift new file mode 100644 index 0000000000..5e2750e72a --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -0,0 +1,48 @@ +// +// TipKitDebugOptionsUIActionHandling.swift +// DuckDuckGo +// +// Copyright © 2024 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 os.log +import TipKitUtils + +protocol TipKitDebugOptionsUIActionHandling { + /// Resets TipKit + func resetTipKitTapped() +} + +struct TipKitDebugOptionsUIActionHandler: TipKitDebugOptionsUIActionHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController = .make(), + logger: Logger = .tipKit) { + + self.controller = controller + self.logger = logger + } + + func resetTipKitTapped() { + if #available(macOS 14.0, *) { + controller.resetTipKitOnNextAppLaunch() + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 0d1a02448e..891f9b6707 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -362,6 +362,8 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { @MainActor func applicationDidFinishLaunching(_ aNotification: Notification) { + TipKitAppEventHandler().appDidFinishLaunching() + APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) Logger.networkProtection.info("DuckDuckGoVPN started") diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore b/LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift b/LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift new file mode 100644 index 0000000000..649901ad34 --- /dev/null +++ b/LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "CombineExtensions", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "CombineExtensions", + targets: ["CombineExtensions"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "CombineExtensions"), + .testTarget( + name: "CombineExtensionsTests", + dependencies: ["CombineExtensions"] + ), + ] +) diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift b/LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift new file mode 100644 index 0000000000..08b22b80fc --- /dev/null +++ b/LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift b/LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift new file mode 100644 index 0000000000..c733bf224b --- /dev/null +++ b/LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift @@ -0,0 +1,6 @@ +import Testing +@testable import CombineExtensions + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} diff --git a/LocalPackages/CombineExtensions/.gitignore b/LocalPackages/CombineExtensions/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/CombineExtensions/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/CombineExtensions/Package.swift b/LocalPackages/CombineExtensions/Package.swift new file mode 100644 index 0000000000..b4dcd38a7a --- /dev/null +++ b/LocalPackages/CombineExtensions/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "CombineExtensions", + platforms: [ + .macOS("11.4") + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "CombineExtensions", + targets: ["CombineExtensions"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "CombineExtensions"), + .testTarget( + name: "CombineExtensionsTests", + dependencies: ["CombineExtensions"] + ), + ] +) diff --git a/LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift b/LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift new file mode 100644 index 0000000000..a250aea649 --- /dev/null +++ b/LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift @@ -0,0 +1,36 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +import Combine +import Foundation + +public final class CurrentValuePublisher { + + private(set) public var value: Output + private let wrappedPublisher: AnyPublisher + private var cancellable: AnyCancellable? + + public init(initialValue: Output, publisher: AnyPublisher) { + value = initialValue + wrappedPublisher = publisher + + subscribeToPublisherUpdates() + } + + private func subscribeToPublisherUpdates() { + cancellable = wrappedPublisher + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { _ in }) { [weak self] value in + self?.value = value + } + } +} + +extension CurrentValuePublisher: Publisher { + public func receive(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { + + wrappedPublisher.receive(subscriber: subscriber) + } + + +} diff --git a/LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift b/LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift new file mode 100644 index 0000000000..c733bf224b --- /dev/null +++ b/LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift @@ -0,0 +1,6 @@ +import Testing +@testable import CombineExtensions + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 92ee8f95e3..7b6c903fe7 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -35,6 +35,7 @@ let package = Package( .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "199.1.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), + .package(path: "../CombineExtensions"), .package(path: "../UDSHelper"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), @@ -101,9 +102,11 @@ let package = Package( .target( name: "NetworkProtectionUI", dependencies: [ + "CombineExtensions", "VPNPixels", .product(name: "NetworkProtection", package: "BrowserServicesKit"), .product(name: "PixelKit", package: "BrowserServicesKit"), + .product(name: "TipKitUtils", package: "BrowserServicesKit"), .product(name: "SwiftUIExtensions", package: "SwiftUIExtensions"), .product(name: "LoginItems", package: "LoginItems"), .product(name: "Lottie", package: "lottie-spm") diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/LottieView+withIntro.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/LottieView+withIntro.swift index bfa0e14367..a4cfe5d07c 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/LottieView+withIntro.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/LottieView+withIntro.swift @@ -28,14 +28,14 @@ extension LottieView where Placeholder: View { let loopEndFrame: AnimationFrameTime } - public func playing(withIntro timing: LoopWithIntroTiming, isAnimating: Binding = .constant(true)) -> Lottie.LottieView { + public func playing(withIntro timing: LoopWithIntroTiming, isAnimating: Bool = true) -> Lottie.LottieView { configure { uiView in - if uiView.isAnimationPlaying, !isAnimating.wrappedValue { + if uiView.isAnimationPlaying, !isAnimating { uiView.stop() return } - guard isAnimating.wrappedValue, !uiView.isAnimationPlaying else { return } + guard isAnimating, !uiView.isAnimationPlaying else { return } if uiView.loopMode == .playOnce, uiView.currentProgress == 1 { return } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 34ac66d9ae..9ac6b06f56 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -19,9 +19,11 @@ import AppKit import Foundation import Combine +import CombineExtensions import SwiftUI import NetworkProtection import LoginItems +import TipKitUtils /// Abstraction of the the VPN status bar menu with a simple interface. /// @@ -139,6 +141,12 @@ public final class StatusBarMenu: NSObject { siteTroubleshootingInfoPublisher: Just(SiteTroubleshootingInfo?(nil)).eraseToAnyPublisher(), uiActionHandler: uiActionHandler) + // TODO: replace with access to actual feature flag + let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) + + let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, + forMenuApp: true) + let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed) let statusViewModel = NetworkProtectionStatusView.Model( @@ -157,6 +165,7 @@ public final class StatusBarMenu: NSObject { statusViewModel: statusViewModel, statusReporter: statusReporter, siteTroubleshootingViewModel: siteTroubleshootingViewModel, + tipsModel: tipsModel, debugInformationViewModel: debugInformationViewModel) popover?.behavior = .transient diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/NetworkProtectionPopover.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/NetworkProtectionPopover.swift index 9eacb83bd6..a39f19de8e 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/NetworkProtectionPopover.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/NetworkProtectionPopover.swift @@ -32,16 +32,19 @@ public final class NetworkProtectionPopover: NSPopover { private let debugInformationViewModel: DebugInformationViewModel private let siteTroubleshootingViewModel: SiteTroubleshootingView.Model private let statusViewModel: NetworkProtectionStatusView.Model + private let tipsModel: VPNTipsModel private var appLifecycleCancellables = Set() public required init(statusViewModel: NetworkProtectionStatusView.Model, statusReporter: NetworkProtectionStatusReporter, siteTroubleshootingViewModel: SiteTroubleshootingView.Model, + tipsModel: VPNTipsModel, debugInformationViewModel: DebugInformationViewModel) { self.statusReporter = statusReporter self.debugInformationViewModel = debugInformationViewModel self.siteTroubleshootingViewModel = siteTroubleshootingViewModel + self.tipsModel = tipsModel self.statusViewModel = statusViewModel super.init() @@ -62,6 +65,7 @@ public final class NetworkProtectionPopover: NSPopover { .environmentObject(debugInformationViewModel) .environmentObject(siteTroubleshootingViewModel) .environmentObject(statusViewModel) + .environmentObject(tipsModel) .environment(\.dismiss, { [weak self] in self?.close() }).fixedSize() diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift similarity index 73% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index c26a8f7b0c..2be1a214db 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNUseSnoozeTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -1,5 +1,5 @@ // -// VPNUseSnoozeTip.swift +// VPNAutoconnectTip.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -19,32 +19,32 @@ import TipKit -/// A tip to suggest to the user to use the snooze feature to momentarily disable the VPN +/// A tip to suggest to the user to use the autoconnect option for the VPN. /// -struct VPNUseSnoozeTip {} +struct VPNAutoconnectTip {} /// Necessary split to support older iOS versions. /// @available(macOS 14.0, *) -extension VPNUseSnoozeTip: Tip { +extension VPNAutoconnectTip: Tip { enum ActionIdentifiers: String { case learnMore = "com.duckduckgo.tipkit.VPNUseSnoozeTip.learnMoreId" } - @Parameter(.transient) - static var vpnEnabled: Bool = false + //@Parameter(.transient) + //static var vpnEnabled: Bool = false var id: String { - "com.duckduckgo.tipkit.VPNUseSnoozeTip" + "com.duckduckgo.vpn.tip.autoconnect" } var title: Text { - Text("Avoid VPN Conflicts") + Text("Connect Automatically") } var message: Text? { - Text("Snooze briefly disconnects the VPN so you can use sites or apps that block VPN traffic.") + Text("The VPN can connect on its own when you log in to your computer.") } var image: Image? { @@ -53,14 +53,14 @@ extension VPNUseSnoozeTip: Tip { var actions: [Action] { [Action(id: ActionIdentifiers.learnMore.rawValue) { - Text("Learn more") + Text("Enable") .foregroundStyle(Color(.linkColor)) }] } - +/* var rules: [Rule] { #Rule(Self.$vpnEnabled) { $0 == true } - } + }*/ } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift similarity index 53% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 839bbb9052..941104b8ae 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNAddWidgetTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -1,5 +1,5 @@ // -// VPNAddWidgetTip.swift +// VPNDomainExclusionsTip.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -19,50 +19,47 @@ import TipKit -/// A tip to suggest to the user that they add our VPN widget for quick access to the VPN +/// A tip to suggest using domain exclusions when a site doesn't work. /// -struct VPNAddWidgetTip {} +struct VPNDomainExclusionsTip {} /// Necessary split to support older iOS versions. /// @available(macOS 14.0, *) -extension VPNAddWidgetTip: Tip { - - enum ActionIdentifiers: String { - case addWidget = "com.duckduckgo.tipkit.VPNAddWidgetTip.addWidget" - } +extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var vpnEnabled: Bool = false - private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNAddWidgetTip.vpnDisconnectedEvent") + /// The containing view was opened when the VPN was already connected. + /// + /// 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") var id: String { - "com.duckduckgo.tipkit.VPNAddWidgetTip" + "com.duckduckgo.vpn.tip.domainExclusions" } var title: Text { - Text("Add VPN Widget") + Text("Website not working?") } var message: Text? { - Text("Turn the VPN on and off right from the Home Screen.") + Text("Exclude websites that block VPN traffic so you can use them without turning the VPN off.") } var image: Image? { - Image(systemName: "rectangle.and.hand.point.up.left.fill") - } - - var actions: [Action] { - [Action(id: ActionIdentifiers.addWidget.rawValue) { - Text("Add widget") - .foregroundStyle(Color(.linkColor)) - }] + Image(systemName: "custom.globe.badge.xmark") } var rules: [Rule] { #Rule(Self.$vpnEnabled) { $0 == false } + #Rule(Self.viewOpenedWhehVPNAlreadyConnectedEvent) { + $0.donations.count > 1 + } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift similarity index 75% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift index ec6ad13651..b1e8f5ee3f 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/TipKit/VPNChangeLocationTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift @@ -1,5 +1,5 @@ // -// VPNChangeLocationTip.swift +// VPNGeoswitchingTip.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -21,15 +21,15 @@ import TipKit /// A tip to suggest to the user to change their location using geo-switching /// -struct VPNChangeLocationTip {} +struct VPNGeoswitchingTip {} @available(macOS 14.0, *) -extension VPNChangeLocationTip: Tip { +extension VPNGeoswitchingTip: Tip { - private static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNChangeLocationTip.vpnConnectedEvent") + static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.geoswitching.vpnConnectedEvent") var id: String { - "com.duckduckgo.tipkit.VPNChangeLocationTip" + "com.duckduckgo.vpn.tip.geoswitching" } var title: Text { @@ -49,10 +49,4 @@ extension VPNChangeLocationTip: Tip { $0.donations.donatedWithin(.week).count > 0 } } - - static func donateVPNConnectedEvent() { - Task { - await vpnConnectedEvent.donate() - } - } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift new file mode 100644 index 0000000000..ac4501fe83 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -0,0 +1,84 @@ +// +// VPNTipsModel.swift +// +// Copyright © 2024 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 AppKit +import Combine +import CombineExtensions +import NetworkProtection +import TipKitUtils + +public final class VPNTipsModel: ObservableObject { + + @Published + private(set) var featureFlag: Bool + let tips: TipGrouping + + private var cancellables = Set() + + static func makeTips(forMenuApp isMenuApp: Bool) -> TipGrouping { + // 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() + // } + // } + // } + if #available(macOS 14, *) { + if isMenuApp { + return LegacyTipGroup(.ordered) { + VPNGeoswitchingTip() + VPNDomainExclusionsTip() + VPNAutoconnectTip() + } + } else { + return LegacyTipGroup(.ordered) { + VPNGeoswitchingTip() + VPNAutoconnectTip() + } + } + } else { + return EmptyTipGroup() + } + } + + public init(featureFlagPublisher: CurrentValuePublisher, + forMenuApp isMenuApp: Bool) { + + self.featureFlag = featureFlagPublisher.value + self.tips = Self.makeTips(forMenuApp: isMenuApp) + + subscribeToFeatureFlagChanges(featureFlagPublisher) + } + + private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { + publisher + .receive(on: DispatchQueue.main) + .assign(to: \.featureFlag, onWeaklyHeld: self) + .store(in: &cancellables) + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift new file mode 100644 index 0000000000..d4cdcbcb0e --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift @@ -0,0 +1,45 @@ +// +// VPNAutoconnectTipView.swift +// +// Copyright © 2024 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 SwiftUI +import TipKit + +@available(macOS 14.0, *) +struct VPNAutoconnectTipView: View { + + // MARK: - Model + + @EnvironmentObject + var tipsModel: VPNTipsModel + + // MARK: - Body + + @ViewBuilder + public var body: some View { + + if tipsModel.featureFlag, + let tip = tipsModel.tips.currentTip as? VPNAutoconnectTip { + + TipView(tip) + //.removeGroupedListStyleInsets() + //.tipCornerRadius(0) + //.tipBackground(Color(designSystemColor: .surface)) + } + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift new file mode 100644 index 0000000000..fc102e8973 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift @@ -0,0 +1,45 @@ +// +// VPNDomainExclusionsTipView.swift +// +// Copyright © 2024 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 SwiftUI +import TipKit + +@available(macOS 14.0, *) +struct VPNDomainExclusionsTipView: View { + + // MARK: - Model + + @EnvironmentObject + var tipsModel: VPNTipsModel + + // MARK: - Body + + @ViewBuilder + public var body: some View { + + if tipsModel.featureFlag, + let tip = tipsModel.tips.currentTip as? VPNGeoswitchingTip { + + TipView(tip) + //.removeGroupedListStyleInsets() + //.tipCornerRadius(0) + //.tipBackground(Color(designSystemColor: .surface)) + } + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift new file mode 100644 index 0000000000..3452bfff19 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift @@ -0,0 +1,44 @@ +// +// VPNGeoswitchingTipView.swift +// +// Copyright © 2024 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 SwiftUI +import TipKit + +@available(macOS 14.0, *) +struct VPNGeoswitchingTipView: View { + + // MARK: - Model + + @EnvironmentObject + var tipsModel: VPNTipsModel + + // MARK: - Body + + @ViewBuilder + public var body: some View { + if tipsModel.featureFlag, + let tip = tipsModel.tips.currentTip as? VPNDomainExclusionsTip { + + TipView(tip) + //.removeGroupedListStyleInsets() + //.tipCornerRadius(0) + //.tipBackground(Color(designSystemColor: .surface)) + } + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index a75ea8f6b9..8c1ce3cd46 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -21,6 +21,7 @@ import SwiftUIExtensions import Combine import NetworkProtection import Lottie +import TipKit public struct TunnelControllerView: View { @@ -52,6 +53,12 @@ public struct TunnelControllerView: View { SiteTroubleshootingView() .padding(.top, 5) + if #available(macOS 15.0, *) { + VPNDomainExclusionsTipView() + .padding(.horizontal, 9) + .padding(.vertical, 6) + } + Divider() .padding(EdgeInsets(top: 5, leading: 9, bottom: 5, trailing: 9)) @@ -62,6 +69,11 @@ public struct TunnelControllerView: View { .disabled(on: !isEnabled) } } + .onAppear { + Task { + await model.handleTunnelControllerShown() + } + } } // MARK: - Composite Views @@ -105,7 +117,7 @@ public struct TunnelControllerView: View { introEndFrame: 100, loopStartFrame: 130, loopEndFrame: 370 - ), isAnimating: $model.isVPNEnabled) + ), isAnimating: model.isVPNEnabled) } @ViewBuilder @@ -168,6 +180,12 @@ public struct TunnelControllerView: View { } } + if #available(macOS 15.0, *) { + VPNGeoswitchingTipView() + .padding(.horizontal, 9) + .padding(.vertical, 6) + } + dividerRow() } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift index 72ec1f22d0..35a5bc317a 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift @@ -20,6 +20,8 @@ import Combine import Foundation import NetworkProtection import SwiftUI +import TipKitUtils +import TipKit @MainActor public final class TunnelControllerViewModel: ObservableObject { @@ -34,8 +36,17 @@ public final class TunnelControllerViewModel: ObservableObject { /// Whether the VPN is enabled /// This is determined based on the connection status, same as the iOS version - @Published - public var isVPNEnabled = false + /// + public var isVPNEnabled: Bool { + get { + switch connectionStatus { + case .connected, .connecting: + return true + default: + return false + } + } + } /// The type of extension that's being used for NetP /// @@ -68,11 +79,6 @@ public final class TunnelControllerViewModel: ObservableObject { private let uiActionHandler: VPNUIActionHandling - // MARK: - Environment - - @EnvironmentObject - private var siteTroubleshootingViewModel: SiteTroubleshootingView.Model - // MARK: - Misc /// The `RunLoop` for the timer. @@ -80,13 +86,6 @@ public final class TunnelControllerViewModel: ObservableObject { private let runLoopMode: RunLoop.Mode? private var cancellables = Set() - // MARK: - Dispatch Queues - - private static let statusDispatchQueue = DispatchQueue(label: "com.duckduckgo.NetworkProtectionStatusView.statusDispatchQueue", qos: .userInteractive) - private static let connectivityIssuesDispatchQueue = DispatchQueue(label: "com.duckduckgo.NetworkProtectionStatusView.connectivityIssuesDispatchQueue", qos: .userInteractive) - private static let serverInfoDispatchQueue = DispatchQueue(label: "com.duckduckgo.NetworkProtectionStatusView.serverInfoDispatchQueue", qos: .userInteractive) - private static let dataVolumeDispatchQueue = DispatchQueue(label: "com.duckduckgo.NetworkProtectionStatusView.dataVolumeDispatchQueue", qos: .userInteractive) - // MARK: - Initialization & Deinitialization public init(controller: TunnelController, @@ -106,6 +105,8 @@ public final class TunnelControllerViewModel: ObservableObject { self.uiActionHandler = uiActionHandler connectionStatus = statusReporter.statusObserver.recentValue + dnsSettings = vpnSettings.dnsSettings + formattedDataVolume = statusReporter.dataVolumeObserver.recentValue.formatted(using: Self.byteCountFormatter) internalServerAddress = statusReporter.serverInfoObserver.recentValue.serverAddress internalServerAttributes = statusReporter.serverInfoObserver.recentValue.serverLocation @@ -140,29 +141,15 @@ public final class TunnelControllerViewModel: ObservableObject { private func subscribeToStatusChanges() { statusReporter.statusObserver.publisher - .subscribe(on: Self.statusDispatchQueue) - .sink { [weak self] status in - - guard let self else { - return - } - - Task { @MainActor in - self.connectionStatus = status - switch status { - case .connected, .connecting: - self.isVPNEnabled = true - default: - self.isVPNEnabled = false - } - } - } + .removeDuplicates() + .receive(on: DispatchQueue.main) + .assign(to: \.connectionStatus, onWeaklyHeld: self) .store(in: &cancellables) } private func subscribeToServerInfoChanges() { statusReporter.serverInfoObserver.publisher - .subscribe(on: Self.serverInfoDispatchQueue) + .receive(on: DispatchQueue.main) .sink { [weak self] serverInfo in guard let self else { @@ -180,7 +167,6 @@ public final class TunnelControllerViewModel: ObservableObject { private func subscribeToDataVolumeUpdates() { statusReporter.dataVolumeObserver.publisher - .subscribe(on: Self.dataVolumeDispatchQueue) .map { $0.formatted(using: Self.byteCountFormatter) } .receive(on: DispatchQueue.main) .assign(to: \.formattedDataVolume, onWeaklyHeld: self) @@ -286,7 +272,7 @@ public final class TunnelControllerViewModel: ObservableObject { @MainActor @Published - private var connectionStatus: NetworkProtection.ConnectionStatus = .default { + private var connectionStatus: NetworkProtection.ConnectionStatus { didSet { detectAndRefreshExternalToggleSwitching() previousConnectionStatus = oldValue @@ -298,6 +284,7 @@ public final class TunnelControllerViewModel: ObservableObject { /// This method serves as a simple mechanism to detect when the toggle is controlled by the agent app, or by another /// external event causing the tunnel to start or stop, so we can disable the toggle as it's transitioning.. /// + @MainActor private func detectAndRefreshExternalToggleSwitching() { switch toggleTransition { case .idle: @@ -321,7 +308,6 @@ public final class TunnelControllerViewModel: ObservableObject { // MARK: - Connection Status: Toggle State - @frozen enum ToggleTransition: Equatable { case idle case switchingOn(locallyInitiated: Bool) @@ -350,6 +336,7 @@ public final class TunnelControllerViewModel: ObservableObject { /// @Published var timeLapsed = UserText.networkProtectionStatusViewTimerZero + @MainActor private func refreshTimeLapsed() { switch connectionStatus { case .connected(let connectedDate): @@ -477,7 +464,7 @@ public final class TunnelControllerViewModel: ObservableObject { private var internalServerAttributes: NetworkProtectionServerInfo.ServerAttributes? @Published - var dnsSettings: NetworkProtectionDNSSettings = .default + var dnsSettings: NetworkProtectionDNSSettings @Published var formattedDataVolume: FormattedDataVolume @@ -516,6 +503,10 @@ public final class TunnelControllerViewModel: ObservableObject { } Task { @MainActor in + if #available(macOS 14.0, *) { + await VPNGeoswitchingTip.vpnConnectedEvent.donate() + } + await tunnelController.start() refreshInternalIsRunning() } @@ -545,6 +536,14 @@ public final class TunnelControllerViewModel: ObservableObject { } } #endif + + // MARK: - UI Events + + func handleTunnelControllerShown() async { + if #available(macOS 14.0, *) { + await VPNDomainExclusionsTip.viewOpenedWhehVPNAlreadyConnectedEvent.donate() + } + } } extension DataVolume { From c5723b2a5126568a41df8a2106c533f48fbecd28 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 09:42:11 +0200 Subject: [PATCH 03/34] WIP --- .../custom.globe.badge.minus.imageset/Contents.json | 12 ++++++++++++ .../custom.globe.badge.minus.svg | 12 ++++++++++++ .../TipViews/Model/VPNDomainExclusionsTip.swift | 2 +- .../Views/TipViews/Model/VPNTipsModel.swift | 2 +- .../Views/TipViews/VPNAutoconnectTipView.swift | 3 --- .../Views/TipViews/VPNDomainExclusionsTipView.swift | 5 +---- .../Views/TipViews/VPNGeoswitchingTipView.swift | 5 +---- 7 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json new file mode 100644 index 0000000000..d7bf1da832 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "custom.globe.badge.minus.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg new file mode 100644 index 0000000000..7c4d10dc50 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 941104b8ae..251b54fa8e 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -51,7 +51,7 @@ extension VPNDomainExclusionsTip: Tip { } var image: Image? { - Image(systemName: "custom.globe.badge.xmark") + Image(.customGlobeBadgeMinus) } var rules: [Rule] { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index ac4501fe83..c4f7ee7214 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -52,12 +52,12 @@ public final class VPNTipsModel: ObservableObject { if isMenuApp { return LegacyTipGroup(.ordered) { VPNGeoswitchingTip() - VPNDomainExclusionsTip() VPNAutoconnectTip() } } else { return LegacyTipGroup(.ordered) { VPNGeoswitchingTip() + VPNDomainExclusionsTip() VPNAutoconnectTip() } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift index d4cdcbcb0e..1082ff299c 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift @@ -37,9 +37,6 @@ struct VPNAutoconnectTipView: View { let tip = tipsModel.tips.currentTip as? VPNAutoconnectTip { TipView(tip) - //.removeGroupedListStyleInsets() - //.tipCornerRadius(0) - //.tipBackground(Color(designSystemColor: .surface)) } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift index fc102e8973..f068902345 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift @@ -34,12 +34,9 @@ struct VPNDomainExclusionsTipView: View { public var body: some View { if tipsModel.featureFlag, - let tip = tipsModel.tips.currentTip as? VPNGeoswitchingTip { + let tip = tipsModel.tips.currentTip as? VPNDomainExclusionsTip { TipView(tip) - //.removeGroupedListStyleInsets() - //.tipCornerRadius(0) - //.tipBackground(Color(designSystemColor: .surface)) } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift index 3452bfff19..8ef6442b54 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift @@ -33,12 +33,9 @@ struct VPNGeoswitchingTipView: View { @ViewBuilder public var body: some View { if tipsModel.featureFlag, - let tip = tipsModel.tips.currentTip as? VPNDomainExclusionsTip { + let tip = tipsModel.tips.currentTip as? VPNGeoswitchingTip { TipView(tip) - //.removeGroupedListStyleInsets() - //.tipCornerRadius(0) - //.tipBackground(Color(designSystemColor: .surface)) } } } From 9c54287e0758877034bf8d0a40df2b9a9133dad5 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 10:10:00 +0200 Subject: [PATCH 04/34] WIP --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- ...etworkProtectionNavBarPopoverManager.swift | 2 +- LocalPackages/CombineExtensions/.gitignore | 8 ----- LocalPackages/CombineExtensions/Package.swift | 27 -------------- .../CurrentValuePublisher.swift | 36 ------------------- .../CombineExtensionsTests.swift | 6 ---- .../NetworkProtectionMac/Package.swift | 2 -- .../Menu/StatusBarMenu.swift | 3 +- .../Model/VPNDomainExclusionsTip.swift | 2 +- .../Views/TipViews/Model/VPNTipsModel.swift | 32 +++++++++++++++-- 10 files changed, 34 insertions(+), 86 deletions(-) delete mode 100644 LocalPackages/CombineExtensions/.gitignore delete mode 100644 LocalPackages/CombineExtensions/Package.swift delete mode 100644 LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift delete mode 100644 LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3a07413cd4..130ad3e96d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3886,7 +3886,6 @@ 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; - 7BDBAD252CC02612000379B7 /* CombineExtensions */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = CombineExtensions; sourceTree = ""; }; 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugMenu.swift; sourceTree = ""; }; 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SystemExtensionManager; sourceTree = ""; }; 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopoverView.swift; sourceTree = ""; }; @@ -5535,7 +5534,6 @@ 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */, 7B8594172B5B25FB0007EB3E /* UDSHelper */, 7B76E6852AD5D77600186A84 /* XPCHelper */, - 7BDBAD252CC02612000379B7 /* CombineExtensions */, ); path = LocalPackages; sourceTree = ""; diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index e56de36b69..1c2b471484 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -19,7 +19,6 @@ import AppLauncher import AppKit import Combine -import CombineExtensions import Common import Foundation import LoginItems @@ -161,6 +160,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, + statusObserver: statusReporter.statusObserver, forMenuApp: false) let popover = NetworkProtectionPopover( diff --git a/LocalPackages/CombineExtensions/.gitignore b/LocalPackages/CombineExtensions/.gitignore deleted file mode 100644 index 0023a53406..0000000000 --- a/LocalPackages/CombineExtensions/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/CombineExtensions/Package.swift b/LocalPackages/CombineExtensions/Package.swift deleted file mode 100644 index b4dcd38a7a..0000000000 --- a/LocalPackages/CombineExtensions/Package.swift +++ /dev/null @@ -1,27 +0,0 @@ -// swift-tools-version: 6.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "CombineExtensions", - platforms: [ - .macOS("11.4") - ], - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "CombineExtensions", - targets: ["CombineExtensions"]), - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .target( - name: "CombineExtensions"), - .testTarget( - name: "CombineExtensionsTests", - dependencies: ["CombineExtensions"] - ), - ] -) diff --git a/LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift b/LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift deleted file mode 100644 index a250aea649..0000000000 --- a/LocalPackages/CombineExtensions/Sources/CombineExtensions/CurrentValuePublisher.swift +++ /dev/null @@ -1,36 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book - -import Combine -import Foundation - -public final class CurrentValuePublisher { - - private(set) public var value: Output - private let wrappedPublisher: AnyPublisher - private var cancellable: AnyCancellable? - - public init(initialValue: Output, publisher: AnyPublisher) { - value = initialValue - wrappedPublisher = publisher - - subscribeToPublisherUpdates() - } - - private func subscribeToPublisherUpdates() { - cancellable = wrappedPublisher - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { [weak self] value in - self?.value = value - } - } -} - -extension CurrentValuePublisher: Publisher { - public func receive(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { - - wrappedPublisher.receive(subscriber: subscriber) - } - - -} diff --git a/LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift b/LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift deleted file mode 100644 index c733bf224b..0000000000 --- a/LocalPackages/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Testing -@testable import CombineExtensions - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 7b6c903fe7..9c51f12b34 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -35,7 +35,6 @@ let package = Package( .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "199.1.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), - .package(path: "../CombineExtensions"), .package(path: "../UDSHelper"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), @@ -102,7 +101,6 @@ let package = Package( .target( name: "NetworkProtectionUI", dependencies: [ - "CombineExtensions", "VPNPixels", .product(name: "NetworkProtection", package: "BrowserServicesKit"), .product(name: "PixelKit", package: "BrowserServicesKit"), diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 9ac6b06f56..201048dc94 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -19,7 +19,7 @@ import AppKit import Foundation import Combine -import CombineExtensions +import Common import SwiftUI import NetworkProtection import LoginItems @@ -145,6 +145,7 @@ public final class StatusBarMenu: NSObject { let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, + statusObserver: statusReporter.statusObserver, forMenuApp: true) let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 251b54fa8e..c6804acf0f 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -56,7 +56,7 @@ extension VPNDomainExclusionsTip: Tip { var rules: [Rule] { #Rule(Self.$vpnEnabled) { - $0 == false + $0 == true } #Rule(Self.viewOpenedWhehVPNAlreadyConnectedEvent) { $0.donations.count > 1 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index c4f7ee7214..b906e190de 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -18,7 +18,7 @@ import AppKit import Combine -import CombineExtensions +import Common import NetworkProtection import TipKitUtils @@ -67,18 +67,46 @@ public final class VPNTipsModel: ObservableObject { } public init(featureFlagPublisher: CurrentValuePublisher, + statusObserver: ConnectionStatusObserver, forMenuApp isMenuApp: Bool) { self.featureFlag = featureFlagPublisher.value self.tips = Self.makeTips(forMenuApp: isMenuApp) - subscribeToFeatureFlagChanges(featureFlagPublisher) + if #available(macOS 14.0, *) { + subscribeToConnectionStatusChanges(statusObserver) + subscribeToFeatureFlagChanges(featureFlagPublisher) + } } + @available(macOS 14.0, *) private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { publisher .receive(on: DispatchQueue.main) .assign(to: \.featureFlag, onWeaklyHeld: self) .store(in: &cancellables) } + + @available(macOS 14.0, *) + private func subscribeToConnectionStatusChanges(_ statusObserver: ConnectionStatusObserver) { + statusObserver.publisher + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] status in + self?.updateVPNConnectionStatusInWidgets(status) + } + .store(in: &cancellables) + } + + // MARK: - Refreshing Tips + + @available(macOS 14.0, *) + private func updateVPNConnectionStatusInWidgets(_ status: ConnectionStatus) { + switch status { + case .connected: + VPNDomainExclusionsTip.vpnEnabled = true + default: + VPNDomainExclusionsTip.vpnEnabled = false + } + } } From 59b9348568e9a1d855dbee7dbf31a79c03128a75 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 10:27:48 +0200 Subject: [PATCH 05/34] Renames a few classes to improve readability --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +++---- ...er.swift => ActiveSiteInfoPublisher.swift} | 32 +++++++++---------- ...etworkProtectionNavBarPopoverManager.swift | 12 +++---- .../Menu/StatusBarMenu.swift | 3 +- .../Contents.json | 3 ++ .../custom.globe.badge.minus.svg | 0 .../ActiveSiteInfo.swift} | 8 ++--- .../SiteTroubleshootingView.swift | 2 +- .../SiteTroubleshootingViewModel.swift | 10 +++--- .../TipViews/Model/VPNAutoconnectTip.swift | 14 ++++---- .../Views/TipViews/Model/VPNTipsModel.swift | 3 ++ .../TunnelControllerView.swift | 6 ++++ 12 files changed, 58 insertions(+), 47 deletions(-) rename DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/{SiteTroubleshootingInfoPublisher.swift => ActiveSiteInfoPublisher.swift} (72%) rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/{Images => Icons}/custom.globe.badge.minus.imageset/Contents.json (71%) rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/{Images => Icons}/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg (100%) rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/{SiteTroubleshootingView/SiteTroubleshootingInfo.swift => ActiveSiteInfo/ActiveSiteInfo.swift} (88%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 130ad3e96d..cae08e488f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1754,8 +1754,8 @@ 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BB108592A43375D000AB95F /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BB108582A43375D000AB95F /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 7BB4BC632C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB4BC622C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift */; }; - 7BB4BC642C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB4BC622C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift */; }; + 7BB4BC632C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB4BC622C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift */; }; + 7BB4BC642C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB4BC622C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift */; }; 7BB4BC6A2C5CD96200E06FC8 /* ActiveDomainPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB4BC692C5CD96200E06FC8 /* ActiveDomainPublisher.swift */; }; 7BB4BC6B2C5CD96200E06FC8 /* ActiveDomainPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB4BC692C5CD96200E06FC8 /* ActiveDomainPublisher.swift */; }; 7BBD45B12A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; @@ -3870,7 +3870,7 @@ 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionIPCTunnelController.swift; sourceTree = ""; }; 7BB108572A43375D000AB95F /* PFMoveApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = ""; }; 7BB108582A43375D000AB95F /* PFMoveApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; - 7BB4BC622C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteTroubleshootingInfoPublisher.swift; sourceTree = ""; }; + 7BB4BC622C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSiteInfoPublisher.swift; sourceTree = ""; }; 7BB4BC692C5CD96200E06FC8 /* ActiveDomainPublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveDomainPublisher.swift; sourceTree = ""; }; 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugUtilities.swift; sourceTree = ""; }; 7BCB90C12C18626E008E3543 /* VPNControllerXPCClient+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VPNControllerXPCClient+ConvenienceInitializers.swift"; sourceTree = ""; }; @@ -5872,7 +5872,7 @@ isa = PBXGroup; children = ( 7BB4BC692C5CD96200E06FC8 /* ActiveDomainPublisher.swift */, - 7BB4BC622C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift */, + 7BB4BC622C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift */, 4B4D60722A0B29FA00BCD287 /* EventMapping+NetworkProtectionError.swift */, BDE981DB2BBD110800645880 /* Assets */, 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */, @@ -11050,7 +11050,7 @@ 3706FBBC293F65D500E42796 /* NSViewExtension.swift in Sources */, 3706FBBE293F65D500E42796 /* DownloadListViewModel.swift in Sources */, 3706FBBF293F65D500E42796 /* BookmarkManagementDetailViewController.swift in Sources */, - 7BB4BC642C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift in Sources */, + 7BB4BC642C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift in Sources */, 841BE93F2C6F236000E9C2B5 /* BookmarkDragDropManager.swift in Sources */, B6B4D1CB2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, F188268E2BBF01C400D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, @@ -12344,7 +12344,7 @@ 1D220BFC2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */, AA512D1424D99D9800230283 /* FaviconManager.swift in Sources */, 7BB108592A43375D000AB95F /* PFMoveApplication.m in Sources */, - 7BB4BC632C5BC13D00E06FC8 /* SiteTroubleshootingInfoPublisher.swift in Sources */, + 7BB4BC632C5BC13D00E06FC8 /* ActiveSiteInfoPublisher.swift in Sources */, 4B0AACAC28BC63ED001038AC /* ChromiumFaviconsReader.swift in Sources */, AABEE6AB24ACA0F90043105B /* SuggestionTableRowView.swift in Sources */, 37CD54CB27F2FDD100F1F7B9 /* DownloadsPreferences.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/SiteTroubleshootingInfoPublisher.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveSiteInfoPublisher.swift similarity index 72% rename from DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/SiteTroubleshootingInfoPublisher.swift rename to DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveSiteInfoPublisher.swift index 3facdea39e..ad5375594d 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/SiteTroubleshootingInfoPublisher.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveSiteInfoPublisher.swift @@ -1,5 +1,5 @@ // -// SiteTroubleshootingInfoPublisher.swift +// ActiveSiteInfoPublisher.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -22,15 +22,15 @@ import NetworkProtectionProxy import NetworkProtectionUI @MainActor -final class SiteTroubleshootingInfoPublisher { +final class ActiveSiteInfoPublisher { private var activeDomain: String? { didSet { - refreshSiteTroubleshootingInfo() + refreshActiveSiteInfo() } } - private let subject: CurrentValueSubject + private let subject: CurrentValueSubject private let activeDomainPublisher: AnyPublisher private let proxySettings: TransparentProxySettings @@ -39,7 +39,7 @@ final class SiteTroubleshootingInfoPublisher { init(activeDomainPublisher: AnyPublisher, proxySettings: TransparentProxySettings) { - subject = CurrentValueSubject(nil) + subject = CurrentValueSubject(nil) self.activeDomainPublisher = activeDomainPublisher self.proxySettings = proxySettings @@ -59,7 +59,7 @@ final class SiteTroubleshootingInfoPublisher { switch change { case .excludedDomains: - refreshSiteTroubleshootingInfo() + refreshActiveSiteInfo() default: break } @@ -68,15 +68,15 @@ final class SiteTroubleshootingInfoPublisher { // MARK: - Refreshing - func refreshSiteTroubleshootingInfo() { - if activeSiteTroubleshootingInfo != subject.value { - subject.send(activeSiteTroubleshootingInfo) + func refreshActiveSiteInfo() { + if activeActiveSiteInfo != subject.value { + subject.send(activeActiveSiteInfo) } } // MARK: - Active Site Troubleshooting Info - var activeSiteTroubleshootingInfo: SiteTroubleshootingInfo? { + var activeActiveSiteInfo: ActiveSiteInfo? { guard let activeDomain else { return nil } @@ -84,13 +84,13 @@ final class SiteTroubleshootingInfoPublisher { return site(forDomain: activeDomain.droppingWwwPrefix()) } - private func site(forDomain domain: String) -> SiteTroubleshootingInfo? { + private func site(forDomain domain: String) -> ActiveSiteInfo? { let icon: NSImage? - let currentSite: NetworkProtectionUI.SiteTroubleshootingInfo? + let currentSite: NetworkProtectionUI.ActiveSiteInfo? icon = FaviconManager.shared.getCachedFavicon(forDomainOrAnySubdomain: domain, sizeCategory: .small)?.image let proxySettings = TransparentProxySettings(defaults: .netP) - currentSite = NetworkProtectionUI.SiteTroubleshootingInfo( + currentSite = NetworkProtectionUI.ActiveSiteInfo( icon: icon, domain: domain, excluded: proxySettings.isExcluding(domain: domain)) @@ -99,12 +99,12 @@ final class SiteTroubleshootingInfoPublisher { } } -extension SiteTroubleshootingInfoPublisher: Publisher { - typealias Output = SiteTroubleshootingInfo? +extension ActiveSiteInfoPublisher: Publisher { + typealias Output = ActiveSiteInfo? typealias Failure = Never nonisolated - func receive(subscriber: S) where S: Subscriber, Never == S.Failure, NetworkProtectionUI.SiteTroubleshootingInfo? == S.Input { + func receive(subscriber: S) where S: Subscriber, Never == S.Failure, NetworkProtectionUI.ActiveSiteInfo? == S.Input { subject.receive(subscriber: subscriber) } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 1c2b471484..532976e01a 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -54,8 +54,8 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let vpnUninstaller: VPNUninstalling @Published - private var siteInfo: SiteTroubleshootingInfo? - private let siteTroubleshootingInfoPublisher: SiteTroubleshootingInfoPublisher + private var siteInfo: ActiveSiteInfo? + private let activeSitePublisher: ActiveSiteInfoPublisher private var cancellables = Set() init(ipcClient: VPNControllerXPCClient, @@ -66,7 +66,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let activeDomainPublisher = ActiveDomainPublisher(windowControllersManager: .shared) - siteTroubleshootingInfoPublisher = SiteTroubleshootingInfoPublisher( + activeSitePublisher = ActiveSiteInfoPublisher( activeDomainPublisher: activeDomainPublisher.eraseToAnyPublisher(), proxySettings: TransparentProxySettings(defaults: .netP)) @@ -74,7 +74,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { } private func subscribeToCurrentSitePublisher() { - siteTroubleshootingInfoPublisher + activeSitePublisher .assign(to: \.siteInfo, onWeaklyHeld: self) .store(in: &cancellables) } @@ -86,7 +86,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { func show(positionedBelow view: NSView, withDelegate delegate: NSPopoverDelegate) -> NSPopover { /// Since the favicon doesn't have a publisher we force refreshing here - siteTroubleshootingInfoPublisher.refreshSiteTroubleshootingInfo() + activeSitePublisher.refreshActiveSiteInfo() let popover: NSPopover = { let controller = NetworkProtectionIPCTunnelController(ipcClient: ipcClient) @@ -110,7 +110,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( connectionStatusPublisher: statusReporter.statusObserver.publisher, - siteTroubleshootingInfoPublisher: $siteInfo.eraseToAnyPublisher(), + activeSitePublisher: $siteInfo.eraseToAnyPublisher(), uiActionHandler: uiActionHandler) let statusViewModel = NetworkProtectionStatusView.Model(controller: controller, diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 201048dc94..e725e7f4eb 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -138,7 +138,7 @@ public final class StatusBarMenu: NSObject { let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( connectionStatusPublisher: Just(NetworkProtection.ConnectionStatus.disconnected).eraseToAnyPublisher(), - siteTroubleshootingInfoPublisher: Just(SiteTroubleshootingInfo?(nil)).eraseToAnyPublisher(), + activeSitePublisher: Just(ActiveSiteInfo?(nil)).eraseToAnyPublisher(), uiActionHandler: uiActionHandler) // TODO: replace with access to actual feature flag @@ -146,6 +146,7 @@ public final class StatusBarMenu: NSObject { let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, + activeSitePublisher: Just(ActiveSiteInfo?(nil)).eraseToAnyPublisher(), forMenuApp: true) let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/Contents.json similarity index 71% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/Contents.json index d7bf1da832..41960a8b91 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg similarity index 100% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingInfo.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/ActiveSiteInfo/ActiveSiteInfo.swift similarity index 88% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingInfo.swift rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/ActiveSiteInfo/ActiveSiteInfo.swift index 33df4b11bb..3a20333bc7 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingInfo.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/ActiveSiteInfo/ActiveSiteInfo.swift @@ -1,5 +1,5 @@ // -// SiteTroubleshootingInfo.swift +// ActiveSiteInfo.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -19,7 +19,7 @@ import AppKit import Foundation -public struct SiteTroubleshootingInfo { +public struct ActiveSiteInfo { public let icon: NSImage? public let domain: String public let excluded: Bool @@ -31,6 +31,4 @@ public struct SiteTroubleshootingInfo { } } -extension SiteTroubleshootingInfo: Equatable { - -} +extension ActiveSiteInfo: Equatable {} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift index ccf2eae271..98551d3713 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift @@ -38,7 +38,7 @@ public struct SiteTroubleshootingView: View { } @ViewBuilder - private func siteTroubleshootingView(_ siteInfo: SiteTroubleshootingInfo) -> some View { + private func siteTroubleshootingView(_ siteInfo: ActiveSiteInfo) -> some View { Divider() .padding(EdgeInsets(top: 5, leading: 9, bottom: 5, trailing: 9)) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift index ceb9c6336a..78f31bec1e 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift @@ -31,9 +31,9 @@ extension SiteTroubleshootingView { private(set) var connectionStatus: ConnectionStatus = .disconnected @Published - private var internalSiteInfo: SiteTroubleshootingInfo? + private var internalSiteInfo: ActiveSiteInfo? - var siteInfo: SiteTroubleshootingInfo? { + var siteInfo: ActiveSiteInfo? { guard case .connected = connectionStatus else { return nil } @@ -46,7 +46,7 @@ extension SiteTroubleshootingView { private var cancellables = Set() public init(connectionStatusPublisher: AnyPublisher, - siteTroubleshootingInfoPublisher: AnyPublisher, + activeSitePublisher: AnyPublisher, uiActionHandler: VPNUIActionHandling, pixelKit: PixelFiring? = PixelKit.shared) { @@ -54,7 +54,7 @@ extension SiteTroubleshootingView { self.pixelKit = pixelKit subscribeToConnectionStatusChanges(connectionStatusPublisher) - subscribeToSiteTroubleshootingInfoChanges(siteTroubleshootingInfoPublisher) + subscribeToActiveSiteInfoChanges(activeSitePublisher) } private func subscribeToConnectionStatusChanges(_ publisher: AnyPublisher) { @@ -65,7 +65,7 @@ extension SiteTroubleshootingView { .store(in: &cancellables) } - private func subscribeToSiteTroubleshootingInfoChanges(_ publisher: AnyPublisher) { + private func subscribeToActiveSiteInfoChanges(_ publisher: AnyPublisher) { publisher .receive(on: DispatchQueue.main) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index 2be1a214db..bf7a5aeaff 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -29,11 +29,11 @@ struct VPNAutoconnectTip {} extension VPNAutoconnectTip: Tip { enum ActionIdentifiers: String { - case learnMore = "com.duckduckgo.tipkit.VPNUseSnoozeTip.learnMoreId" + case enable = "com.duckduckgo.vpn.tip.autoconnect.action.enable" } - //@Parameter(.transient) - //static var vpnEnabled: Bool = false + @Parameter(.transient) + static var vpnEnabled: Bool = false var id: String { "com.duckduckgo.vpn.tip.autoconnect" @@ -48,19 +48,19 @@ extension VPNAutoconnectTip: Tip { } var image: Image? { - Image(systemName: "powersleep") + Image(systemName: "point.3.filled.connected.trianglepath.dotted") } var actions: [Action] { - [Action(id: ActionIdentifiers.learnMore.rawValue) { + [Action(id: ActionIdentifiers.enable.rawValue) { Text("Enable") .foregroundStyle(Color(.linkColor)) }] } -/* + var rules: [Rule] { #Rule(Self.$vpnEnabled) { $0 == true } - }*/ + } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index b906e190de..5f726c29dc 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -68,6 +68,7 @@ public final class VPNTipsModel: ObservableObject { public init(featureFlagPublisher: CurrentValuePublisher, statusObserver: ConnectionStatusObserver, + activeSitePublisher: AnyPublisher, forMenuApp isMenuApp: Bool) { self.featureFlag = featureFlagPublisher.value @@ -104,8 +105,10 @@ public final class VPNTipsModel: ObservableObject { private func updateVPNConnectionStatusInWidgets(_ status: ConnectionStatus) { switch status { case .connected: + VPNAutoconnectTip.vpnEnabled = true VPNDomainExclusionsTip.vpnEnabled = true default: + VPNAutoconnectTip.vpnEnabled = false VPNDomainExclusionsTip.vpnEnabled = false } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 8c1ce3cd46..ee67349fd6 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -186,6 +186,12 @@ public struct TunnelControllerView: View { .padding(.vertical, 6) } + if #available(macOS 15.0, *) { + VPNAutoconnectTipView() + .padding(.horizontal, 9) + .padding(.vertical, 6) + } + dividerRow() } } From 2492fee470b574a3721ab53abc3d2c1632c73f08 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 10:47:22 +0200 Subject: [PATCH 06/34] Makes several improvements to the code so that some publishers offer an initial valu --- ...etworkProtectionNavBarPopoverManager.swift | 7 ++- .../Menu/StatusBarMenu.swift | 6 +- .../Model/VPNDomainExclusionsTip.swift | 6 ++ .../Views/TipViews/Model/VPNTipsModel.swift | 57 ++++++++++++++----- .../TunnelControllerView.swift | 10 ++-- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 532976e01a..bcb84184ef 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -108,9 +108,13 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let proxySettings = TransparentProxySettings(defaults: .netP) let uiActionHandler = VPNUIActionHandler(vpnURLEventHandler: vpnURLEventHandler, proxySettings: proxySettings) + let activeSitePublisher = CurrentValuePublisher( + initialValue: nil, + publisher: $siteInfo.eraseToAnyPublisher()) + let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( connectionStatusPublisher: statusReporter.statusObserver.publisher, - activeSitePublisher: $siteInfo.eraseToAnyPublisher(), + activeSitePublisher: activeSitePublisher.eraseToAnyPublisher(), uiActionHandler: uiActionHandler) let statusViewModel = NetworkProtectionStatusView.Model(controller: controller, @@ -161,6 +165,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, + activeSitePublisher: activeSitePublisher, forMenuApp: false) let popover = NetworkProtectionPopover( diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index e725e7f4eb..5b61902ec7 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -136,9 +136,11 @@ public final class StatusBarMenu: NSObject { return } + let activeSitePublisher = CurrentValuePublisher(initialValue: nil, publisher: Just(ActiveSiteInfo?(nil)).eraseToAnyPublisher()) + let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( connectionStatusPublisher: Just(NetworkProtection.ConnectionStatus.disconnected).eraseToAnyPublisher(), - activeSitePublisher: Just(ActiveSiteInfo?(nil)).eraseToAnyPublisher(), + activeSitePublisher: activeSitePublisher.eraseToAnyPublisher(), uiActionHandler: uiActionHandler) // TODO: replace with access to actual feature flag @@ -146,7 +148,7 @@ public final class StatusBarMenu: NSObject { let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, - activeSitePublisher: Just(ActiveSiteInfo?(nil)).eraseToAnyPublisher(), + activeSitePublisher: activeSitePublisher, forMenuApp: true) let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index c6804acf0f..e10c49fc4e 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -31,6 +31,9 @@ extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var vpnEnabled: Bool = false + @Parameter(.transient) + static var hasActiveSite: Bool = false + /// The containing view was opened when the VPN was already connected. /// /// This condition may be indicative that the user is struggling, so they might want @@ -55,6 +58,9 @@ extension VPNDomainExclusionsTip: Tip { } var rules: [Rule] { + #Rule(Self.$hasActiveSite) { + $0 == true + } #Rule(Self.$vpnEnabled) { $0 == true } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 5f726c29dc..fa6f786b79 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -24,6 +24,35 @@ import TipKitUtils public final class VPNTipsModel: ObservableObject { + @Published + private(set) var activeSiteInfo: ActiveSiteInfo? { + didSet { + guard #available(macOS 14.0, *) else { + return + } + + VPNDomainExclusionsTip.hasActiveSite = (activeSiteInfo != nil) + } + } + + @Published + private(set) var connectionStatus: ConnectionStatus { + didSet { + guard #available(macOS 14.0, *) else { + return + } + + switch connectionStatus { + case .connected: + VPNAutoconnectTip.vpnEnabled = true + VPNDomainExclusionsTip.vpnEnabled = true + default: + VPNAutoconnectTip.vpnEnabled = false + VPNDomainExclusionsTip.vpnEnabled = false + } + } + } + @Published private(set) var featureFlag: Bool let tips: TipGrouping @@ -68,21 +97,25 @@ public final class VPNTipsModel: ObservableObject { public init(featureFlagPublisher: CurrentValuePublisher, statusObserver: ConnectionStatusObserver, - activeSitePublisher: AnyPublisher, + activeSitePublisher: CurrentValuePublisher, forMenuApp isMenuApp: Bool) { + self.activeSiteInfo = activeSitePublisher.value + self.connectionStatus = statusObserver.recentValue self.featureFlag = featureFlagPublisher.value self.tips = Self.makeTips(forMenuApp: isMenuApp) if #available(macOS 14.0, *) { subscribeToConnectionStatusChanges(statusObserver) subscribeToFeatureFlagChanges(featureFlagPublisher) + subscribeToActiveSiteChanges(activeSitePublisher) } } @available(macOS 14.0, *) private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { publisher + .removeDuplicates() .receive(on: DispatchQueue.main) .assign(to: \.featureFlag, onWeaklyHeld: self) .store(in: &cancellables) @@ -93,23 +126,17 @@ public final class VPNTipsModel: ObservableObject { statusObserver.publisher .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [weak self] status in - self?.updateVPNConnectionStatusInWidgets(status) - } + .assign(to: \.connectionStatus, onWeaklyHeld: self) .store(in: &cancellables) } - // MARK: - Refreshing Tips - @available(macOS 14.0, *) - private func updateVPNConnectionStatusInWidgets(_ status: ConnectionStatus) { - switch status { - case .connected: - VPNAutoconnectTip.vpnEnabled = true - VPNDomainExclusionsTip.vpnEnabled = true - default: - VPNAutoconnectTip.vpnEnabled = false - VPNDomainExclusionsTip.vpnEnabled = false - } + private func subscribeToActiveSiteChanges(_ publisher: CurrentValuePublisher) { + + publisher + .removeDuplicates() + .receive(on: DispatchQueue.main) + .assign(to: \.activeSiteInfo, onWeaklyHeld: self) + .store(in: &cancellables) } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index ee67349fd6..f90d7fdff3 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -57,6 +57,10 @@ public struct TunnelControllerView: View { VPNDomainExclusionsTipView() .padding(.horizontal, 9) .padding(.vertical, 6) + + VPNAutoconnectTipView() + .padding(.horizontal, 9) + .padding(.vertical, 6) } Divider() @@ -186,12 +190,6 @@ public struct TunnelControllerView: View { .padding(.vertical, 6) } - if #available(macOS 15.0, *) { - VPNAutoconnectTipView() - .padding(.horizontal, 9) - .padding(.vertical, 6) - } - dividerRow() } } From 80b97e0e9fd02e56776303b8a6f6f00f403587c7 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 14:29:44 +0200 Subject: [PATCH 07/34] WIP --- .../NetworkProtectionNavBarPopoverManager.swift | 5 +++-- .../NetworkProtectionUI/Menu/StatusBarMenu.swift | 3 ++- .../Views/TipViews/Model/VPNTipsModel.swift | 15 ++++++++++++++- .../Views/TipViews/VPNAutoconnectTipView.swift | 2 +- .../TunnelControllerView.swift | 10 ++++++---- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index bcb84184ef..89b435c3af 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -89,6 +89,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { activeSitePublisher.refreshActiveSiteInfo() let popover: NSPopover = { + let vpnSettings = VPNSettings(defaults: .netP) let controller = NetworkProtectionIPCTunnelController(ipcClient: ipcClient) let statusReporter = DefaultNetworkProtectionStatusReporter( @@ -102,7 +103,6 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { ) let onboardingStatusPublisher = UserDefaults.netP.networkProtectionOnboardingStatusPublisher - _ = VPNSettings(defaults: .netP) let appLauncher = AppLauncher(appBundleURL: Bundle.main.bundleURL) let vpnURLEventHandler = VPNURLEventHandler() let proxySettings = TransparentProxySettings(defaults: .netP) @@ -166,7 +166,8 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, activeSitePublisher: activeSitePublisher, - forMenuApp: false) + forMenuApp: false, + vpnSettings: vpnSettings) let popover = NetworkProtectionPopover( statusViewModel: statusViewModel, diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 5b61902ec7..90f4f27a82 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -149,7 +149,8 @@ public final class StatusBarMenu: NSObject { let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, activeSitePublisher: activeSitePublisher, - forMenuApp: true) + forMenuApp: true, + vpnSettings: VPNSettings(defaults: userDefaults)) let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index fa6f786b79..27f9057a0c 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -20,6 +20,7 @@ import AppKit import Combine import Common import NetworkProtection +import TipKit import TipKitUtils public final class VPNTipsModel: ObservableObject { @@ -57,6 +58,7 @@ public final class VPNTipsModel: ObservableObject { private(set) var featureFlag: Bool let tips: TipGrouping + private let vpnSettings: VPNSettings private var cancellables = Set() static func makeTips(forMenuApp isMenuApp: Bool) -> TipGrouping { @@ -98,12 +100,14 @@ public final class VPNTipsModel: ObservableObject { public init(featureFlagPublisher: CurrentValuePublisher, statusObserver: ConnectionStatusObserver, activeSitePublisher: CurrentValuePublisher, - forMenuApp isMenuApp: Bool) { + forMenuApp isMenuApp: Bool, + vpnSettings: VPNSettings) { self.activeSiteInfo = activeSitePublisher.value self.connectionStatus = statusObserver.recentValue self.featureFlag = featureFlagPublisher.value self.tips = Self.makeTips(forMenuApp: isMenuApp) + self.vpnSettings = vpnSettings if #available(macOS 14.0, *) { subscribeToConnectionStatusChanges(statusObserver) @@ -139,4 +143,13 @@ public final class VPNTipsModel: ObservableObject { .assign(to: \.activeSiteInfo, onWeaklyHeld: self) .store(in: &cancellables) } + + // MARK: - Tip Action handling + + @available(macOS 14.0, *) + func autoconnectTipActionHandler(_ action: Tip.Action) { + if action.id == VPNAutoconnectTip.ActionIdentifiers.enable.rawValue { + vpnSettings.connectOnLogin = true + } + } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift index 1082ff299c..c484541551 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift @@ -36,7 +36,7 @@ struct VPNAutoconnectTipView: View { if tipsModel.featureFlag, let tip = tipsModel.tips.currentTip as? VPNAutoconnectTip { - TipView(tip) + TipView(tip, action: tipsModel.autoconnectTipActionHandler) } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index f90d7fdff3..7ebb3c8a1d 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -50,6 +50,12 @@ public struct TunnelControllerView: View { featureToggleRow() + if #available(macOS 15.0, *) { + VPNAutoconnectTipView() + .padding(.horizontal, 9) + .padding(.vertical, 6) + } + SiteTroubleshootingView() .padding(.top, 5) @@ -57,10 +63,6 @@ public struct TunnelControllerView: View { VPNDomainExclusionsTipView() .padding(.horizontal, 9) .padding(.vertical, 6) - - VPNAutoconnectTipView() - .padding(.horizontal, 9) - .padding(.vertical, 6) } Divider() From 5d0687fbd48f21a172283ca03aeecc7ce800296a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 14:32:54 +0200 Subject: [PATCH 08/34] Updated BSK --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++---- .../xcshareddata/swiftpm/Package.resolved | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cae08e488f..d0ead5690f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3830,7 +3830,6 @@ 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAppEvents.swift; sourceTree = ""; }; 7B2E52242A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAgentNotificationsPresenter.swift; sourceTree = ""; }; 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNavBarPopoverManager.swift; sourceTree = ""; }; - 7B3930002CBEA86A004C48E2 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionSimulateFailureMenu.swift; sourceTree = ""; }; 7B4C5CF42BE51D640007A164 /* VPNUninstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUninstallerTests.swift; sourceTree = ""; }; 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -5520,7 +5519,6 @@ 378E279C2970217400FCADA2 /* LocalPackages */ = { isa = PBXGroup; children = ( - 7B3930002CBEA86A004C48E2 /* BrowserServicesKit */, 9D9DE5712C63A96400D20B15 /* AppKitExtensions */, 7B9167A82C09E88800322310 /* AppLauncher */, 378E279D2970217400FCADA2 /* BuildToolPlugins */, @@ -14337,8 +14335,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 199.1.0; + kind = revision; + revision = c05ef03b3320ccecdace3b1d53d945313585c170; }; }; 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 06c52ecb68..0c2dfcbf1b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,14 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "revision" : "c05ef03b3320ccecdace3b1d53d945313585c170" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", From 3c6260fbe49b5f435d738d3bc45557be2bd4fcef Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 15:33:52 +0200 Subject: [PATCH 09/34] Changed the geolocation tip icon --- .../Icons/geolocationTip.imageset/Contents.json | 12 ++++++++++++ .../geolocationTip.imageset/Map-Pin-32 1.pdf | Bin 0 -> 2190 bytes .../TipViews/Model/VPNGeoswitchingTip.swift | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Map-Pin-32 1.pdf diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json new file mode 100644 index 0000000000..804359b240 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Map-Pin-32 1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Map-Pin-32 1.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Map-Pin-32 1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1b5222ff723acf3e86fe40242b2fad0628eb9c3a GIT binary patch literal 2190 zcmZXWc~nw)8^>G31vl(f(q4=km0~s`FqoEO4Jw*zt~qHW!8J4j14^YVcO5b%>riHy z<~rh@DU@q2>A07aX*JqdSsu-$G{{tCtYQ^$}38C;MJU`PxK7qkJXEE*__kQv4aWFFzt0le(E zyG)X0;BeU>|7YX@IuR<1=B^95ltIHW9TsZ4tx5R^cyADk_O#K z_1*mD_XV7R;`ZM>z}KJWkc*$jXNU8UwKZqvW?p`tADf+@zHjTE-c8s|eqL1dp(xia zsQV;gugnG<7VKGXsGLhyy{MvJYRk)oI7FerYf4TY zgIZBZt+KyUVZz*HDbe*F6zId9C36GsiW#ubyX6mZs@5qcI9#1OSvAO`VuM|I@p;eQ zdb4dFVTY~!V|8ghzc%quC2yQkwn?6Zu{ZNF-JOLP!oazIoHZ!XM95$)MyR9rYVs#nudF7@oI zcyWA7^d%x+$d}@Le$|hz+$r*C4Pp3b8}7Gu{%?OHel(z@#XD=_F7K1YD&FVw%Ey>s59aqV|)-4Ob~h zP3)o5Tet5z1z+utDX8w%D9qRnH?g}^I@N+ch@L%aXsfxiKErgvBtc`y^stAcs zIoGFOEwWZQmbxqB*}HLelrqWb%>=0KP=0k&U8C`D+m+Yt4>jcF9v>Ji?Zk41)2h@I z{W{|LHZrFyd=r1_sRG30lv2emjYTBDmxN50D;g%p9b&qwZFsjDm+PX9 zw?kFyD~6r&PkUXJq<9$U(|SqwD`%s_G~(-O2z}yMm7o`RX>*s|ofreR5h%d`2Xzr# z-dV_AaAzJcBtFjfNlQ89uP&t-CuQg5T*)MpvO>nIf$PVs72r=r^^c7lrVLmEDMsZPBqhqxVW9-iuqN**i}73~dVu_4~Y5 zJ9A^?^&zcrRab<@qhv&g%OB**x8dI8>S=PEilApRS4UySWe|r+%ncxAdY$ZpLhKuqb{sn#5G+j;uUV&& zu6sFx*lm3ymzC%+#uoRA7S1Zu0{0UIH4G{K|X z_(l7mO~?5h+YrWfwGD^i$Ak%ym#&PaSx2?@o%f(L$080y!-dEO&hx3csQFtjTSrIg z`9nz0p>F)cvs2A=h}a=|tgUe|{%pC~>~SX(+AW8QO?F$Ue?(>ytM~Q{BMx?W*^El( z8|!24SHfYhP7DvJOZGSYbhD^ubC#miKTc^(>S<1f6HSso2{tNgdUyV^K1}cQy5sq5 z6W>@LUqF9Q4n`h^Dmj~G`ho_t&bst#Ov z*{>3URgKWgBIN{L-ycj2?@8L@cTU{sl%U**nt3dNbol$&Rs4sxR$JjcLn3|X_**6D|Tb90g%bDCk4(kM)3UUBB z<>Yvh6U&;#F0B9(;}}SjwF<~5a0iy_ShG&ZVzK|~ zpYsKX%iA#fE|Yvf&PqXfV_ychrf?|?){*Z+L)l;e)D+kO{W8A%378QGcmg1kzZ%vQ zSn>eoiiDT-DYq2~YlgS@mt-be#P_GnWr_b!vM~J?$YoOk86X=f|9X3EWa~h4SjRvk u03Ap(`mz*^JB!5yJh17+PW-3};h?$S6|3o~=*)~&X#r2he6{fv$P literal 0 HcmV?d00001 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift index b1e8f5ee3f..9d609f39c8 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift @@ -41,7 +41,7 @@ extension VPNGeoswitchingTip: Tip { } var image: Image? { - Image(systemName: "globe.americas.fill") + Image(.geolocationTip) } var rules: [Rule] { From e6ccb08ffad4bc41dc15ef34a5f123b52f56191c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 16:53:27 +0200 Subject: [PATCH 10/34] Updated icons for VPN onboarding tips --- .../Contents.json | 5 +---- .../Icons/autoconnectTip.imageset/Moon-32 1.pdf | Bin 0 -> 1407 bytes .../custom.globe.badge.minus.svg | 12 ------------ .../domainExclusionsTip.imageset/Contents.json | 12 ++++++++++++ .../Widget-Add-32 1.pdf | Bin 0 -> 1763 bytes .../Views/TipViews/Model/VPNAutoconnectTip.swift | 2 +- .../TipViews/Model/VPNDomainExclusionsTip.swift | 2 +- 7 files changed, 15 insertions(+), 18 deletions(-) rename LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/{custom.globe.badge.minus.imageset => autoconnectTip.imageset}/Contents.json (50%) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Moon-32 1.pdf delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Widget-Add-32 1.pdf diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json similarity index 50% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/Contents.json rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json index 41960a8b91..d0573906e0 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json @@ -1,15 +1,12 @@ { "images" : [ { - "filename" : "custom.globe.badge.minus.svg", + "filename" : "Moon-32 1.pdf", "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Moon-32 1.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Moon-32 1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9648d1c6ea23c789c82aee593e576cc42148d224 GIT binary patch literal 1407 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~g0XjsW1oyUkL&l+t|;~nok>cn zd6O9;)h(S4J2Ks1<6*YjFK>GJs_l&4?8|TTCo284|M{@L|Nj5|wSRt}zklxje@i=? ze|PuE#`9;^)~D~6-XC}E_vX_(YVE2{OD1zYkhxc!=rNtga{t|ZGRwa27Ak-JIXdLJ zOswBzu|>DG8qAi|zfs_S^3aiMFY4~`oi&?wYS!|I*DH5@3R$$f^jvFA`kIL4S;8A? zj^(jW-1T$Dkv$j0y`R#ym=2+ zR)(i)?-lpjdUNGMD}nA8`MVDB-xuDt*YL-@3;xG*gv{FP8^!IdZfz>~&i;j?sM)&i z=Q-_TsWI;Tzr|UY-#@mC+n;2!zj|+*mQd@ zJu1EBsUOO8c+q*&ZDLz{W71qUy49^ybv+QEeT=QA*F+*yBZxV9ONU@v$gDLRAFNe) zArg#VH}hddYQ` z^rV8|o9mo|+vYY+jV(K7yViyCitTY7j?CHaj$VFAJA&RQ=%rqmVW>IXEo`IBdEr=& zunSw(ZFFz=_~F7^DciIQU0Hke9gMEuO>kqc=%r}=${FGC?(n#}7!?c&) z&v!~?&RR5m8PgL5J)fDvdK{0qT}rfGhAGt=^+j!L3uInu6#J&p*o%7$i+z~m7uKXb z(#-bvqA%utJTq(WGE)hkh^1d{C0-~@bLK2oUL)ju-$(ZAx1t8#Um2n6%~vEaaY-@l zTs|R@$L`Ol9of9dmI)c=)xII&7WZLY#zM2v-uoX^8(YNPhxv?TF&_8@Ch@96kT??y~8go`>K~W zXS>}D)+@~Y_m(AlNKQGPv-kNXiMqM>5FHE6LLrp}sS5f5iRrLB<(rz4ndp>Xp%ATLpl1LE2!^o{ObEe(nG}>- zoL^d$oLZ~^%GRL#2+E?)`FSO&c|aRMc^W1dkXQs1Q!s@JA?19K5WGat_s&cKI#mJW zgCGT%J1K)r?-?k|Rv0I6|ssI#*0z(4@FiRm1E@TXJCkP;fEKJQYgv>3`gbWQ#fnkH9(a_Kc=zOS9 zNl{{EPHGVsDE2&EfWe_roS&Pjsi2XWq6rBU{h<8(5(SWl!2zrvoLQ9$bUnB@Oe`t^ Qd&khw!jwx@)z#k(0ELME9RL6T literal 0 HcmV?d00001 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg deleted file mode 100644 index 7c4d10dc50..0000000000 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/custom.globe.badge.minus.imageset/custom.globe.badge.minus.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json new file mode 100644 index 0000000000..b5be95588c --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Widget-Add-32 1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Widget-Add-32 1.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Widget-Add-32 1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d3ee1fb1af8f576dc03ca6ca22cff442b8dcf0e4 GIT binary patch literal 1763 zcmZXVdo)ye9KhwQ)YPV3wv^Y^dW^)(Fr!f(JB;x*4C|R?m@6|o56uJRQM+X|C6mNZ z(Rv*zY*UHWqb0lIXzZ|IjEcfYl;R+z_KsJt?;rR4?&r++`}=;s_nglddgBRZDD&Mg z7z#iFtOyzm0BmdkJ3AQ2q$plt79Vp2flB9sYyd%^ler)sjAT(jC<6+F2guydjRq{C zpOYX2Rp4;hAejM+Uo(+?uTmScIc0W1<2u2ubb-ZM!^cdns{l@)bAf-`(Gk!v5oyfX z9Y9exsmdvP-EU_ekY&5@POkR;9WQxqLa3)_ztB*B@wS*Kvp+8rFa?p=7eiF~9xz`1 zNG6hhkA9Im7HsGCw>53X(AF+bZ`+9V_~4LKyFrvXmC$NFKQ~#chRJ#xL*3YxJKEnD zV_at?|B2G%5PCKa(KJed4fY$|rx>WbxZ4(4S=f4lFw}3bBbHH3$oCN2`Wa>Xb<4o* zVb#K|1yNFea#Dg+gMkP!bH40fvAIjq{xS_UA0!=%p~D{KsC21|{LRvEPWF;u431Qu z=(VTIZVTDZqcKs-$+w2}N3NGNW?p`VcPAVBfloIz3g;%QEA)#4v-@a| zs~#k%itkMnoDCFitRtPDI?PR(>8mZw(mtuO?;oF7RiWrvsJ>2Bk>{Y+b=ZtevVB{o zOSnmOQ_>qB-SpoMwf<@N{?&an*SwE&Fjm@#C2?tY*;o zY*^TRjt+jylfEjg(~0$@->dcHgL8w^Vwe8eddnT>;oalIB~H)9F6qt;($|I(ChPlOhXMVD7QjjkPfbZjeA zyUne1`lk0zbZX^*p#4YUlXYGU<%-Hle^dqI;~&p zH^;~2oZ^`&S*eEyD{bt)BSQ1|dweT3@m|dMuRI*unG5EkX`ymrMKe4inqNkqua&AT z?3`0OsEvK6@X@kRR?MVO&62TDNNe<}u+Xa*5*Y%=WU{y%AS85IQ+@|yKmg%Q-oN54 zPmn?-JFwz`5CCb8T#OF~ON$ls;j~hT1aeqBb|lCF6zWtsPoY6a7LyAyp&J$6T!G$X zHpBqTGWy7_3bblnggcc2wF)REAOS1)II?(9F6xW^IZKvZ*@o38l@tSVRxeaEwlugS znMKu)<(0F#v>@1BC<@PXKcjSwej(Viln*P|(z$A*jk{ z2#v&kfv_JExok3(4zgj2ujh(~)&b|R7@!FNr&3IomVzL$SX@9c@Zy3IzSKh?)cxXL T4w=ne90!F#qhN-HPDH~0BM8Pi literal 0 HcmV?d00001 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index bf7a5aeaff..51ca3cb0b0 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -48,7 +48,7 @@ extension VPNAutoconnectTip: Tip { } var image: Image? { - Image(systemName: "point.3.filled.connected.trianglepath.dotted") + Image(.autoconnectTip) } var actions: [Action] { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index e10c49fc4e..026524c062 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -54,7 +54,7 @@ extension VPNDomainExclusionsTip: Tip { } var image: Image? { - Image(.customGlobeBadgeMinus) + Image(.domainExclusionsTip) } var rules: [Rule] { From 607feb02ff04f931e3785c2c7968857818ae24ff Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 22 Oct 2024 11:30:07 +0200 Subject: [PATCH 11/34] WIP --- DuckDuckGo.xcodeproj/project.pbxproj | 2 + .../xcshareddata/swiftpm/Package.resolved | 8 -- .../DuckDuckGo Privacy Browser.xcscheme | 2 +- DuckDuckGo/InfoPlist.xcstrings | 2 +- DuckDuckGo/Localizable.xcstrings | 10 +- ...etworkProtectionNavBarPopoverManager.swift | 8 +- .../TipKit/TipKitAppEventHandling.swift | 7 +- DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements | 1 + .../Menu/StatusBarMenu.swift | 8 +- .../TipViews/Model/VPNAutoconnectTip.swift | 6 +- .../Model/VPNDomainExclusionsTip.swift | 18 ++-- .../TipViews/Model/VPNGeoswitchingTip.swift | 4 +- .../Views/TipViews/Model/VPNTipsModel.swift | 95 ++++++++++++------- .../TipViews/VPNAutoconnectTipView.swift | 2 +- .../TipViews/VPNDomainExclusionsTipView.swift | 2 +- .../TipViews/VPNGeoswitchingTipView.swift | 2 +- .../TunnelControllerView.swift | 20 +++- .../TunnelControllerViewModel.swift | 16 ++-- 18 files changed, 124 insertions(+), 89 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d0ead5690f..3d8cb8ae18 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3827,6 +3827,7 @@ 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ContentOverlay.storyboard; sourceTree = ""; }; 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayViewController.swift; sourceTree = ""; }; 7B25FE322AD12C990012AFAB /* NetworkProtectionMac */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NetworkProtectionMac; sourceTree = ""; }; + 7B26AEE52CC674C900D66678 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAppEvents.swift; sourceTree = ""; }; 7B2E52242A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAgentNotificationsPresenter.swift; sourceTree = ""; }; 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNavBarPopoverManager.swift; sourceTree = ""; }; @@ -5519,6 +5520,7 @@ 378E279C2970217400FCADA2 /* LocalPackages */ = { isa = PBXGroup; children = ( + 7B26AEE52CC674C900D66678 /* BrowserServicesKit */, 9D9DE5712C63A96400D20B15 /* AppKitExtensions */, 7B9167A82C09E88800322310 /* AppLauncher */, 378E279D2970217400FCADA2 /* BuildToolPlugins */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0c2dfcbf1b..06c52ecb68 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,14 +27,6 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "revision" : "c05ef03b3320ccecdace3b1d53d945313585c170" - } - }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index 5cd264a90f..a0ad0111f9 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -79,7 +79,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..a2886fe7e3 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" @@ -57,15 +53,15 @@ extension VPNDomainExclusionsTip: Tip { Image(.domainExclusionsTip) } - var rules: [Rule] { + var rules: [Self.Rule] { #Rule(Self.$hasActiveSite) { - $0 == true + $0 } #Rule(Self.$vpnEnabled) { - $0 == true + $0 } - #Rule(Self.viewOpenedWhehVPNAlreadyConnectedEvent) { - $0.donations.count > 1 + #Rule(Self.viewOpenedWhenVPNAlreadyConnectedEvent) { + $0.donations.count > 0 } } } 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..5ca9a33128 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -20,14 +20,16 @@ import AppKit import Combine import Common import NetworkProtection +import os.log import TipKit import TipKitUtils +@MainActor public final class VPNTipsModel: ObservableObject { @Published private(set) var activeSiteInfo: ActiveSiteInfo? { - didSet { + willSet { guard #available(macOS 14.0, *) else { return } @@ -38,13 +40,20 @@ public final class VPNTipsModel: ObservableObject { @Published private(set) var connectionStatus: ConnectionStatus { - didSet { + willSet { guard #available(macOS 14.0, *) else { return } - switch connectionStatus { + switch newValue { case .connected: + if case connectionStatus = .connecting { + Task { + print("🧉💎 Geoswitching tip donated") + await VPNGeoswitchingTip.vpnConnectedEvent.donate() + } + } + VPNAutoconnectTip.vpnEnabled = true VPNDomainExclusionsTip.vpnEnabled = true default: @@ -56,44 +65,58 @@ public final class VPNTipsModel: ObservableObject { @Published private(set) var featureFlag: Bool - let tips: TipGrouping + + @Published + private var 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 { + + logger.debug("🧉🤌 makeTips") + + guard #available(macOS 14.0, *) else { + return EmptyTipGroup() + } + + let domainExclusionsTip = VPNDomainExclusionsTip() + let geoswitchingTip = VPNGeoswitchingTip() + + Task { + for await statusUpdate in geoswitchingTip.statusUpdates { + logger.debug("🧉 VPNGeoswitchingTip status updated: \(String(describing: statusUpdate), privacy: .public)") + logger.debug("🪙 VPNGeoswitchingTip summary:\nL shouldDisplay: \(String(describing: geoswitchingTip.shouldDisplay), privacy: .public)") + } + } + + Task { + for await statusUpdate in domainExclusionsTip.statusUpdates { + logger.debug("🧉 VPNDomainExclusionsTip status updated: \(String(describing: statusUpdate), privacy: .public)") + logger.debug("🪙 VPNDomainExclusionsTip summary:\nL shouldDisplay: \(String(describing: domainExclusionsTip.shouldDisplay), privacy: .public)\nL hasActiveSite: \(String(describing: VPNDomainExclusionsTip.hasActiveSite), privacy: .public)\nL vpnEnabled: \(String(describing: VPNDomainExclusionsTip.vpnEnabled), 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 { ... what's below + if isMenuApp { + return LegacyTipGroup(.ordered) { + VPNGeoswitchingTip() + VPNAutoconnectTip() } } else { - return EmptyTipGroup() + return LegacyTipGroup(.ordered) { + VPNGeoswitchingTip() + VPNDomainExclusionsTip() + VPNAutoconnectTip() + } } } @@ -101,12 +124,15 @@ public final class VPNTipsModel: ObservableObject { statusObserver: ConnectionStatusObserver, activeSitePublisher: CurrentValuePublisher, forMenuApp isMenuApp: Bool, - vpnSettings: VPNSettings) { + vpnSettings: VPNSettings, + logger: Logger) { + print("🧉🟢 New model instance") 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, *) { @@ -152,4 +178,9 @@ public final class VPNTipsModel: ObservableObject { vpnSettings.connectOnLogin = true } } + + @available(macOS 14.0, *) + var currentTip: (any Tip)? { + tips.currentTip + } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift index c484541551..539cc41501 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift @@ -34,7 +34,7 @@ struct VPNAutoconnectTipView: View { public var body: some View { if tipsModel.featureFlag, - let tip = tipsModel.tips.currentTip as? VPNAutoconnectTip { + let tip = tipsModel.currentTip as? VPNAutoconnectTip { TipView(tip, action: tipsModel.autoconnectTipActionHandler) } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift index f068902345..629e1ed3b2 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift @@ -34,7 +34,7 @@ struct VPNDomainExclusionsTipView: View { public var body: some View { if tipsModel.featureFlag, - let tip = tipsModel.tips.currentTip as? VPNDomainExclusionsTip { + let tip = tipsModel.currentTip as? VPNDomainExclusionsTip { TipView(tip) } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift index 8ef6442b54..9b197e7456 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift @@ -33,7 +33,7 @@ struct VPNGeoswitchingTipView: View { @ViewBuilder public var body: some View { if tipsModel.featureFlag, - let tip = tipsModel.tips.currentTip as? VPNGeoswitchingTip { + let tip = tipsModel.currentTip as? VPNGeoswitchingTip { TipView(tip) } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 7ebb3c8a1d..58357954be 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -22,6 +22,7 @@ import Combine import NetworkProtection import Lottie import TipKit +import TipKitUtils public struct TunnelControllerView: View { @@ -41,6 +42,9 @@ public struct TunnelControllerView: View { self.model = model } + @EnvironmentObject + private var tipsModel: VPNTipsModel + // MARK: - View Contents public var body: some View { @@ -60,9 +64,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.currentTip as? VPNDomainExclusionsTip { + + TipView(VPNDomainExclusionsTip()) + .padding(.horizontal, 9) + .padding(.vertical, 6) + } } Divider() @@ -187,7 +199,7 @@ public struct TunnelControllerView: View { } if #available(macOS 15.0, *) { - VPNGeoswitchingTipView() + TipView(VPNGeoswitchingTip()) .padding(.horizontal, 9) .padding(.vertical, 6) } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift index 35a5bc317a..9f9c02ed69 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift @@ -181,11 +181,15 @@ public final class TunnelControllerViewModel: ObservableObject { } refreshTimeLapsed() - let call = refreshTimeLapsed - let newTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in + let newTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in + + guard let self else { + return + } + Task { @MainActor in - call() + self.refreshTimeLapsed() } } @@ -503,10 +507,6 @@ public final class TunnelControllerViewModel: ObservableObject { } Task { @MainActor in - if #available(macOS 14.0, *) { - await VPNGeoswitchingTip.vpnConnectedEvent.donate() - } - await tunnelController.start() refreshInternalIsRunning() } @@ -541,7 +541,7 @@ public final class TunnelControllerViewModel: ObservableObject { func handleTunnelControllerShown() async { if #available(macOS 14.0, *) { - await VPNDomainExclusionsTip.viewOpenedWhehVPNAlreadyConnectedEvent.donate() + await VPNDomainExclusionsTip.viewOpenedWhenVPNAlreadyConnectedEvent.donate() } } } From aa937d547df4fc821e4a27063f3d39fe59a019f8 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 22 Oct 2024 14:37:00 +0200 Subject: [PATCH 12/34] WIP --- .../TipKit/TipKitAppEventHandling.swift | 7 +-- .../TipViews/Model/VPNGeoswitchingTip.swift | 5 +- .../Views/TipViews/Model/VPNTipsModel.swift | 61 ++++++++++++------- .../TunnelControllerView.swift | 21 ++++++- 4 files changed, 62 insertions(+), 32 deletions(-) diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 037920bb72..57b2ff0500 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -44,17 +44,14 @@ struct TipKitAppEventHandler: TipKitAppEventHandling { let appConfigurationGroupIdentifier = Bundle.main.appGroup(bundle: .appConfiguration) - /* guard let dataStoreLocation = try? DataStoreLocation.groupContainer(identifier: appConfigurationGroupIdentifier) else { fatalError() } - */ - let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appConfigurationGroupIdentifier)! controller.configureTipKit([ - .displayFrequency(.immediate) //, - //.datastoreLocation(.url(url)) + .displayFrequency(.immediate), + .datastoreLocation(dataStoreLocation) ]) } else { logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift index 27aac17ebb..7d583dd1d5 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift @@ -21,9 +21,10 @@ import TipKit /// A tip to suggest to the user to change their location using geo-switching /// -@available(macOS 14.0, *) -struct VPNGeoswitchingTip: Tip { +struct VPNGeoswitchingTip {} +@available(macOS 14.0, *) +extension VPNGeoswitchingTip: Tip { static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.geoswitching.vpnConnectedEvent") var id: String { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 5ca9a33128..5c04330cd6 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -29,46 +29,32 @@ public final class VPNTipsModel: ObservableObject { @Published private(set) var activeSiteInfo: ActiveSiteInfo? { - willSet { + didSet { guard #available(macOS 14.0, *) else { return } - VPNDomainExclusionsTip.hasActiveSite = (activeSiteInfo != nil) + print("🧉🧉 activeSiteInfo: \(String(describing: activeSiteInfo))") + handleActiveSiteInfoChanged(newValue: activeSiteInfo) } } @Published private(set) var connectionStatus: ConnectionStatus { - willSet { + didSet { guard #available(macOS 14.0, *) else { return } - switch newValue { - case .connected: - if case connectionStatus = .connecting { - Task { - print("🧉💎 Geoswitching tip donated") - await VPNGeoswitchingTip.vpnConnectedEvent.donate() - } - } - - VPNAutoconnectTip.vpnEnabled = true - VPNDomainExclusionsTip.vpnEnabled = true - default: - VPNAutoconnectTip.vpnEnabled = false - VPNDomainExclusionsTip.vpnEnabled = false - } + print("🧉🧉 activeSiteInfo: \(String(describing: connectionStatus))") + handleConnectionStatusChanged(oldValue: oldValue, newValue: connectionStatus) } } @Published private(set) var featureFlag: Bool - @Published private var tips: TipGrouping - private let vpnSettings: VPNSettings private let logger: Logger private var cancellables = Set() @@ -77,7 +63,7 @@ public final class VPNTipsModel: ObservableObject { logger.debug("🧉🤌 makeTips") - guard #available(macOS 14.0, *) else { + guard #available(macOS 15.0, *) else { return EmptyTipGroup() } @@ -107,12 +93,12 @@ public final class VPNTipsModel: ObservableObject { // } // } else { ... what's below if isMenuApp { - return LegacyTipGroup(.ordered) { + return TipGroup(.ordered) { VPNGeoswitchingTip() VPNAutoconnectTip() } } else { - return LegacyTipGroup(.ordered) { + return TipGroup(.ordered) { VPNGeoswitchingTip() VPNDomainExclusionsTip() VPNAutoconnectTip() @@ -136,6 +122,9 @@ public final class VPNTipsModel: ObservableObject { self.vpnSettings = vpnSettings if #available(macOS 14.0, *) { + handleActiveSiteInfoChanged(newValue: activeSiteInfo) + handleConnectionStatusChanged(oldValue: connectionStatus, newValue: connectionStatus) + subscribeToConnectionStatusChanges(statusObserver) subscribeToFeatureFlagChanges(featureFlagPublisher) subscribeToActiveSiteChanges(activeSitePublisher) @@ -170,6 +159,32 @@ public final class VPNTipsModel: ObservableObject { .store(in: &cancellables) } + // MARK: - Handle Refreshing + + @available(macOS 14.0, *) + private func handleActiveSiteInfoChanged(newValue: ActiveSiteInfo?) { + VPNDomainExclusionsTip.hasActiveSite = (activeSiteInfo != nil) + } + + @available(macOS 14.0, *) + private func handleConnectionStatusChanged(oldValue: ConnectionStatus, newValue: ConnectionStatus) { + switch newValue { + case .connected: + if case oldValue = .connecting { + Task { + print("🧉💎 Geoswitching tip donated") + await VPNGeoswitchingTip.vpnConnectedEvent.donate() + } + } + + VPNAutoconnectTip.vpnEnabled = true + VPNDomainExclusionsTip.vpnEnabled = true + default: + VPNAutoconnectTip.vpnEnabled = false + VPNDomainExclusionsTip.vpnEnabled = false + } + } + // MARK: - Tip Action handling @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 58357954be..7269c007d5 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -45,6 +45,18 @@ public struct TunnelControllerView: View { @EnvironmentObject private var tipsModel: VPNTipsModel + private var tipGroup: TipGrouping = { + guard #available(macOS 15.0, *) else { + return EmptyTipGroup() + } + + return TipGroup(.ordered) { + VPNGeoswitchingTip() + VPNDomainExclusionsTip() + VPNAutoconnectTip() + } + }() + // MARK: - View Contents public var body: some View { @@ -56,6 +68,7 @@ public struct TunnelControllerView: View { if #available(macOS 15.0, *) { VPNAutoconnectTipView() + //TipView(tipGroup.currentTip as? VPNAutoconnectTip) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -71,7 +84,9 @@ public struct TunnelControllerView: View { if tipsModel.featureFlag {//, //let tip = tipsModel.currentTip as? VPNDomainExclusionsTip { - TipView(VPNDomainExclusionsTip()) + //TipView(VPNDomainExclusionsTip()) + //TipView(tipGroup.currentTip as? VPNDomainExclusionsTip) + VPNDomainExclusionsTipView() .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -199,7 +214,9 @@ public struct TunnelControllerView: View { } if #available(macOS 15.0, *) { - TipView(VPNGeoswitchingTip()) + //TipView(VPNGeoswitchingTip()) + VPNGeoswitchingTipView() + //TipView(tipGroup.currentTip as? VPNGeoswitchingTip) .padding(.horizontal, 9) .padding(.vertical, 6) } From 7d0404ab8895115c0df57020d7ec7e50500b069c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 24 Oct 2024 12:50:54 +0200 Subject: [PATCH 13/34] WIP --- .../Views/TipViews/Model/VPNTipsModel.swift | 11 ++++++----- .../TunnelControllerView.swift | 15 ++++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 5c04330cd6..b9e6df6ec3 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -54,11 +54,11 @@ public final class VPNTipsModel: ObservableObject { @Published private(set) var featureFlag: Bool - private var tips: TipGrouping + //private var tips: TipGrouping private let vpnSettings: VPNSettings private let logger: Logger private var cancellables = Set() - +/* static func makeTips(forMenuApp isMenuApp: Bool, logger: Logger) -> TipGrouping { logger.debug("🧉🤌 makeTips") @@ -104,7 +104,7 @@ public final class VPNTipsModel: ObservableObject { VPNAutoconnectTip() } } - } + }*/ public init(featureFlagPublisher: CurrentValuePublisher, statusObserver: ConnectionStatusObserver, @@ -118,7 +118,7 @@ public final class VPNTipsModel: ObservableObject { self.connectionStatus = statusObserver.recentValue self.featureFlag = featureFlagPublisher.value self.logger = logger - self.tips = Self.makeTips(forMenuApp: isMenuApp, logger: logger) + //self.tips = Self.makeTips(forMenuApp: isMenuApp, logger: logger) self.vpnSettings = vpnSettings if #available(macOS 14.0, *) { @@ -196,6 +196,7 @@ public final class VPNTipsModel: ObservableObject { @available(macOS 14.0, *) var currentTip: (any Tip)? { - tips.currentTip + //tips.currentTip + return nil } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 7269c007d5..f49d2e011d 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -44,7 +44,7 @@ public struct TunnelControllerView: View { @EnvironmentObject private var tipsModel: VPNTipsModel - +/* private var tipGroup: TipGrouping = { guard #available(macOS 15.0, *) else { return EmptyTipGroup() @@ -55,7 +55,7 @@ public struct TunnelControllerView: View { VPNDomainExclusionsTip() VPNAutoconnectTip() } - }() + }()*/ // MARK: - View Contents @@ -67,7 +67,8 @@ public struct TunnelControllerView: View { featureToggleRow() if #available(macOS 15.0, *) { - VPNAutoconnectTipView() + TipView(VPNAutoconnectTip()) + //VPNAutoconnectTipView() //TipView(tipGroup.currentTip as? VPNAutoconnectTip) .padding(.horizontal, 9) .padding(.vertical, 6) @@ -84,9 +85,9 @@ public struct TunnelControllerView: View { if tipsModel.featureFlag {//, //let tip = tipsModel.currentTip as? VPNDomainExclusionsTip { - //TipView(VPNDomainExclusionsTip()) + TipView(VPNDomainExclusionsTip()) //TipView(tipGroup.currentTip as? VPNDomainExclusionsTip) - VPNDomainExclusionsTipView() + //VPNDomainExclusionsTipView() .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -214,8 +215,8 @@ public struct TunnelControllerView: View { } if #available(macOS 15.0, *) { - //TipView(VPNGeoswitchingTip()) - VPNGeoswitchingTipView() + TipView(VPNGeoswitchingTip()) + //VPNGeoswitchingTipView() //TipView(tipGroup.currentTip as? VPNGeoswitchingTip) .padding(.horizontal, 9) .padding(.vertical, 6) From f5b3b715551d2716dde1e6bca128af71536bdaef Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 28 Oct 2024 16:03:03 +0100 Subject: [PATCH 14/34] WIP --- DuckDuckGo.xcodeproj/project.pbxproj | 16 +++- .../xcshareddata/swiftpm/Package.resolved | 11 ++- DuckDuckGo/Menus/MainMenuActions.swift | 1 - ...etworkProtectionNavBarPopoverManager.swift | 4 +- .../TipKit/TipKitAppEventHandling.swift | 1 - ...itController+ConvenienceInitializers.swift | 1 - DuckDuckGo/TipKit/TipKitController.swift | 93 +++++++++++++++++++ .../TipKitDebugOptionsUIActionHandling.swift | 1 - .../NetworkProtectionMac/Package.swift | 1 - .../Menu/StatusBarMenu.swift | 5 +- .../TipViews/Model/VPNAutoconnectTip.swift | 14 ++- .../Model/VPNDomainExclusionsTip.swift | 9 +- .../Views/TipViews/Model/VPNTipsModel.swift | 57 +----------- .../TunnelControllerView.swift | 58 +++++++++--- .../TunnelControllerViewModel.swift | 1 - 15 files changed, 188 insertions(+), 85 deletions(-) create mode 100644 DuckDuckGo/TipKit/TipKitController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3d8cb8ae18..f3dbd9d0f2 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -1671,6 +1671,10 @@ 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */; }; 7B1E819F27C8874900FF0E60 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 7B1E81A027C8874900FF0E60 /* ContentOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */; }; + 7B22D86E2CCFD7B7006A76E1 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B22D86D2CCFD7B7006A76E1 /* TipKitController.swift */; }; + 7B22D86F2CCFD7B7006A76E1 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B22D86D2CCFD7B7006A76E1 /* TipKitController.swift */; }; + 7B22D8702CCFD7B7006A76E1 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B22D86D2CCFD7B7006A76E1 /* TipKitController.swift */; }; + 7B22D8712CCFD7B7006A76E1 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B22D86D2CCFD7B7006A76E1 /* TipKitController.swift */; }; 7B2366842C09FAC2002D393F /* VPNAppLauncher in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2366832C09FAC2002D393F /* VPNAppLauncher */; }; 7B2366862C09FACD002D393F /* VPNAppLauncher in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2366852C09FACD002D393F /* VPNAppLauncher */; }; 7B2366882C09FADA002D393F /* VPNAppLauncher in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2366872C09FADA002D393F /* VPNAppLauncher */; }; @@ -3826,8 +3830,8 @@ 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayPopover.swift; sourceTree = ""; }; 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ContentOverlay.storyboard; sourceTree = ""; }; 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayViewController.swift; sourceTree = ""; }; + 7B22D86D2CCFD7B7006A76E1 /* TipKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitController.swift; sourceTree = ""; }; 7B25FE322AD12C990012AFAB /* NetworkProtectionMac */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NetworkProtectionMac; sourceTree = ""; }; - 7B26AEE52CC674C900D66678 /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = SOURCE_ROOT; }; 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAppEvents.swift; sourceTree = ""; }; 7B2E52242A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAgentNotificationsPresenter.swift; sourceTree = ""; }; 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNavBarPopoverManager.swift; sourceTree = ""; }; @@ -5520,7 +5524,6 @@ 378E279C2970217400FCADA2 /* LocalPackages */ = { isa = PBXGroup; children = ( - 7B26AEE52CC674C900D66678 /* BrowserServicesKit */, 9D9DE5712C63A96400D20B15 /* AppKitExtensions */, 7B9167A82C09E88800322310 /* AppLauncher */, 378E279D2970217400FCADA2 /* BuildToolPlugins */, @@ -6735,6 +6738,7 @@ children = ( 7BDBAD102CBFF633000379B7 /* Logger+TipKit.swift */, 7BDBAD112CBFF633000379B7 /* TipKitAppEventHandling.swift */, + 7B22D86D2CCFD7B7006A76E1 /* TipKitController.swift */, 7BDBAD122CBFF633000379B7 /* TipKitController+ConvenienceInitializers.swift */, 7BDBAD132CBFF633000379B7 /* TipKitDebugOptionsUIActionHandling.swift */, ); @@ -11078,6 +11082,7 @@ 3706FBCE293F65D500E42796 /* SavePaymentMethodViewController.swift in Sources */, 9FA5A0A62BC8F34900153786 /* UserDefaultsBookmarkFoldersStore.swift in Sources */, 1DDC85002B835BC000670238 /* SearchPreferences.swift in Sources */, + 7B22D8712CCFD7B7006A76E1 /* TipKitController.swift in Sources */, 3706FBD0293F65D500E42796 /* WebKitVersionProvider.swift in Sources */, 3706FBD1293F65D500E42796 /* NSCoderExtensions.swift in Sources */, B6D6A5DD2982A4CE001F5F11 /* Tab+Navigation.swift in Sources */, @@ -11896,6 +11901,7 @@ 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, 7BA7CC592AD1203B0042E5CE /* UserText+NetworkProtection.swift in Sources */, 7BD7B0032C19D3830039D20A /* VPNIPCResources.swift in Sources */, + 7B22D86F2CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51942BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, @@ -11943,6 +11949,7 @@ 4BA7C4DB2B3F63AE00AFE511 /* NetworkExtensionController.swift in Sources */, 7B1459552B7D438F00047F2C /* VPNProxyLauncher.swift in Sources */, 7BD7B0042C19D3830039D20A /* VPNIPCResources.swift in Sources */, + 7B22D8702CCFD7B7006A76E1 /* TipKitController.swift in Sources */, F1DA51952BF6081E00CF29FA /* AttributionPixelHandler.swift in Sources */, 4BA7C4D92B3F61FB00AFE511 /* BundleExtension.swift in Sources */, F1D0429E2BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift in Sources */, @@ -12526,6 +12533,7 @@ AAC5E4C725D6A6E8007F5990 /* AddBookmarkPopover.swift in Sources */, B6F7127E29F6779000594A45 /* QRSharingService.swift in Sources */, 3768D83B2C24C0A8004120AE /* RemoteMessageViewModel.swift in Sources */, + 7B22D86E2CCFD7B7006A76E1 /* TipKitController.swift in Sources */, B68C2FB227706E6A00BF2C7D /* ProcessExtension.swift in Sources */, CD2AB5CA2C8225E70019EB49 /* URLTokenValidator.swift in Sources */, B6106BA726A7BECC0013B453 /* PermissionAuthorizationQuery.swift in Sources */, @@ -14337,8 +14345,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = revision; - revision = c05ef03b3320ccecdace3b1d53d945313585c170; + kind = exactVersion; + version = 199.1.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 06c52ecb68..00eac3628a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "state" : { + "revision" : "e0c0c85c18372f73fb97c5cf070f1de70c906a1f", + "version" : "199.1.0" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", @@ -66,7 +75,7 @@ { "identity" : "lottie-spm", "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm", + "location" : "https://github.com/airbnb/lottie-spm.git", "state" : { "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", "version" : "4.4.3" diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 9addd60d5b..a5389b599d 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -27,7 +27,6 @@ import Subscription import WebKit import os.log import SwiftUI -import TipKitUtils // Actions are sent to objects of responder chain diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 892d8936d8..a149cfdbf8 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -109,7 +109,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let proxySettings = TransparentProxySettings(defaults: .netP) let uiActionHandler = VPNUIActionHandler(vpnURLEventHandler: vpnURLEventHandler, proxySettings: proxySettings) - let activeSitePublisher = CurrentValuePublisher( + let activeSitePublisher = CurrentValueSubject( initialValue: nil, publisher: $siteInfo.eraseToAnyPublisher()) @@ -162,7 +162,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { }) // TODO: replace with access to actual feature flag - let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) + let tipsFeatureFlagPublisher = CurrentValueSubject(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 57b2ff0500..71bc59c7f5 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -19,7 +19,6 @@ import Foundation import os.log -import TipKitUtils import TipKit protocol TipKitAppEventHandling { diff --git a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift index 93884bff37..abe4b30fb0 100644 --- a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift +++ b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift @@ -18,7 +18,6 @@ // import Foundation -import TipKitUtils import os extension TipKitController { diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift new file mode 100644 index 0000000000..c91a0b534f --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController.swift @@ -0,0 +1,93 @@ +// +// TipKitController.swift +// DuckDuckGo +// +// Copyright © 2024 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 os.log +import TipKit + +protocol TipKitControlling { + @available(iOS 17.0, *) + func configureTipKit() + + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() +} + +final class TipKitController { + + private let logger: Logger + private let userDefaults: UserDefaults + + private var resetTipKitOnNextLaunch: Bool { + get { + userDefaults.bool(forKey: "resetTipKitOnNextLaunch") + } + + set { + userDefaults.set(newValue, forKey: "resetTipKitOnNextLaunch") + } + } + + init(logger: Logger, + userDefaults: UserDefaults) { + + self.logger = logger + self.userDefaults = userDefaults + } + + @available(iOS 17.0, macOS 14.0, *) + func configureTipKit(_ configuration: [Tips.ConfigurationOption] = []) { + do { + if resetTipKitOnNextLaunch { + resetTipKit() + resetTipKitOnNextLaunch = false + } + + try Tips.configure(configuration) + + logger.debug("TipKit initialized") + } catch { + logger.error("Failed to initialize TipKit: \(error)") + } + } + + @available(iOS 17.0, macOS 14.0, *) + private func resetTipKit() { + do { + try Tips.resetDatastore() + + logger.debug("TipKit reset") + } catch { + logger.debug("Failed to reset TipKit: \(error)") + } + } + + /// Resets TipKit + /// + /// One thing that's not documented as of 2024-10-09 is that resetting TipKit must happen before it's configured. + /// When trying to reset it after it's configured we get `TipKit.TipKitError(value: TipKit.TipKitError.Value.tipsDatastoreAlreadyConfigured)`. + /// In order to make things work for us we set a user defaults value that ensures TipKit will be reset on next + /// app launch instead of directly trying to reset it here. + /// + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() { + resetTipKitOnNextLaunch = true + logger.debug("TipKit will reset on next app launch") + } +} diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift index 5e2750e72a..9ab3e9eb4d 100644 --- a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -19,7 +19,6 @@ import Foundation import os.log -import TipKitUtils protocol TipKitDebugOptionsUIActionHandling { /// Resets TipKit diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 9c51f12b34..92ee8f95e3 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -104,7 +104,6 @@ let package = Package( "VPNPixels", .product(name: "NetworkProtection", package: "BrowserServicesKit"), .product(name: "PixelKit", package: "BrowserServicesKit"), - .product(name: "TipKitUtils", package: "BrowserServicesKit"), .product(name: "SwiftUIExtensions", package: "SwiftUIExtensions"), .product(name: "LoginItems", package: "LoginItems"), .product(name: "Lottie", package: "lottie-spm") diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 73087cd3fd..9ee24d43f7 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -24,7 +24,6 @@ import LoginItems import NetworkProtection import os.log import SwiftUI -import TipKitUtils /// Abstraction of the the VPN status bar menu with a simple interface. /// @@ -137,7 +136,7 @@ public final class StatusBarMenu: NSObject { return } - let activeSitePublisher = CurrentValuePublisher(initialValue: nil, publisher: Just(ActiveSiteInfo?(nil)).eraseToAnyPublisher()) + let activeSitePublisher = CurrentValueSubject(ActiveSiteInfo?(nil)) let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( connectionStatusPublisher: Just(NetworkProtection.ConnectionStatus.disconnected).eraseToAnyPublisher(), @@ -145,7 +144,7 @@ public final class StatusBarMenu: NSObject { uiActionHandler: uiActionHandler) // TODO: replace with access to actual feature flag - let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) + let tipsFeatureFlagPublisher = CurrentValueSubject(true) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index 8881f62dee..0de70d87bf 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -21,13 +21,19 @@ import TipKit /// A tip to suggest to the user to use the autoconnect option for the VPN. /// +struct VPNAutoconnectTip {} + @available(macOS 14.0, *) -struct VPNAutoconnectTip: Tip { +extension VPNAutoconnectTip: Tip { enum ActionIdentifiers: String { case enable = "com.duckduckgo.vpn.tip.autoconnect.action.enable" } + static let domainExclusionsTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.autoconnect.domainExclusionsTipDismissedEvent") + + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.autoconnect.geolocationTipDismissedEvent") + @Parameter(.transient) static var vpnEnabled: Bool = false @@ -58,5 +64,11 @@ struct VPNAutoconnectTip: Tip { #Rule(Self.$vpnEnabled) { $0 == true } + #Rule(Self.domainExclusionsTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.geolocationTipDismissedEvent) { + $0.donations.count > 0 + } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index a2886fe7e3..36ecb8a64b 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -21,8 +21,10 @@ import TipKit /// A tip to suggest using domain exclusions when a site doesn't work. /// +struct VPNDomainExclusionsTip {} + @available(macOS 14.0, *) -struct VPNDomainExclusionsTip: Tip { +extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var vpnEnabled: Bool = false @@ -30,6 +32,8 @@ struct VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var hasActiveSite: Bool = false + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.geolocationTipDismissedEvent") + /// The containing view was opened when the VPN was already connected. /// /// This condition may be indicative that the user is struggling, so they might want @@ -60,6 +64,9 @@ struct VPNDomainExclusionsTip: Tip { #Rule(Self.$vpnEnabled) { $0 } + #Rule(Self.geolocationTipDismissedEvent) { + $0.donations.count > 0 + } #Rule(Self.viewOpenedWhenVPNAlreadyConnectedEvent) { $0.donations.count > 0 } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index b9e6df6ec3..30cca0eb41 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -22,7 +22,6 @@ import Common import NetworkProtection import os.log import TipKit -import TipKitUtils @MainActor public final class VPNTipsModel: ObservableObject { @@ -58,57 +57,10 @@ public final class VPNTipsModel: ObservableObject { private let vpnSettings: VPNSettings private let logger: Logger private var cancellables = Set() -/* - static func makeTips(forMenuApp isMenuApp: Bool, logger: Logger) -> TipGrouping { - logger.debug("🧉🤌 makeTips") - - guard #available(macOS 15.0, *) else { - return EmptyTipGroup() - } - - let domainExclusionsTip = VPNDomainExclusionsTip() - let geoswitchingTip = VPNGeoswitchingTip() - - Task { - for await statusUpdate in geoswitchingTip.statusUpdates { - logger.debug("🧉 VPNGeoswitchingTip status updated: \(String(describing: statusUpdate), privacy: .public)") - logger.debug("🪙 VPNGeoswitchingTip summary:\nL shouldDisplay: \(String(describing: geoswitchingTip.shouldDisplay), privacy: .public)") - } - } - - Task { - for await statusUpdate in domainExclusionsTip.statusUpdates { - logger.debug("🧉 VPNDomainExclusionsTip status updated: \(String(describing: statusUpdate), privacy: .public)") - logger.debug("🪙 VPNDomainExclusionsTip summary:\nL shouldDisplay: \(String(describing: domainExclusionsTip.shouldDisplay), privacy: .public)\nL hasActiveSite: \(String(describing: VPNDomainExclusionsTip.hasActiveSite), privacy: .public)\nL vpnEnabled: \(String(describing: VPNDomainExclusionsTip.vpnEnabled), 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, *) { - // return TipGroup(.ordered) { - // tips - // } - // } else { ... what's below - if isMenuApp { - return TipGroup(.ordered) { - VPNGeoswitchingTip() - VPNAutoconnectTip() - } - } else { - return TipGroup(.ordered) { - VPNGeoswitchingTip() - VPNDomainExclusionsTip() - VPNAutoconnectTip() - } - } - }*/ - - public init(featureFlagPublisher: CurrentValuePublisher, + public init(featureFlagPublisher: CurrentValueSubject, statusObserver: ConnectionStatusObserver, - activeSitePublisher: CurrentValuePublisher, + activeSitePublisher: CurrentValueSubject, forMenuApp isMenuApp: Bool, vpnSettings: VPNSettings, logger: Logger) { @@ -118,7 +70,6 @@ public final class VPNTipsModel: ObservableObject { self.connectionStatus = statusObserver.recentValue self.featureFlag = featureFlagPublisher.value self.logger = logger - //self.tips = Self.makeTips(forMenuApp: isMenuApp, logger: logger) self.vpnSettings = vpnSettings if #available(macOS 14.0, *) { @@ -132,7 +83,7 @@ public final class VPNTipsModel: ObservableObject { } @available(macOS 14.0, *) - private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { + private func subscribeToFeatureFlagChanges(_ publisher: CurrentValueSubject) { publisher .removeDuplicates() .receive(on: DispatchQueue.main) @@ -150,7 +101,7 @@ public final class VPNTipsModel: ObservableObject { } @available(macOS 14.0, *) - private func subscribeToActiveSiteChanges(_ publisher: CurrentValuePublisher) { + private func subscribeToActiveSiteChanges(_ publisher: CurrentValueSubject) { publisher .removeDuplicates() diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index f49d2e011d..9fb55fb197 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -22,7 +22,6 @@ import Combine import NetworkProtection import Lottie import TipKit -import TipKitUtils public struct TunnelControllerView: View { @@ -44,18 +43,6 @@ public struct TunnelControllerView: View { @EnvironmentObject private var tipsModel: VPNTipsModel -/* - private var tipGroup: TipGrouping = { - guard #available(macOS 15.0, *) else { - return EmptyTipGroup() - } - - return TipGroup(.ordered) { - VPNGeoswitchingTip() - VPNDomainExclusionsTip() - VPNAutoconnectTip() - } - }()*/ // MARK: - View Contents @@ -215,7 +202,7 @@ public struct TunnelControllerView: View { } if #available(macOS 15.0, *) { - TipView(VPNGeoswitchingTip()) + TipView(geoswitchingTip) //VPNGeoswitchingTipView() //TipView(tipGroup.currentTip as? VPNGeoswitchingTip) .padding(.horizontal, 9) @@ -248,6 +235,49 @@ public struct TunnelControllerView: View { } } + // MARK: - Tips + + let geoswitchingTip: VPNGeoswitchingTip = { + let tip = VPNGeoswitchingTip() + + if #available(macOS 14.0, *) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() + await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() + } + } + } + } + } + + return tip + }() + + let snoozeTip: VPNDomainExclusionsTip = { + let tip = VPNDomainExclusionsTip() + + if #available(macOS 14.0, *) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() + } + } + } + } + } + + return tip + }() + + let autoconnectTip: VPNAutoconnectTip = { + VPNAutoconnectTip() + }() + // MARK: - Rows private func dividerRow() -> some View { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift index 9f9c02ed69..c12a8dc1f8 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift @@ -20,7 +20,6 @@ import Combine import Foundation import NetworkProtection import SwiftUI -import TipKitUtils import TipKit @MainActor From 83899f396195a774c72b697fcc76a45b33102450 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 28 Oct 2024 17:30:34 +0100 Subject: [PATCH 15/34] WIP --- ...etworkProtectionNavBarPopoverManager.swift | 4 +- .../Combine/CurrentValuePublisher.swift | 36 ++++++++++++++++ .../Menu/StatusBarMenu.swift | 8 +++- .../Views/TipViews/Model/VPNTipsModel.swift | 35 ++++++++++++---- .../TipViews/VPNAutoconnectTipView.swift | 42 ------------------- .../TipViews/VPNDomainExclusionsTipView.swift | 42 ------------------- .../TipViews/VPNGeoswitchingTipView.swift | 41 ------------------ .../TunnelControllerView.swift | 42 ++++++------------- 8 files changed, 84 insertions(+), 166 deletions(-) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index a149cfdbf8..892d8936d8 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -109,7 +109,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let proxySettings = TransparentProxySettings(defaults: .netP) let uiActionHandler = VPNUIActionHandler(vpnURLEventHandler: vpnURLEventHandler, proxySettings: proxySettings) - let activeSitePublisher = CurrentValueSubject( + let activeSitePublisher = CurrentValuePublisher( initialValue: nil, publisher: $siteInfo.eraseToAnyPublisher()) @@ -162,7 +162,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { }) // TODO: replace with access to actual feature flag - let tipsFeatureFlagPublisher = CurrentValueSubject(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) + let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift new file mode 100644 index 0000000000..a250aea649 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift @@ -0,0 +1,36 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +import Combine +import Foundation + +public final class CurrentValuePublisher { + + private(set) public var value: Output + private let wrappedPublisher: AnyPublisher + private var cancellable: AnyCancellable? + + public init(initialValue: Output, publisher: AnyPublisher) { + value = initialValue + wrappedPublisher = publisher + + subscribeToPublisherUpdates() + } + + private func subscribeToPublisherUpdates() { + cancellable = wrappedPublisher + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { _ in }) { [weak self] value in + self?.value = value + } + } +} + +extension CurrentValuePublisher: Publisher { + public func receive(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { + + wrappedPublisher.receive(subscriber: subscriber) + } + + +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 9ee24d43f7..9d93ed6165 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -136,7 +136,9 @@ public final class StatusBarMenu: NSObject { return } - let activeSitePublisher = CurrentValueSubject(ActiveSiteInfo?(nil)) + let activeSitePublisher = CurrentValuePublisher( + initialValue: ActiveSiteInfo?(nil), + publisher: Just(nil).eraseToAnyPublisher()) let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( connectionStatusPublisher: Just(NetworkProtection.ConnectionStatus.disconnected).eraseToAnyPublisher(), @@ -144,7 +146,9 @@ public final class StatusBarMenu: NSObject { uiActionHandler: uiActionHandler) // TODO: replace with access to actual feature flag - let tipsFeatureFlagPublisher = CurrentValueSubject(true) + let tipsFeatureFlagPublisher = CurrentValuePublisher( + initialValue: true, + publisher: Just(true).eraseToAnyPublisher()) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 30cca0eb41..954de8ed21 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -58,9 +58,9 @@ public final class VPNTipsModel: ObservableObject { private let logger: Logger private var cancellables = Set() - public init(featureFlagPublisher: CurrentValueSubject, + public init(featureFlagPublisher: CurrentValuePublisher, statusObserver: ConnectionStatusObserver, - activeSitePublisher: CurrentValueSubject, + activeSitePublisher: CurrentValuePublisher, forMenuApp isMenuApp: Bool, vpnSettings: VPNSettings, logger: Logger) { @@ -83,7 +83,7 @@ public final class VPNTipsModel: ObservableObject { } @available(macOS 14.0, *) - private func subscribeToFeatureFlagChanges(_ publisher: CurrentValueSubject) { + private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { publisher .removeDuplicates() .receive(on: DispatchQueue.main) @@ -101,7 +101,7 @@ public final class VPNTipsModel: ObservableObject { } @available(macOS 14.0, *) - private func subscribeToActiveSiteChanges(_ publisher: CurrentValueSubject) { + private func subscribeToActiveSiteChanges(_ publisher: CurrentValuePublisher) { publisher .removeDuplicates() @@ -146,8 +146,29 @@ public final class VPNTipsModel: ObservableObject { } @available(macOS 14.0, *) - var currentTip: (any Tip)? { - //tips.currentTip - return nil + func subscribeToTipStatusChanges(_ tip: VPNGeoswitchingTip) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() + await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() + } + } + } + } + } + + @available(macOS 14.0, *) + func subscribeToTipStatusChanges(_ tip: VPNDomainExclusionsTip) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() + } + } + } + } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift deleted file mode 100644 index 539cc41501..0000000000 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNAutoconnectTipView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// VPNAutoconnectTipView.swift -// -// Copyright © 2024 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 SwiftUI -import TipKit - -@available(macOS 14.0, *) -struct VPNAutoconnectTipView: View { - - // MARK: - Model - - @EnvironmentObject - var tipsModel: VPNTipsModel - - // MARK: - Body - - @ViewBuilder - public var body: some View { - - if tipsModel.featureFlag, - let tip = tipsModel.currentTip as? VPNAutoconnectTip { - - TipView(tip, action: tipsModel.autoconnectTipActionHandler) - } - } -} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift deleted file mode 100644 index 629e1ed3b2..0000000000 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNDomainExclusionsTipView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// VPNDomainExclusionsTipView.swift -// -// Copyright © 2024 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 SwiftUI -import TipKit - -@available(macOS 14.0, *) -struct VPNDomainExclusionsTipView: View { - - // MARK: - Model - - @EnvironmentObject - var tipsModel: VPNTipsModel - - // MARK: - Body - - @ViewBuilder - public var body: some View { - - if tipsModel.featureFlag, - let tip = tipsModel.currentTip as? VPNDomainExclusionsTip { - - TipView(tip) - } - } -} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift deleted file mode 100644 index 9b197e7456..0000000000 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/VPNGeoswitchingTipView.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// VPNGeoswitchingTipView.swift -// -// Copyright © 2024 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 SwiftUI -import TipKit - -@available(macOS 14.0, *) -struct VPNGeoswitchingTipView: View { - - // MARK: - Model - - @EnvironmentObject - var tipsModel: VPNTipsModel - - // MARK: - Body - - @ViewBuilder - public var body: some View { - if tipsModel.featureFlag, - let tip = tipsModel.currentTip as? VPNGeoswitchingTip { - - TipView(tip) - } - } -} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 9fb55fb197..8e91eb70d3 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -53,7 +53,7 @@ public struct TunnelControllerView: View { featureToggleRow() - if #available(macOS 15.0, *) { + if #available(macOS 14.0, *) { TipView(VPNAutoconnectTip()) //VPNAutoconnectTipView() //TipView(tipGroup.currentTip as? VPNAutoconnectTip) @@ -64,7 +64,7 @@ public struct TunnelControllerView: View { SiteTroubleshootingView() .padding(.top, 5) - if #available(macOS 15.0, *) { + if #available(macOS 14.0, *) { //VPNDomainExclusionsTipView() //.padding(.horizontal, 9) //.padding(.vertical, 6) @@ -201,10 +201,8 @@ public struct TunnelControllerView: View { } } - if #available(macOS 15.0, *) { + if #available(macOS 14.0, *) { TipView(geoswitchingTip) - //VPNGeoswitchingTipView() - //TipView(tipGroup.currentTip as? VPNGeoswitchingTip) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -237,46 +235,30 @@ public struct TunnelControllerView: View { // MARK: - Tips - let geoswitchingTip: VPNGeoswitchingTip = { + var geoswitchingTip: VPNGeoswitchingTip { + let tip = VPNGeoswitchingTip() if #available(macOS 14.0, *) { - if tip.shouldDisplay { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() - await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() - } - } - } - } + tipsModel.subscribeToTipStatusChanges(tip) } return tip - }() + } - let snoozeTip: VPNDomainExclusionsTip = { + var snoozeTip: VPNDomainExclusionsTip { let tip = VPNDomainExclusionsTip() if #available(macOS 14.0, *) { - if tip.shouldDisplay { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() - } - } - } - } + tipsModel.subscribeToTipStatusChanges(tip) } return tip - }() + } - let autoconnectTip: VPNAutoconnectTip = { + var autoconnectTip: VPNAutoconnectTip { VPNAutoconnectTip() - }() + } // MARK: - Rows From a2fa5dc5d85894f1a03d6aa9b090be10c1af0cdc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 29 Oct 2024 12:10:26 +0100 Subject: [PATCH 16/34] WIP --- .../Model/VPNDomainExclusionsTip.swift | 10 ++++++++++ .../Views/TipViews/Model/VPNTipsModel.swift | 11 ++++------- .../TunnelControllerView.swift | 19 +++++++++++++++++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 36ecb8a64b..15c4e85cd8 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -32,6 +32,13 @@ extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var hasActiveSite: Bool = false + /// Whether the tip can be shown. + /// + /// This tip is not shown for the VPN menu app. + /// + @Parameter(.transient) + static var canShow: Bool = false + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.geolocationTipDismissedEvent") /// The containing view was opened when the VPN was already connected. @@ -64,6 +71,9 @@ extension VPNDomainExclusionsTip: Tip { #Rule(Self.$vpnEnabled) { $0 } + #Rule(Self.$canShow) { + $0 + } #Rule(Self.geolocationTipDismissedEvent) { $0.donations.count > 0 } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 954de8ed21..51bc763de6 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -33,7 +33,6 @@ public final class VPNTipsModel: ObservableObject { return } - print("🧉🧉 activeSiteInfo: \(String(describing: activeSiteInfo))") handleActiveSiteInfoChanged(newValue: activeSiteInfo) } } @@ -45,7 +44,6 @@ public final class VPNTipsModel: ObservableObject { return } - print("🧉🧉 activeSiteInfo: \(String(describing: connectionStatus))") handleConnectionStatusChanged(oldValue: oldValue, newValue: connectionStatus) } } @@ -53,7 +51,6 @@ public final class VPNTipsModel: ObservableObject { @Published private(set) var featureFlag: Bool - //private var tips: TipGrouping private let vpnSettings: VPNSettings private let logger: Logger private var cancellables = Set() @@ -65,7 +62,6 @@ public final class VPNTipsModel: ObservableObject { vpnSettings: VPNSettings, logger: Logger) { - print("🧉🟢 New model instance") self.activeSiteInfo = activeSitePublisher.value self.connectionStatus = statusObserver.recentValue self.featureFlag = featureFlagPublisher.value @@ -73,6 +69,8 @@ public final class VPNTipsModel: ObservableObject { self.vpnSettings = vpnSettings if #available(macOS 14.0, *) { + VPNDomainExclusionsTip.canShow = !isMenuApp + handleActiveSiteInfoChanged(newValue: activeSiteInfo) handleConnectionStatusChanged(oldValue: connectionStatus, newValue: connectionStatus) @@ -123,7 +121,6 @@ public final class VPNTipsModel: ObservableObject { case .connected: if case oldValue = .connecting { Task { - print("🧉💎 Geoswitching tip donated") await VPNGeoswitchingTip.vpnConnectedEvent.donate() } } @@ -145,7 +142,7 @@ public final class VPNTipsModel: ObservableObject { } } - @available(macOS 14.0, *) + /*@available(macOS 14.0, *) func subscribeToTipStatusChanges(_ tip: VPNGeoswitchingTip) { if tip.shouldDisplay { Task { @@ -170,5 +167,5 @@ public final class VPNTipsModel: ObservableObject { } } } - } + }*/ } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 8e91eb70d3..072285c239 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -240,7 +240,14 @@ public struct TunnelControllerView: View { let tip = VPNGeoswitchingTip() if #available(macOS 14.0, *) { - tipsModel.subscribeToTipStatusChanges(tip) + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() + await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() + } + } + } } return tip @@ -250,7 +257,15 @@ public struct TunnelControllerView: View { let tip = VPNDomainExclusionsTip() if #available(macOS 14.0, *) { - tipsModel.subscribeToTipStatusChanges(tip) + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() + } + } + } + } } return tip From 9ca31ecb701e2c1391b34dd5c53912957cf1c4ef Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 19 Nov 2024 11:47:28 +0100 Subject: [PATCH 17/34] WIP --- DuckDuckGo/Application/AppDelegate.swift | 2 +- DuckDuckGo/TipKit/TipKitAppEventHandling.swift | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index c8826a85ef..e74ee1fcc5 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -388,7 +388,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: subscriptionManager.accountManager)).applicationDidFinishLaunching() - TipKitAppEventHandler().appDidFinishLaunching() + TipKitAppEventHandler(featureFlagger: featureFlagger).appDidFinishLaunching() setUpAutoClearHandler() diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 71bc59c7f5..86c2b426ad 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -18,6 +18,7 @@ // import Foundation +import BrowserServicesKit import os.log import TipKit @@ -28,16 +29,24 @@ protocol TipKitAppEventHandling { struct TipKitAppEventHandler: TipKitAppEventHandling { private let controller: TipKitController + private let featureFlagger: FeatureFlagger private let logger: Logger init(controller: TipKitController = .make(), + featureFlagger: FeatureFlagger, logger: Logger = .tipKit) { self.controller = controller + self.featureFlagger = featureFlagger self.logger = logger } func appDidFinishLaunching() { + guard featureFlagger.isFeatureOn(.networkProtectionUserTips) else { + logger.log("TipKit disabled by remote feature flag.") + return + } + if #available(macOS 14.0, *) { typealias DataStoreLocation = Tips.ConfigurationOption.DatastoreLocation @@ -50,7 +59,7 @@ struct TipKitAppEventHandler: TipKitAppEventHandling { controller.configureTipKit([ .displayFrequency(.immediate), - .datastoreLocation(dataStoreLocation) + .datastoreLocation(.applicationDefault) ]) } else { logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") From 865a5a7feb8caccc7a432e5ffdf4ea4611e8d004 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 19 Nov 2024 15:24:24 +0100 Subject: [PATCH 18/34] Fixes TipKit on macOS. More fixes coming up. --- .../TipKit/TipKitAppEventHandling.swift | 3 +- DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 6 ++-- .../TipViews/Model/VPNGeoswitchingTip.swift | 8 +++-- .../Views/TipViews/Model/VPNTipsModel.swift | 22 +++++++++++-- .../TunnelControllerView.swift | 31 +++++++++---------- 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 86c2b426ad..24108f3cd1 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -53,13 +53,12 @@ struct TipKitAppEventHandler: TipKitAppEventHandling { let appConfigurationGroupIdentifier = Bundle.main.appGroup(bundle: .appConfiguration) guard let dataStoreLocation = try? DataStoreLocation.groupContainer(identifier: appConfigurationGroupIdentifier) else { - fatalError() } controller.configureTipKit([ .displayFrequency(.immediate), - .datastoreLocation(.applicationDefault) + .datastoreLocation(dataStoreLocation) ]) } else { logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 15167ce480..2748926f11 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -366,8 +366,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { @MainActor func applicationDidFinishLaunching(_ aNotification: Notification) { - TipKitAppEventHandler().appDidFinishLaunching() - APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) Logger.networkProtection.log("DuckDuckGoVPN started") @@ -377,6 +375,10 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { // Load cached config (if any) privacyConfigurationManager.reload(etag: configurationStore.loadEtag(for: .privacyConfiguration), data: configurationStore.loadData(for: .privacyConfiguration)) + // It's important for this to be set-up after the privacy configuration is loaded + // as it relies on it for the remote feature flag. + TipKitAppEventHandler(featureFlagger: featureFlagger).appDidFinishLaunching() + setupMenuVisibility() Task { @MainActor in diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift index 7d583dd1d5..88bb6d1dfc 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift @@ -25,7 +25,9 @@ struct VPNGeoswitchingTip {} @available(macOS 14.0, *) extension VPNGeoswitchingTip: Tip { - static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.geoswitching.vpnConnectedEvent") + + @Parameter + static var vpnEnabledAtLeastOnce: Bool = false var id: String { "com.duckduckgo.vpn.tip.geoswitching" @@ -44,8 +46,8 @@ extension VPNGeoswitchingTip: Tip { } var rules: [Rule] { - #Rule(Self.vpnConnectedEvent) { - $0.donations.donatedWithin(.week).count > 0 + #Rule(Self.$vpnEnabledAtLeastOnce) { + $0 } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 51bc763de6..8c2fef34e8 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -120,9 +120,7 @@ public final class VPNTipsModel: ObservableObject { switch newValue { case .connected: if case oldValue = .connecting { - Task { - await VPNGeoswitchingTip.vpnConnectedEvent.donate() - } + VPNGeoswitchingTip.vpnEnabledAtLeastOnce = true } VPNAutoconnectTip.vpnEnabled = true @@ -142,6 +140,24 @@ public final class VPNTipsModel: ObservableObject { } } + let geoswitchingTip: VPNGeoswitchingTip = { + + let tip = VPNGeoswitchingTip() + + if #available(macOS 14.0, *) { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() + await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() + } + } + } + } + + return tip + }() + /*@available(macOS 14.0, *) func subscribeToTipStatusChanges(_ tip: VPNGeoswitchingTip) { if tip.shouldDisplay { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 072285c239..8627fd1a37 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -21,6 +21,7 @@ import SwiftUIExtensions import Combine import NetworkProtection import Lottie +import os.log import TipKit public struct TunnelControllerView: View { @@ -54,7 +55,7 @@ public struct TunnelControllerView: View { featureToggleRow() if #available(macOS 14.0, *) { - TipView(VPNAutoconnectTip()) + TipView(autoconnectTip) //VPNAutoconnectTipView() //TipView(tipGroup.currentTip as? VPNAutoconnectTip) .padding(.horizontal, 9) @@ -72,7 +73,7 @@ public struct TunnelControllerView: View { if tipsModel.featureFlag {//, //let tip = tipsModel.currentTip as? VPNDomainExclusionsTip { - TipView(VPNDomainExclusionsTip()) + TipView(domainExclusionsTip) //TipView(tipGroup.currentTip as? VPNDomainExclusionsTip) //VPNDomainExclusionsTipView() .padding(.horizontal, 9) @@ -202,7 +203,7 @@ public struct TunnelControllerView: View { } if #available(macOS 14.0, *) { - TipView(geoswitchingTip) + TipView(tipsModel.geoswitchingTip) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -235,7 +236,7 @@ public struct TunnelControllerView: View { // MARK: - Tips - var geoswitchingTip: VPNGeoswitchingTip { + /*let geoswitchingTip: VPNGeoswitchingTip = { let tip = VPNGeoswitchingTip() @@ -251,29 +252,27 @@ public struct TunnelControllerView: View { } return tip - } + }()*/ - var snoozeTip: VPNDomainExclusionsTip { + let domainExclusionsTip: VPNDomainExclusionsTip = { let tip = VPNDomainExclusionsTip() if #available(macOS 14.0, *) { - if tip.shouldDisplay { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() - } + Task { + for await status in tip.statusUpdates { + Logger.networkProtection.debug("🧉 DomainExclusionsTip status: \(String(describing: status), privacy: .public)") + + if case .invalidated = status { + await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() } } } } return tip - } + }() - var autoconnectTip: VPNAutoconnectTip { - VPNAutoconnectTip() - } + let autoconnectTip = VPNAutoconnectTip() // MARK: - Rows From d807463f65fcd79b96d4d6de460250528a6ac8cf Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 19 Nov 2024 16:12:20 +0100 Subject: [PATCH 19/34] WIP --- .../TipKit/TipKitAppEventHandling.swift | 5 +++ .../Model/VPNDomainExclusionsTip.swift | 10 ------ .../Views/TipViews/Model/VPNTipsModel.swift | 33 ++++++++++++++--- .../TunnelControllerView.swift | 36 ++++++++----------- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 24108f3cd1..c8850222a9 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -50,6 +50,11 @@ struct TipKitAppEventHandler: TipKitAppEventHandling { if #available(macOS 14.0, *) { typealias DataStoreLocation = Tips.ConfigurationOption.DatastoreLocation + /// A this time TipKit does not seem to handle synchronization of state between multiple apps very well. + /// That said, we still use the app configuration group for the data store in hopes this will soon change. + /// As long as we don't use TipKit for the same views from multiple Apps we'll be fine, but we can test + /// whether it's still broken rather easily if we keep the state in a shared app group, and we avoid having + /// to migrate the store in the future. let appConfigurationGroupIdentifier = Bundle.main.appGroup(bundle: .appConfiguration) guard let dataStoreLocation = try? DataStoreLocation.groupContainer(identifier: appConfigurationGroupIdentifier) else { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 15c4e85cd8..36ecb8a64b 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -32,13 +32,6 @@ extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var hasActiveSite: Bool = false - /// Whether the tip can be shown. - /// - /// This tip is not shown for the VPN menu app. - /// - @Parameter(.transient) - static var canShow: Bool = false - static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.geolocationTipDismissedEvent") /// The containing view was opened when the VPN was already connected. @@ -71,9 +64,6 @@ extension VPNDomainExclusionsTip: Tip { #Rule(Self.$vpnEnabled) { $0 } - #Rule(Self.$canShow) { - $0 - } #Rule(Self.geolocationTipDismissedEvent) { $0.donations.count > 0 } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 8c2fef34e8..3e7a5f9a7b 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -49,8 +49,9 @@ public final class VPNTipsModel: ObservableObject { } @Published - private(set) var featureFlag: Bool + private var featureFlag: Bool + private let isMenuApp: Bool private let vpnSettings: VPNSettings private let logger: Logger private var cancellables = Set() @@ -65,12 +66,11 @@ public final class VPNTipsModel: ObservableObject { self.activeSiteInfo = activeSitePublisher.value self.connectionStatus = statusObserver.recentValue self.featureFlag = featureFlagPublisher.value + self.isMenuApp = isMenuApp self.logger = logger self.vpnSettings = vpnSettings if #available(macOS 14.0, *) { - VPNDomainExclusionsTip.canShow = !isMenuApp - handleActiveSiteInfoChanged(newValue: activeSiteInfo) handleConnectionStatusChanged(oldValue: connectionStatus, newValue: connectionStatus) @@ -80,6 +80,10 @@ public final class VPNTipsModel: ObservableObject { } } + var canShowTips: Bool { + !isMenuApp && featureFlag + } + @available(macOS 14.0, *) private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { publisher @@ -112,7 +116,8 @@ public final class VPNTipsModel: ObservableObject { @available(macOS 14.0, *) private func handleActiveSiteInfoChanged(newValue: ActiveSiteInfo?) { - VPNDomainExclusionsTip.hasActiveSite = (activeSiteInfo != nil) + Logger.networkProtection.debug("🧉 Active site info changed: \(String(describing: newValue))") + return VPNDomainExclusionsTip.hasActiveSite = (activeSiteInfo != nil) } @available(macOS 14.0, *) @@ -140,6 +145,26 @@ public final class VPNTipsModel: ObservableObject { } } + // MARK: - Tips + + let autoconnectTip = VPNAutoconnectTip() + + let domainExclusionsTip: VPNDomainExclusionsTip = { + let tip = VPNDomainExclusionsTip() + + if #available(macOS 14.0, *) { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() + } + } + } + } + + return tip + }() + let geoswitchingTip: VPNGeoswitchingTip = { let tip = VPNGeoswitchingTip() diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 8627fd1a37..e9321e1d34 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -54,10 +54,10 @@ public struct TunnelControllerView: View { featureToggleRow() - if #available(macOS 14.0, *) { - TipView(autoconnectTip) - //VPNAutoconnectTipView() - //TipView(tipGroup.currentTip as? VPNAutoconnectTip) + if #available(macOS 14.0, *), + tipsModel.canShowTips { + + TipView(tipsModel.autoconnectTip) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -65,20 +65,12 @@ public struct TunnelControllerView: View { SiteTroubleshootingView() .padding(.top, 5) - if #available(macOS 14.0, *) { - //VPNDomainExclusionsTipView() - //.padding(.horizontal, 9) - //.padding(.vertical, 6) - - if tipsModel.featureFlag {//, - //let tip = tipsModel.currentTip as? VPNDomainExclusionsTip { + if #available(macOS 14.0, *), + tipsModel.canShowTips { - TipView(domainExclusionsTip) - //TipView(tipGroup.currentTip as? VPNDomainExclusionsTip) - //VPNDomainExclusionsTipView() - .padding(.horizontal, 9) - .padding(.vertical, 6) - } + TipView(tipsModel.domainExclusionsTip) + .padding(.horizontal, 9) + .padding(.vertical, 6) } Divider() @@ -202,7 +194,9 @@ public struct TunnelControllerView: View { } } - if #available(macOS 14.0, *) { + if #available(macOS 14.0, *), + tipsModel.canShowTips { + TipView(tipsModel.geoswitchingTip) .padding(.horizontal, 9) .padding(.vertical, 6) @@ -253,7 +247,7 @@ public struct TunnelControllerView: View { return tip }()*/ - +/* let domainExclusionsTip: VPNDomainExclusionsTip = { let tip = VPNDomainExclusionsTip() @@ -270,9 +264,9 @@ public struct TunnelControllerView: View { } return tip - }() + }()*/ - let autoconnectTip = VPNAutoconnectTip() + //let autoconnectTip = VPNAutoconnectTip() // MARK: - Rows From a32cb01310d9b52d4e9e0d6e0b585b2ef79b960a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 19 Nov 2024 16:28:04 +0100 Subject: [PATCH 20/34] Rolls back some unintentional changes --- .../DuckDuckGo Privacy Browser.xcscheme | 2 +- DuckDuckGo/InfoPlist.xcstrings | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index a0ad0111f9..5cd264a90f 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -79,7 +79,7 @@ Date: Tue, 19 Nov 2024 18:23:11 +0100 Subject: [PATCH 21/34] WIP --- .../TipViews/Model/VPNAutoconnectTip.swift | 1 - .../Model/VPNDomainExclusionsTip.swift | 1 - .../TipViews/Model/VPNGeoswitchingTip.swift | 1 - .../Views/TipViews/Model/VPNTipsModel.swift | 119 ++++++++---------- .../TunnelControllerView.swift | 42 +------ .../TunnelControllerViewModel.swift | 8 -- 6 files changed, 51 insertions(+), 121 deletions(-) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index 0de70d87bf..3a033d165e 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -1,6 +1,5 @@ // // VPNAutoconnectTip.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 36ecb8a64b..1a4002f7fd 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -1,6 +1,5 @@ // // VPNDomainExclusionsTip.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift index 88bb6d1dfc..39eaab6b3a 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift @@ -1,6 +1,5 @@ // // VPNGeoswitchingTip.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 3e7a5f9a7b..a5566261ba 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -77,6 +77,9 @@ public final class VPNTipsModel: ObservableObject { subscribeToConnectionStatusChanges(statusObserver) subscribeToFeatureFlagChanges(featureFlagPublisher) subscribeToActiveSiteChanges(activeSitePublisher) + + subscribeToStatusChanges(for: domainExclusionsTip) + subscribeToStatusChanges(for: geoswitchingTip) } } @@ -84,6 +87,8 @@ public final class VPNTipsModel: ObservableObject { !isMenuApp && featureFlag } + // MARK - Subscriptions + @available(macOS 14.0, *) private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { publisher @@ -112,6 +117,46 @@ public final class VPNTipsModel: ObservableObject { .store(in: &cancellables) } + // MARK: - Tips + + let autoconnectTip = VPNAutoconnectTip() + let domainExclusionsTip = VPNDomainExclusionsTip() + let geoswitchingTip = VPNGeoswitchingTip() + + // MARK: - Tip Action handling + + @available(macOS 14.0, *) + func autoconnectTipActionHandler(_ action: Tip.Action) { + if action.id == VPNAutoconnectTip.ActionIdentifiers.enable.rawValue { + vpnSettings.connectOnLogin = true + } + } + + // MARK: - Subscriptions: Tips + + @available(macOS 14.0, *) + func subscribeToStatusChanges(for tip: VPNGeoswitchingTip) { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() + await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() + } + } + } + } + + @available(macOS 14.0, *) + func subscribeToStatusChanges(for tip: VPNDomainExclusionsTip) { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() + } + } + } + } + // MARK: - Handle Refreshing @available(macOS 14.0, *) @@ -136,77 +181,13 @@ public final class VPNTipsModel: ObservableObject { } } - // MARK: - Tip Action handling - - @available(macOS 14.0, *) - func autoconnectTipActionHandler(_ action: Tip.Action) { - if action.id == VPNAutoconnectTip.ActionIdentifiers.enable.rawValue { - vpnSettings.connectOnLogin = true - } - } - - // MARK: - Tips - - let autoconnectTip = VPNAutoconnectTip() - - let domainExclusionsTip: VPNDomainExclusionsTip = { - let tip = VPNDomainExclusionsTip() - - if #available(macOS 14.0, *) { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() - } - } - } - } - - return tip - }() + // MARK: - UI Events - let geoswitchingTip: VPNGeoswitchingTip = { + func handleTunnelControllerShown() async { + if #available(macOS 14.0, *), + case .invalidated = VPNGeoswitchingTip().status { - let tip = VPNGeoswitchingTip() - - if #available(macOS 14.0, *) { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() - await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() - } - } - } - } - - return tip - }() - - /*@available(macOS 14.0, *) - func subscribeToTipStatusChanges(_ tip: VPNGeoswitchingTip) { - if tip.shouldDisplay { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() - await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() - } - } - } + await VPNDomainExclusionsTip.viewOpenedWhenVPNAlreadyConnectedEvent.donate() } } - - @available(macOS 14.0, *) - func subscribeToTipStatusChanges(_ tip: VPNDomainExclusionsTip) { - if tip.shouldDisplay { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() - } - } - } - } - }*/ } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index e9321e1d34..55fc3b2631 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -85,7 +85,7 @@ public struct TunnelControllerView: View { } .onAppear { Task { - await model.handleTunnelControllerShown() + await tipsModel.handleTunnelControllerShown() } } } @@ -228,46 +228,6 @@ public struct TunnelControllerView: View { } } - // MARK: - Tips - - /*let geoswitchingTip: VPNGeoswitchingTip = { - - let tip = VPNGeoswitchingTip() - - if #available(macOS 14.0, *) { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() - await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() - } - } - } - } - - return tip - }()*/ -/* - let domainExclusionsTip: VPNDomainExclusionsTip = { - let tip = VPNDomainExclusionsTip() - - if #available(macOS 14.0, *) { - Task { - for await status in tip.statusUpdates { - Logger.networkProtection.debug("🧉 DomainExclusionsTip status: \(String(describing: status), privacy: .public)") - - if case .invalidated = status { - await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() - } - } - } - } - - return tip - }()*/ - - //let autoconnectTip = VPNAutoconnectTip() - // MARK: - Rows private func dividerRow() -> some View { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift index c12a8dc1f8..b0945d7a6f 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerViewModel.swift @@ -535,14 +535,6 @@ public final class TunnelControllerViewModel: ObservableObject { } } #endif - - // MARK: - UI Events - - func handleTunnelControllerShown() async { - if #available(macOS 14.0, *) { - await VPNDomainExclusionsTip.viewOpenedWhenVPNAlreadyConnectedEvent.donate() - } - } } extension DataVolume { From ed60c48cc760509f4e3e7062c0df71bde9b565dc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 20 Nov 2024 16:50:00 +0100 Subject: [PATCH 22/34] Pushes additional fixes --- .../Model/VPNPreferencesModel.swift | 24 +++++++++++++++++-- .../SiteTroubleshootingView.swift | 7 ++++++ .../TipViews/Model/VPNAutoconnectTip.swift | 1 - .../Model/VPNDomainExclusionsTip.swift | 18 +++++++------- .../Views/TipViews/Model/VPNTipsModel.swift | 19 +++++++++++---- .../TunnelControllerView.swift | 13 ++++++---- 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift index fd841fcedf..76dbc16634 100644 --- a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift @@ -32,12 +32,20 @@ final class VPNPreferencesModel: ObservableObject { @Published var connectOnLogin: Bool { didSet { + guard settings.connectOnLogin != connectOnLogin else { + return + } + settings.connectOnLogin = connectOnLogin } } @Published var excludeLocalNetworks: Bool { didSet { + guard settings.excludeLocalNetworks != excludeLocalNetworks else { + return + } + settings.excludeLocalNetworks = excludeLocalNetworks Task { @@ -49,8 +57,6 @@ final class VPNPreferencesModel: ObservableObject { } } - @Published var secureDNS: Bool = true - @Published var showInMenuBar: Bool { didSet { settings.showInMenuBar = showInMenuBar @@ -117,6 +123,8 @@ final class VPNPreferencesModel: ObservableObject { locationItem = VPNLocationPreferenceItemModel(selectedLocation: settings.selectedLocation) subscribeToOnboardingStatusChanges(defaults: defaults) + subscribeToConnectOnLoginSettingChanges() + subscribeToExcludeLocalNetworksSettingChanges() subscribeToShowInMenuBarSettingChanges() subscribeToShowInBrowserToolbarSettingsChanges() subscribeToLocationSettingChanges() @@ -129,6 +137,18 @@ final class VPNPreferencesModel: ObservableObject { .store(in: &cancellables) } + func subscribeToConnectOnLoginSettingChanges() { + settings.connectOnLoginPublisher + .assign(to: \.connectOnLogin, onWeaklyHeld: self) + .store(in: &cancellables) + } + + func subscribeToExcludeLocalNetworksSettingChanges() { + settings.excludeLocalNetworksPublisher + .assign(to: \.excludeLocalNetworks, onWeaklyHeld: self) + .store(in: &cancellables) + } + func subscribeToShowInMenuBarSettingChanges() { settings.showInMenuBarPublisher .removeDuplicates() diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift index 98551d3713..b8f5e0d929 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingView.swift @@ -27,6 +27,9 @@ public struct SiteTroubleshootingView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var model: Model + @EnvironmentObject + private var tipsModel: VPNTipsModel + // MARK: - View Contents public var body: some View { @@ -51,6 +54,10 @@ public struct SiteTroubleshootingView: View { Toggle(isOn: Binding(get: { siteInfo.excluded }, set: { value in + if #available(macOS 14.0, *) { + tipsModel.handleSiteExcluded() + } + model.setExclusion(value, forDomain: siteInfo.domain) })) { HStack(spacing: 5) { diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index 3a033d165e..8f11a5794c 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -55,7 +55,6 @@ extension VPNAutoconnectTip: Tip { var actions: [Action] { [Action(id: ActionIdentifiers.enable.rawValue) { Text("Enable") - .foregroundStyle(Color(.linkColor)) }] } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 1a4002f7fd..8e30981e1e 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -31,14 +31,14 @@ extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var hasActiveSite: Bool = false - static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.geolocationTipDismissedEvent") - - /// The containing view was opened when the VPN was already connected. + /// This flag attempt to capture the user's intent to disable the VPN, which is the right time to show this tip. /// - /// This condition may be indicative that the user is struggling, so they might want - /// to exclude a site. + /// If the VPN is ON when the status view is opened, there's a reasonable expectation the user might want to disable the VPN. /// - static let viewOpenedWhenVPNAlreadyConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.popoverOpenedWhileAlreadyConnected") + @Parameter + static var statusViewOpenedWhenVPNIsOn: Bool = false + + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.geolocationTipDismissedEvent") var id: String { "com.duckduckgo.vpn.tip.domainExclusions" @@ -63,10 +63,10 @@ extension VPNDomainExclusionsTip: Tip { #Rule(Self.$vpnEnabled) { $0 } - #Rule(Self.geolocationTipDismissedEvent) { - $0.donations.count > 0 + #Rule(Self.$statusViewOpenedWhenVPNIsOn) { + $0 } - #Rule(Self.viewOpenedWhenVPNAlreadyConnectedEvent) { + #Rule(Self.geolocationTipDismissedEvent) { $0.donations.count > 0 } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index a5566261ba..b4feb73144 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -129,6 +129,8 @@ public final class VPNTipsModel: ObservableObject { func autoconnectTipActionHandler(_ action: Tip.Action) { if action.id == VPNAutoconnectTip.ActionIdentifiers.enable.rawValue { vpnSettings.connectOnLogin = true + + autoconnectTip.invalidate(reason: .actionPerformed) } } @@ -183,11 +185,20 @@ public final class VPNTipsModel: ObservableObject { // MARK: - UI Events - func handleTunnelControllerShown() async { - if #available(macOS 14.0, *), - case .invalidated = VPNGeoswitchingTip().status { + @available(macOS 14.0, *) + func handleLocationsShown() { + geoswitchingTip.invalidate(reason: .actionPerformed) + } - await VPNDomainExclusionsTip.viewOpenedWhenVPNAlreadyConnectedEvent.donate() + @available(macOS 14.0, *) + func handleSiteExcluded() { + geoswitchingTip.invalidate(reason: .actionPerformed) + } + + @available(macOS 14.0, *) + func handleTunnelControllerShown() { + if case .connected = connectionStatus { + VPNDomainExclusionsTip.statusViewOpenedWhenVPNIsOn = true } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 55fc3b2631..5691abbb3f 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -34,7 +34,8 @@ public struct TunnelControllerView: View { /// The view model that this instance will use. /// - @ObservedObject var model: TunnelControllerViewModel + @ObservedObject + var model: TunnelControllerViewModel // MARK: - Initializers @@ -57,7 +58,7 @@ public struct TunnelControllerView: View { if #available(macOS 14.0, *), tipsModel.canShowTips { - TipView(tipsModel.autoconnectTip) + TipView(tipsModel.autoconnectTip, action: tipsModel.autoconnectTipActionHandler) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -84,8 +85,8 @@ public struct TunnelControllerView: View { } } .onAppear { - Task { - await tipsModel.handleTunnelControllerShown() + if #available(macOS 14.0, *) { + tipsModel.handleTunnelControllerShown() } } } @@ -150,6 +151,10 @@ public struct TunnelControllerView: View { .padding(EdgeInsets(top: 6, leading: 9, bottom: 6, trailing: 9)) MenuItemCustomButton { + if #available(macOS 14.0, *) { + tipsModel.handleLocationsShown() + } + model.showLocationSettings() dismiss() } label: { isHovered in From bf91f74c176e7c2a777d1d33677455bfdbb9e325 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 20 Nov 2024 17:14:12 +0100 Subject: [PATCH 23/34] Addresses an issue with the code to handle the TipKit feature flag --- ...etworkProtectionNavBarPopoverManager.swift | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 7cc7d8d02c..83e3243ae2 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -30,6 +30,8 @@ import os.log import Subscription import SwiftUI import VPNAppLauncher +import BrowserServicesKit +import FeatureFlags protocol NetworkProtectionIPCClient { var ipcStatusObserver: ConnectionStatusObserver { get } @@ -162,8 +164,23 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { _ = try? await self?.vpnUninstaller.uninstall(removeSystemExtension: true) }) - // TODO: replace with access to actual feature flag - let tipsFeatureFlagPublisher = CurrentValuePublisher(initialValue: true, publisher: Just(true).eraseToAnyPublisher()) + let featureFlagger = NSApp.delegateTyped.featureFlagger + let tipsFeatureFlagInitialValue = featureFlagger.isFeatureOn(.networkProtectionUserTips) + let tipsFeatureFlagPublisher: CurrentValuePublisher + + if let overridesHandler = featureFlagger.localOverrides?.actionHandler as? FeatureFlagOverridesPublishingHandler { + + let featureFlagPublisher = overridesHandler.flagDidChangePublisher + .filter { $0.0 == .networkProtectionUserTips } + + tipsFeatureFlagPublisher = CurrentValuePublisher( + initialValue: tipsFeatureFlagInitialValue, + publisher: Just(tipsFeatureFlagInitialValue).eraseToAnyPublisher()) + } else { + tipsFeatureFlagPublisher = CurrentValuePublisher( + initialValue: tipsFeatureFlagInitialValue, + publisher: Just(tipsFeatureFlagInitialValue).eraseToAnyPublisher()) + } let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, From 3ae57006946b1c34e4f8ab3fa02db9dda3dd9704 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 20 Nov 2024 17:21:30 +0100 Subject: [PATCH 24/34] Fixes swiftlint warnings --- DuckDuckGo/TipKit/Logger+TipKit.swift | 1 - .../TipKit/TipKitAppEventHandling.swift | 1 - ...itController+ConvenienceInitializers.swift | 1 - DuckDuckGo/TipKit/TipKitController.swift | 1 - .../TipKitDebugOptionsUIActionHandling.swift | 1 - .../CombineExtensions/.gitignore | 8 ------- .../CombineExtensions/Package.swift | 24 ------------------- .../CombineExtensions/CombineExtensions.swift | 2 -- .../CombineExtensionsTests.swift | 6 ----- .../Combine/CurrentValuePublisher.swift | 24 ++++++++++++++----- .../Menu/StatusBarMenu.swift | 8 +++---- .../Views/TipViews/Model/VPNTipsModel.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 13 files changed, 24 insertions(+), 57 deletions(-) delete mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore delete mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift delete mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift delete mode 100644 LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift diff --git a/DuckDuckGo/TipKit/Logger+TipKit.swift b/DuckDuckGo/TipKit/Logger+TipKit.swift index 1d791692b4..26941cfc6f 100644 --- a/DuckDuckGo/TipKit/Logger+TipKit.swift +++ b/DuckDuckGo/TipKit/Logger+TipKit.swift @@ -1,6 +1,5 @@ // // Logger+TipKit.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index c8850222a9..614de3ecf3 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -1,6 +1,5 @@ // // TipKitAppEventHandling.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift index abe4b30fb0..ce1de51c30 100644 --- a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift +++ b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift @@ -1,6 +1,5 @@ // // TipKitController+ConvenienceInitializers.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift index c91a0b534f..8b8ff507e3 100644 --- a/DuckDuckGo/TipKit/TipKitController.swift +++ b/DuckDuckGo/TipKit/TipKitController.swift @@ -1,6 +1,5 @@ // // TipKitController.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift index 9ab3e9eb4d..eb2279b608 100644 --- a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -1,6 +1,5 @@ // // TipKitDebugOptionsUIActionHandling.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore b/LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore deleted file mode 100644 index 0023a53406..0000000000 --- a/LocalPackages/BuildToolPlugins/CombineExtensions/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift b/LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift deleted file mode 100644 index 649901ad34..0000000000 --- a/LocalPackages/BuildToolPlugins/CombineExtensions/Package.swift +++ /dev/null @@ -1,24 +0,0 @@ -// swift-tools-version: 6.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "CombineExtensions", - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "CombineExtensions", - targets: ["CombineExtensions"]), - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .target( - name: "CombineExtensions"), - .testTarget( - name: "CombineExtensionsTests", - dependencies: ["CombineExtensions"] - ), - ] -) diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift b/LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift deleted file mode 100644 index 08b22b80fc..0000000000 --- a/LocalPackages/BuildToolPlugins/CombineExtensions/Sources/CombineExtensions/CombineExtensions.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book diff --git a/LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift b/LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift deleted file mode 100644 index c733bf224b..0000000000 --- a/LocalPackages/BuildToolPlugins/CombineExtensions/Tests/CombineExtensionsTests/CombineExtensionsTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Testing -@testable import CombineExtensions - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift index a250aea649..557147b340 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Combine/CurrentValuePublisher.swift @@ -1,5 +1,20 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book +// +// CurrentValuePublisher.swift +// +// Copyright © 2024 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 Combine import Foundation @@ -27,10 +42,7 @@ public final class CurrentValuePublisher { } extension CurrentValuePublisher: Publisher { - public func receive(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { - + public func receive(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { wrappedPublisher.receive(subscriber: subscriber) } - - } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 9d93ed6165..451c2c949b 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -145,17 +145,17 @@ public final class StatusBarMenu: NSObject { activeSitePublisher: activeSitePublisher.eraseToAnyPublisher(), uiActionHandler: uiActionHandler) - // TODO: replace with access to actual feature flag + // We don't use tips in the status menu app. let tipsFeatureFlagPublisher = CurrentValuePublisher( - initialValue: true, - publisher: Just(true).eraseToAnyPublisher()) + initialValue: false, + publisher: Just(false).eraseToAnyPublisher()) let tipsModel = VPNTipsModel(featureFlagPublisher: tipsFeatureFlagPublisher, statusObserver: statusReporter.statusObserver, activeSitePublisher: activeSitePublisher, forMenuApp: true, vpnSettings: VPNSettings(defaults: userDefaults), - logger: Logger.init(subsystem: "DuckDuckGo", category: "TipKit")) + logger: Logger(subsystem: "DuckDuckGo", category: "TipKit")) let debugInformationViewModel = DebugInformationViewModel(showDebugInformation: isOptionKeyPressed) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index b4feb73144..16e0de4811 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -87,7 +87,7 @@ public final class VPNTipsModel: ObservableObject { !isMenuApp && featureFlag } - // MARK - Subscriptions + // MARK: - Subscriptions @available(macOS 14.0, *) private func subscribeToFeatureFlagChanges(_ publisher: CurrentValuePublisher) { diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index eee1efdb93..ff5dcabd99 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 18b2b7a24e88264628e0ec8d18b393ffea1773be Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 20 Nov 2024 17:32:53 +0100 Subject: [PATCH 25/34] Rolls back an unintentional change --- LocalPackages/SubscriptionUI/Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index ff5dcabd99..eee1efdb93 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 986777c1334439666132549c3da6b8024837f767 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 21 Nov 2024 13:59:59 +0100 Subject: [PATCH 26/34] Makes some changes to improve TipKit support --- DuckDuckGo/TipKit/TipKitAppEventHandling.swift | 15 +-------------- .../TipViews/Model/VPNDomainExclusionsTip.swift | 7 ++++--- .../Views/TipViews/Model/VPNTipsModel.swift | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 614de3ecf3..f5b14f3f01 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -47,22 +47,9 @@ struct TipKitAppEventHandler: TipKitAppEventHandling { } if #available(macOS 14.0, *) { - typealias DataStoreLocation = Tips.ConfigurationOption.DatastoreLocation - - /// A this time TipKit does not seem to handle synchronization of state between multiple apps very well. - /// That said, we still use the app configuration group for the data store in hopes this will soon change. - /// As long as we don't use TipKit for the same views from multiple Apps we'll be fine, but we can test - /// whether it's still broken rather easily if we keep the state in a shared app group, and we avoid having - /// to migrate the store in the future. - let appConfigurationGroupIdentifier = Bundle.main.appGroup(bundle: .appConfiguration) - - guard let dataStoreLocation = try? DataStoreLocation.groupContainer(identifier: appConfigurationGroupIdentifier) else { - fatalError() - } - controller.configureTipKit([ .displayFrequency(.immediate), - .datastoreLocation(dataStoreLocation) + .datastoreLocation(.applicationDefault) ]) } else { logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 8e30981e1e..0f3fae0d70 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -38,7 +38,8 @@ extension VPNDomainExclusionsTip: Tip { @Parameter static var statusViewOpenedWhenVPNIsOn: Bool = false - static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.domainExclusions.geolocationTipDismissedEvent") + @Parameter + static var geolocationTipDismissed: Bool = false var id: String { "com.duckduckgo.vpn.tip.domainExclusions" @@ -66,8 +67,8 @@ extension VPNDomainExclusionsTip: Tip { #Rule(Self.$statusViewOpenedWhenVPNIsOn) { $0 } - #Rule(Self.geolocationTipDismissedEvent) { - $0.donations.count > 0 + #Rule(Self.$geolocationTipDismissed) { + $0 } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 16e0de4811..b4563e2dbf 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -141,7 +141,7 @@ public final class VPNTipsModel: ObservableObject { Task { for await status in tip.statusUpdates { if case .invalidated = status { - await VPNDomainExclusionsTip.geolocationTipDismissedEvent.donate() + VPNDomainExclusionsTip.geolocationTipDismissed = true await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() } } From 112a301a6cf40ad210dc3dfa64222e4b3ef81af4 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 21 Nov 2024 15:06:41 +0100 Subject: [PATCH 27/34] Fixes some issues with VPN tips --- .../Views/TipViews/Model/VPNAutoconnectTip.swift | 9 +++++---- .../Views/TipViews/Model/VPNTipsModel.swift | 16 ++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index 8f11a5794c..a857760d58 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -29,13 +29,14 @@ extension VPNAutoconnectTip: Tip { case enable = "com.duckduckgo.vpn.tip.autoconnect.action.enable" } - static let domainExclusionsTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.autoconnect.domainExclusionsTipDismissedEvent") - static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.autoconnect.geolocationTipDismissedEvent") @Parameter(.transient) static var vpnEnabled: Bool = false + @Parameter + static var vpnEnabledWhenDomainExclusionsAlreadyDismissed: Bool = false + var id: String { "com.duckduckgo.vpn.tip.autoconnect" } @@ -62,8 +63,8 @@ extension VPNAutoconnectTip: Tip { #Rule(Self.$vpnEnabled) { $0 == true } - #Rule(Self.domainExclusionsTipDismissedEvent) { - $0.donations.count > 0 + #Rule(Self.$vpnEnabledWhenDomainExclusionsAlreadyDismissed) { + $0 } #Rule(Self.geolocationTipDismissedEvent) { $0.donations.count > 0 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index b4563e2dbf..d63ee6dc9f 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -78,7 +78,6 @@ public final class VPNTipsModel: ObservableObject { subscribeToFeatureFlagChanges(featureFlagPublisher) subscribeToActiveSiteChanges(activeSitePublisher) - subscribeToStatusChanges(for: domainExclusionsTip) subscribeToStatusChanges(for: geoswitchingTip) } } @@ -148,17 +147,6 @@ public final class VPNTipsModel: ObservableObject { } } - @available(macOS 14.0, *) - func subscribeToStatusChanges(for tip: VPNDomainExclusionsTip) { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - await VPNAutoconnectTip.domainExclusionsTipDismissedEvent.donate() - } - } - } - } - // MARK: - Handle Refreshing @available(macOS 14.0, *) @@ -173,6 +161,10 @@ public final class VPNTipsModel: ObservableObject { case .connected: if case oldValue = .connecting { VPNGeoswitchingTip.vpnEnabledAtLeastOnce = true + + if case .invalidated = domainExclusionsTip.status { + VPNAutoconnectTip.vpnEnabledWhenDomainExclusionsAlreadyDismissed = true + } } VPNAutoconnectTip.vpnEnabled = true From cd57672c34f690a9cabcdeff8a3b44bdb737886b Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 22 Nov 2024 12:10:21 +0100 Subject: [PATCH 28/34] Fixes several UI issues in the new VPN tips --- .../BothAppTargets/ActiveDomainPublisher.swift | 10 ++++++++-- .../NetworkProtectionNavBarPopoverManager.swift | 10 +++++++--- .../NetworkProtectionUI/Menu/StatusBarMenu.swift | 8 ++++++-- .../Icons/autoconnectTip.imageset/Contents.json | 2 +- .../Icons/autoconnectTip.imageset/Moon-32 1.pdf | Bin 1407 -> 0 bytes .../Power-Reconnect-32.pdf | Bin 0 -> 2980 bytes .../domainExclusionsTip.imageset/Contents.json | 2 +- .../Site-Broken-32.pdf | Bin 0 -> 2182 bytes .../Widget-Add-32 1.pdf | Bin 1763 -> 0 bytes .../Icons/geolocationTip.imageset/Contents.json | 2 +- .../geolocationTip.imageset/Location-32.pdf | Bin 0 -> 1570 bytes .../geolocationTip.imageset/Map-Pin-32 1.pdf | Bin 2190 -> 0 bytes .../SiteTroubleshootingViewModel.swift | 10 ++++++---- .../Views/TipViews/Model/VPNAutoconnectTip.swift | 7 ++++--- .../Views/TipViews/Model/VPNTipsModel.swift | 4 +++- .../TunnelControllerView.swift | 3 +++ 16 files changed, 40 insertions(+), 18 deletions(-) delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Moon-32 1.pdf create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Power-Reconnect-32.pdf create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Site-Broken-32.pdf delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Widget-Add-32 1.pdf create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Location-32.pdf delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Map-Pin-32 1.pdf diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveDomainPublisher.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveDomainPublisher.swift index 4a77857ae6..24648e7ef9 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveDomainPublisher.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/ActiveDomainPublisher.swift @@ -43,7 +43,13 @@ final class ActiveDomainPublisher { } } + @MainActor init(windowControllersManager: WindowControllersManager) { + + if let tabContent = windowControllersManager.lastKeyMainWindowController?.activeTab?.content { + activeDomain = Self.domain(from: tabContent) + } + self.windowControllersManager = windowControllersManager Task { @MainActor in @@ -73,7 +79,7 @@ final class ActiveDomainPublisher { @MainActor private func subscribeToActiveTabContentChanges() { activeTabContentCancellable = activeTab?.$content - .map(domain(from:)) + .map(Self.domain(from:)) .removeDuplicates() .assign(to: \.activeDomain, onWeaklyHeld: self) } @@ -88,7 +94,7 @@ final class ActiveDomainPublisher { } } - private func domain(from tabContent: Tab.TabContent) -> String? { + private static func domain(from tabContent: Tab.TabContent) -> String? { if case .url(let url, _, _) = tabContent { return url.host diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 83e3243ae2..3fd21aa827 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -112,13 +112,17 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { let proxySettings = TransparentProxySettings(defaults: .netP) let uiActionHandler = VPNUIActionHandler(vpnURLEventHandler: vpnURLEventHandler, proxySettings: proxySettings) + let connectionStatusPublisher = CurrentValuePublisher( + initialValue: statusReporter.statusObserver.recentValue, + publisher: statusReporter.statusObserver.publisher) + let activeSitePublisher = CurrentValuePublisher( - initialValue: nil, + initialValue: siteInfo, publisher: $siteInfo.eraseToAnyPublisher()) let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( - connectionStatusPublisher: statusReporter.statusObserver.publisher, - activeSitePublisher: activeSitePublisher.eraseToAnyPublisher(), + connectionStatusPublisher: connectionStatusPublisher, + activeSitePublisher: activeSitePublisher, uiActionHandler: uiActionHandler) let statusViewModel = NetworkProtectionStatusView.Model(controller: controller, diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index 451c2c949b..d5730c40ce 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -136,13 +136,17 @@ public final class StatusBarMenu: NSObject { return } + let connectionStatusPublisher = CurrentValuePublisher( + initialValue: .disconnected, + publisher: Just(NetworkProtection.ConnectionStatus.disconnected).eraseToAnyPublisher()) + let activeSitePublisher = CurrentValuePublisher( initialValue: ActiveSiteInfo?(nil), publisher: Just(nil).eraseToAnyPublisher()) let siteTroubleshootingViewModel = SiteTroubleshootingView.Model( - connectionStatusPublisher: Just(NetworkProtection.ConnectionStatus.disconnected).eraseToAnyPublisher(), - activeSitePublisher: activeSitePublisher.eraseToAnyPublisher(), + connectionStatusPublisher: connectionStatusPublisher, + activeSitePublisher: activeSitePublisher, uiActionHandler: uiActionHandler) // We don't use tips in the status menu app. diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json index d0573906e0..61e3f585d4 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Moon-32 1.pdf", + "filename" : "Power-Reconnect-32.pdf", "idiom" : "universal" } ], diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Moon-32 1.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Moon-32 1.pdf deleted file mode 100644 index 9648d1c6ea23c789c82aee593e576cc42148d224..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1407 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~g0XjsW1oyUkL&l+t|;~nok>cn zd6O9;)h(S4J2Ks1<6*YjFK>GJs_l&4?8|TTCo284|M{@L|Nj5|wSRt}zklxje@i=? ze|PuE#`9;^)~D~6-XC}E_vX_(YVE2{OD1zYkhxc!=rNtga{t|ZGRwa27Ak-JIXdLJ zOswBzu|>DG8qAi|zfs_S^3aiMFY4~`oi&?wYS!|I*DH5@3R$$f^jvFA`kIL4S;8A? zj^(jW-1T$Dkv$j0y`R#ym=2+ zR)(i)?-lpjdUNGMD}nA8`MVDB-xuDt*YL-@3;xG*gv{FP8^!IdZfz>~&i;j?sM)&i z=Q-_TsWI;Tzr|UY-#@mC+n;2!zj|+*mQd@ zJu1EBsUOO8c+q*&ZDLz{W71qUy49^ybv+QEeT=QA*F+*yBZxV9ONU@v$gDLRAFNe) zArg#VH}hddYQ` z^rV8|o9mo|+vYY+jV(K7yViyCitTY7j?CHaj$VFAJA&RQ=%rqmVW>IXEo`IBdEr=& zunSw(ZFFz=_~F7^DciIQU0Hke9gMEuO>kqc=%r}=${FGC?(n#}7!?c&) z&v!~?&RR5m8PgL5J)fDvdK{0qT}rfGhAGt=^+j!L3uInu6#J&p*o%7$i+z~m7uKXb z(#-bvqA%utJTq(WGE)hkh^1d{C0-~@bLK2oUL)ju-$(ZAx1t8#Um2n6%~vEaaY-@l zTs|R@$L`Ol9of9dmI)c=)xII&7WZLY#zM2v-uoX^8(YNPhxv?TF&_8@Ch@96kT??y~8go`>K~W zXS>}D)+@~Y_m(AlNKQGPv-kNXiMqM>5FHE6LLrp}sS5f5iRrLB<(rz4ndp>Xp%ATLpl1LE2!^o{ObEe(nG}>- zoL^d$oLZ~^%GRL#2+E?)`FSO&c|aRMc^W1dkXQs1Q!s@JA?19K5WGat_s&cKI#mJW zgCGT%J1K)r?-?k|Rv0I6|ssI#*0z(4@FiRm1E@TXJCkP;fEKJQYgv>3`gbWQ#fnkH9(a_Kc=zOS9 zNl{{EPHGVsDE2&EfWe_roS&Pjsi2XWq6rBU{h<8(5(SWl!2zrvoLQ9$bUnB@Oe`t^ Qd&khw!jwx@)z#k(0ELME9RL6T diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Power-Reconnect-32.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/autoconnectTip.imageset/Power-Reconnect-32.pdf new file mode 100644 index 0000000000000000000000000000000000000000..667ac9a9982298e81e50b73e8c0daccdc3e958fa GIT binary patch literal 2980 zcmZXWc{tQ-8^_BQQ;M>acaEiOna#{tN0PB0A{4^J7z{I-nX;9w5XQbQlY_>d%JMoA zq9R+8Y!#9{dr=sZcc$%}^Pc;U-}OA->$#Wj{rl%5Z+RMh0-}lp03bjxka*Dx00e4l z15qddmf*&|0-%4_L1<5J3YG)}p}jE_>}jkk(GAPWUK4=B3bGvR`Y2hXXb*fg zDT|sX3X)lf(q(S0gfuZXhje!*HYdlWb+valK6gzzCs3Rinw7mdpf96Lm5{O9unlcI zYAiU=5%-)vjm7RhG-gUvOE*7c?ig)J)jAgbXZm5;o4_3*m*owK$r9XuZKU@-dtO;c zyIeccx&Kw~d9>Fj|5&vGl4oSy!9{WmE%|XY(Lh)XC zOGUq*kkWTO@4Y%PB-(GG%+DW`|H=ut+fni(=N$+F+{`q!+ugv`3>ux zO2vjGgSwR(Z``L5sWKRY=a)pzF>=UV5gL|vs2;**ca z2)}ZrcHlGtH3aYfO6i>Z09A5jaHOSj^}f7PWJ{W2NRe{ie{#BXbW)0c+)04vf}-&B zTC#*Z)~agZNt8X5SaqNPlsQFSTEcx3Y7(GP7_v zF;(2eJp6+Ka)ziHvV3l30C~)|Wc|!tq*93!*?^jA+_Xq`{el-6ce(9OFH0|d_Q5)> zUQwrC_=O6U)>0m%c*ulc9&zgqR_H)6X{9!HBFpLnCgoalejBl9%172-Eo5|Jin+FHoN_u7a+s483mgjY1(N?zOMJPyaGYpAiwk8xw&jBSo0^5;|D7xU;8Rlnon_B4~Q z#YV1vBZBek}W zyXJ1_&Ly4vx_x~G1wt!rgR}fk141aWb%pnzJKHU83nt$My*t_v6q2;%-wOzF1P!kg zxBNk?xw9)FGF$`)99llChYEg;@av9@JLWPH9}75S{?06WuTlbVRURe>uY*2z3Ll)Z z5yzKJH7I1qQtA}a5bdeHNU!taaJM<@mt)cx|1-+>J$>*(l7AT5ytSTBwSQcH*6X?@ zzwniaxuGKwnu0NYbo+SRa5HOFxSXA-wiOPpafynY!ykb9%ViCZFG>hM zb&EROY1ZE^|9F9!9dH~g0q1J7ThD%AX?W8*GoJt$T&l|^G=&+V)tMFLQr8$^eSJed4R{6 zoL;*7ULHrykzzL=EN~xDksDAm3DM&Q+sUSObi}9b zLLUHj>!P=NgeFRKD`y=PWRMJw!QK|)(?2s124=mC@a^XPdawMrai>;2>mJY!n< zM}v5W_)*A!`i8tQ(_1gzOg4Kn)!vOwMK0?&@iYa+GG>A)&B3zGI<0+#_XV!UUXC_J zEF9VSdQ8T9J9bMao?@5rMYZ*;%JC(R$exr(5if;)dL}0Zk0!Y=~oQZj^1z>!C-(A0FMP)2FE_8<&&CZ%b4Q zjQ=KSS90*ldZT~GjUO~weJXRO_Rc+1 zV$qFti(HM4!MkCX%*a$ov76Xw3mms*aTg(x$oF+uQ6=A`z}x}8=ZJJpL6sa%(DPKM ziazy3aT)kzfCW@2UI=fOF{$p58zhl#5D!r@o#cUyitI5)m}T*~jAX&B0yTol&T13v zuTeFIpz#Lv?@E{5svn^f(|u}fx%ic)c%_ux>|e(jx)*0w(&aUZ?Prk(^$TkjM5jMG z@JiaI%F1#`T93zcRK*n-lyRogmj$Zxx1$@WDM>FfwcM<%kv{Uxy-$oJ7UtVNpV7(& z9^a5jXiX{QsY0umk&Uig)w41@EG%0!Bd$L#`dB5w|Dtc)rRRiTrM`#tl5}Gc03UDS zz9uE%`v{+B76(y0qyzA%@F!QJ!PoUgWD`$LuK4=$U+Yh$rM+)RN2kb{jwYCi6uwO^ zcj5Ucx$HWBHn2e0impI906vKQo$t@1_5=LHY}FSOv`B;@*gR1%trWbuR8%Tm^d~H+ z^BKYNd1Kk$IV1q5#V8d4c`TmnteWFqz^5#P#&B}x<7`d@Yw#&Rk8$63Hh`tbSto~4 z`|jsCQ)4cKI*|pc{qd9T_tqkwgg43VR)6@)dDVeinGIAwi0CI;v03RmazSAb$S>kj z`z5nT45UXO5GiD!!-XH5bvD2k3j|qWa6iGz9P8$ZIYsmbIsn0{;BWEQ0S5gk{dM|T z$qGv*UM9I>$v`&yv7wF4ar#681xsLcWP{&N(Go*qX+Zc7>2G+*mVRL)$kfw~H7bzp zzzX=Yk3R7-D;M&w`N`ig@#i)W|ER<^NdDE3J=k~S`WOnvn~3{osy_+q4gdp@fbaI% zUmyYwhrxj?`A0y&z;7oY;gySU@h=I^TEstJA)qYR|0ikul}I6BJiW0b z0Q>J5pJuH?flTzlDgqTe-4wqs1!P4eQh;p3-xdtA_6)?brvJvw7!u{19Vim64v?2O Hut5J0>(eBL literal 0 HcmV?d00001 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json index b5be95588c..0640c66ec7 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Widget-Add-32 1.pdf", + "filename" : "Site-Broken-32.pdf", "idiom" : "universal" } ], diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Site-Broken-32.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/domainExclusionsTip.imageset/Site-Broken-32.pdf new file mode 100644 index 0000000000000000000000000000000000000000..05129144c90dbf4e97e804c57f577bf20b33dc6e GIT binary patch literal 2182 zcmZXWc|6qlAIF=rj;So0Y)kZw9aoMSv*SL-H8O^VB4t>U`7V<&Q*+1_54j5!VL}=j z6hFl3aAg&lT#Lz-QWE0~Nh)oAGl}+i|M7jizpuyp{dphX$K(6_!0@&h9fYnS6p8@g z0PWauC;*t50TvcekV+E0LiPWy>tVBv6@^>a$niya0}(f5@x(#<0Pze z2OG|vij9|8$XPxjDVL5s>>GjPwRUggIduW&FXsbW9bj_-|2x+fpxq3kDIDazY%w7Uc6>+IsT`Gx z%^Hq(z3E&+YXlqKSN6Sh?A-e7`B7~Io8`rz^j4JIEJR;?qn`lovk5AAcC2(PHITmr zwpIMq(dN!+MY&b~3Flw#=CtOeOt#%nav#Ibly)X~go}&eQ^c?4j#tRIof4c3WDSMP zyCalMbLsAtL1GcGY?}2ewB_rB-E)aKuM=_mk~8nvro^1KAr)98Xz%9^Ik{Z8%v*w7 z6t;<%?NJjroZxgJ(cCk85^{LCDoHxC5!iW;@0r2Rr{@#SPWmehvN}Ba-L#&dGR~^+ z_%U7i^-nT?*yjXiCHV>t_b2OmP4X-`%dO7#o@cdpYE^Ye@b2!}A6s;UonIk%b>CgW zH3h|=3?&H$Vxyf9bi(`eV{twL$Aw!|Oe(dQf#F?Fr7MJVqKSvdj`vRD<8FzYv^l3+ zu+Dde1qe2!E0QH2%g<*96+)I+Xl*O;mD)fpB~R>g69^~5FA1$#Kw-?JG><< zK>tqT1i?$AS+@!^XQsJ(yrpW``d#a_fg9|AO=7q~oCl4iFuX?#Av(48-z z>&ne8bG~kFXr3)~Wh#Ha;V#w66_PFbR&<$5>Nsu}-Z+n#OQbj2Mpi zkzSBLX07qMg22ACqG=p_uk7hHh3A8bMcp0>L+X!vOQp}TzmE@pn6I8TwKzQ^~M$NXS z){LQyBf6BxUN;M`LKYrL`cu8KZqe6upW@Z~*!$tAt6nz^T@D0AdNrk1KThprIWkqn zY8N~^C08QwSG(B_kVLnvc@Z+c{`1%2A)>(=R1hcxa)T+qY{){^=vh&zG$sS^^!iGY zLm?+XKo3v!TSto%NFo!hX~BRe0M~_ov2PAie_i_Kt)JuyGH5KiFUSBybQ57oL@FB^ zl?hUX6-8)U7x6^8Py53X(AF+bZ`+9V_~4LKyFrvXmC$NFKQ~#chRJ#xL*3YxJKEnD zV_at?|B2G%5PCKa(KJed4fY$|rx>WbxZ4(4S=f4lFw}3bBbHH3$oCN2`Wa>Xb<4o* zVb#K|1yNFea#Dg+gMkP!bH40fvAIjq{xS_UA0!=%p~D{KsC21|{LRvEPWF;u431Qu z=(VTIZVTDZqcKs-$+w2}N3NGNW?p`VcPAVBfloIz3g;%QEA)#4v-@a| zs~#k%itkMnoDCFitRtPDI?PR(>8mZw(mtuO?;oF7RiWrvsJ>2Bk>{Y+b=ZtevVB{o zOSnmOQ_>qB-SpoMwf<@N{?&an*SwE&Fjm@#C2?tY*;o zY*^TRjt+jylfEjg(~0$@->dcHgL8w^Vwe8eddnT>;oalIB~H)9F6qt;($|I(ChPlOhXMVD7QjjkPfbZjeA zyUne1`lk0zbZX^*p#4YUlXYGU<%-Hle^dqI;~&p zH^;~2oZ^`&S*eEyD{bt)BSQ1|dweT3@m|dMuRI*unG5EkX`ymrMKe4inqNkqua&AT z?3`0OsEvK6@X@kRR?MVO&62TDNNe<}u+Xa*5*Y%=WU{y%AS85IQ+@|yKmg%Q-oN54 zPmn?-JFwz`5CCb8T#OF~ON$ls;j~hT1aeqBb|lCF6zWtsPoY6a7LyAyp&J$6T!G$X zHpBqTGWy7_3bblnggcc2wF)REAOS1)II?(9F6xW^IZKvZ*@o38l@tSVRxeaEwlugS znMKu)<(0F#v>@1BC<@PXKcjSwej(Viln*P|(z$A*jk{ z2#v&kfv_JExok3(4zgj2ujh(~)&b|R7@!FNr&3IomVzL$SX@9c@Zy3IzSKh?)cxXL T4w=ne90!F#qhN-HPDH~0BM8Pi diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json index 804359b240..cb612d2e7d 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Map-Pin-32 1.pdf", + "filename" : "Location-32.pdf", "idiom" : "universal" } ], diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Location-32.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Location-32.pdf new file mode 100644 index 0000000000000000000000000000000000000000..25b89dda779c25b09f31d0062a0dd1de4461d887 GIT binary patch literal 1570 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~f^q7U)9X}Cd0d}=6*YBJlC}2t zXY4fm!0CRMsWXpr>yhu9=dAK7YVf@NK<@tB9~0l^*8h0<__{j1@#i?2TmI)7cy_~~<6O>M1Z$-S$`f9z>AEYY?+si`V* zb)K~Vd(Cp6Y^n9i?RRF0#NJ4|vr)|`c%R1P_+S7iD~%HuXQ9yhJqBKxXnv`^YuN%rstwMoaQ^w;l3*w~94eEox!K z!p_@|t^eCxJ# z;k9>8)JKoAUD?aO-gt6FX3KFsrpl$APpek7uym^JST-R`Xt4;Vl4kmT^|hTfi741U3DT1LoLI8)l9u$H-uEpFbJBJ`v^vno7E_k=n;+;Z$a>$I&Cqg$bsc$TIes^v%%N36KUK@F@YBDcLH~qTLvE}2P z#jkjuRtmBSCUNZzdtwxEqOEAI*X9*WD=$p-T77h{YROuYYZvb&bw)7F4gB;$u(M5Q z!=^=AT%YX>joQpF1~U2iJHMQFAy>sYH~jwYLs6Vj7xizbPOyub>Z~cHeNW75oo%78 zZAPzPr@~&hr&r^vCzmCz%bmZvb@JqNYxxX2_SdezSFl_N%$&UVb;$3&+?+g(wG+;8 zRo8UfNINjI%IOCm|CK!l{@u^uzI93O!>OlVdHMUd?M+@5JHzVFYU;kd zm0v|4GdW}!E;(bdd*Pi&^VZMs&5+W`JeS<9Kjo$0v|8>rpI&d-ees8?{izkP-pfng zxYwP~((*6LKG`2SPf+XHr1;li-p6LY@UHVcwrOFPdtEQ*TF-;i{_r@a$Iq$=PJOlK z%JqcjroCax6=hn_&5xZ}|Fq1q|Iqa!>BuRuM?YWuy7kVU86FdIcLtWG@!X#tee&p} z`^hTDii*W%l`>aNlP=jBw6J;7_NUhrry0(=`}Cn;pF*wUX8-)tx~qQ2RUZw{F5&&5 z`|kd(83LBii)KA|^^<3tLerTg`Np^8zHgVDt08v%$DUZpvo9=IA5?emS~%rMN5Ji_ z6Nh%|Jt|s0Phj7N0^h4F*Wa842UEdS^TmOeW%;ICzdbakv)b)Hcng_HIRP4Y60}_jXVhW~E zA*8|yBm^(C^u04vfKF8a`5;IEW{-1zDNwH=hWm>lg%m6>%uq}Md9WC6A;@76k2@!p zBp}^2U0nAd!g9{l0-3bD4AwvTrbXA51 zCcuC|Rb^xd^fj80g(Y05q$n{nC$)$R6nma7z~Im*&d*KNRM5yw(S!tweo%gXi2}&O g-~iSS&a6rWx*l9&Cl-}}y<=!>V!@@V>gw+X0104XMgRZ+ literal 0 HcmV?d00001 diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Map-Pin-32 1.pdf b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/geolocationTip.imageset/Map-Pin-32 1.pdf deleted file mode 100644 index 1b5222ff723acf3e86fe40242b2fad0628eb9c3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2190 zcmZXWc~nw)8^>G31vl(f(q4=km0~s`FqoEO4Jw*zt~qHW!8J4j14^YVcO5b%>riHy z<~rh@DU@q2>A07aX*JqdSsu-$G{{tCtYQ^$}38C;MJU`PxK7qkJXEE*__kQv4aWFFzt0le(E zyG)X0;BeU>|7YX@IuR<1=B^95ltIHW9TsZ4tx5R^cyADk_O#K z_1*mD_XV7R;`ZM>z}KJWkc*$jXNU8UwKZqvW?p`tADf+@zHjTE-c8s|eqL1dp(xia zsQV;gugnG<7VKGXsGLhyy{MvJYRk)oI7FerYf4TY zgIZBZt+KyUVZz*HDbe*F6zId9C36GsiW#ubyX6mZs@5qcI9#1OSvAO`VuM|I@p;eQ zdb4dFVTY~!V|8ghzc%quC2yQkwn?6Zu{ZNF-JOLP!oazIoHZ!XM95$)MyR9rYVs#nudF7@oI zcyWA7^d%x+$d}@Le$|hz+$r*C4Pp3b8}7Gu{%?OHel(z@#XD=_F7K1YD&FVw%Ey>s59aqV|)-4Ob~h zP3)o5Tet5z1z+utDX8w%D9qRnH?g}^I@N+ch@L%aXsfxiKErgvBtc`y^stAcs zIoGFOEwWZQmbxqB*}HLelrqWb%>=0KP=0k&U8C`D+m+Yt4>jcF9v>Ji?Zk41)2h@I z{W{|LHZrFyd=r1_sRG30lv2emjYTBDmxN50D;g%p9b&qwZFsjDm+PX9 zw?kFyD~6r&PkUXJq<9$U(|SqwD`%s_G~(-O2z}yMm7o`RX>*s|ofreR5h%d`2Xzr# z-dV_AaAzJcBtFjfNlQ89uP&t-CuQg5T*)MpvO>nIf$PVs72r=r^^c7lrVLmEDMsZPBqhqxVW9-iuqN**i}73~dVu_4~Y5 zJ9A^?^&zcrRab<@qhv&g%OB**x8dI8>S=PEilApRS4UySWe|r+%ncxAdY$ZpLhKuqb{sn#5G+j;uUV&& zu6sFx*lm3ymzC%+#uoRA7S1Zu0{0UIH4G{K|X z_(l7mO~?5h+YrWfwGD^i$Ak%ym#&PaSx2?@o%f(L$080y!-dEO&hx3csQFtjTSrIg z`9nz0p>F)cvs2A=h}a=|tgUe|{%pC~>~SX(+AW8QO?F$Ue?(>ytM~Q{BMx?W*^El( z8|!24SHfYhP7DvJOZGSYbhD^ubC#miKTc^(>S<1f6HSso2{tNgdUyV^K1}cQy5sq5 z6W>@LUqF9Q4n`h^Dmj~G`ho_t&bst#Ov z*{>3URgKWgBIN{L-ycj2?@8L@cTU{sl%U**nt3dNbol$&Rs4sxR$JjcLn3|X_**6D|Tb90g%bDCk4(kM)3UUBB z<>Yvh6U&;#F0B9(;}}SjwF<~5a0iy_ShG&ZVzK|~ zpYsKX%iA#fE|Yvf&PqXfV_ychrf?|?){*Z+L)l;e)D+kO{W8A%378QGcmg1kzZ%vQ zSn>eoiiDT-DYq2~YlgS@mt-be#P_GnWr_b!vM~J?$YoOk86X=f|9X3EWa~h4SjRvk u03Ap(`mz*^JB!5yJh17+PW-3};h?$S6|3o~=*)~&X#r2he6{fv$P diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift index 78f31bec1e..3980694a2d 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift @@ -45,19 +45,21 @@ extension SiteTroubleshootingView { private let pixelKit: PixelFiring? private var cancellables = Set() - public init(connectionStatusPublisher: AnyPublisher, - activeSitePublisher: AnyPublisher, + public init(connectionStatusPublisher: CurrentValuePublisher, + activeSitePublisher: CurrentValuePublisher, uiActionHandler: VPNUIActionHandling, pixelKit: PixelFiring? = PixelKit.shared) { + connectionStatus = connectionStatusPublisher.value self.uiActionHandler = uiActionHandler self.pixelKit = pixelKit + internalSiteInfo = activeSitePublisher.value subscribeToConnectionStatusChanges(connectionStatusPublisher) subscribeToActiveSiteInfoChanges(activeSitePublisher) } - private func subscribeToConnectionStatusChanges(_ publisher: AnyPublisher) { + private func subscribeToConnectionStatusChanges(_ publisher: CurrentValuePublisher) { publisher .receive(on: DispatchQueue.main) @@ -65,7 +67,7 @@ extension SiteTroubleshootingView { .store(in: &cancellables) } - private func subscribeToActiveSiteInfoChanges(_ publisher: AnyPublisher) { + private func subscribeToActiveSiteInfoChanges(_ publisher: CurrentValuePublisher) { publisher .receive(on: DispatchQueue.main) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index a857760d58..4a795ef1b1 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -29,7 +29,8 @@ extension VPNAutoconnectTip: Tip { case enable = "com.duckduckgo.vpn.tip.autoconnect.action.enable" } - static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.autoconnect.geolocationTipDismissedEvent") + @Parameter + static var geolocationTipDismissed: Bool = false @Parameter(.transient) static var vpnEnabled: Bool = false @@ -66,8 +67,8 @@ extension VPNAutoconnectTip: Tip { #Rule(Self.$vpnEnabledWhenDomainExclusionsAlreadyDismissed) { $0 } - #Rule(Self.geolocationTipDismissedEvent) { - $0.donations.count > 0 + #Rule(Self.$geolocationTipDismissed) { + $0 } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index d63ee6dc9f..2623d7f63a 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -26,6 +26,8 @@ import TipKit @MainActor public final class VPNTipsModel: ObservableObject { + static let imageSize = CGSize(width: 32, height: 32) + @Published private(set) var activeSiteInfo: ActiveSiteInfo? { didSet { @@ -141,7 +143,7 @@ public final class VPNTipsModel: ObservableObject { for await status in tip.statusUpdates { if case .invalidated = status { VPNDomainExclusionsTip.geolocationTipDismissed = true - await VPNAutoconnectTip.geolocationTipDismissedEvent.donate() + VPNAutoconnectTip.geolocationTipDismissed = true } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 5691abbb3f..c2cad0a594 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -59,6 +59,7 @@ public struct TunnelControllerView: View { tipsModel.canShowTips { TipView(tipsModel.autoconnectTip, action: tipsModel.autoconnectTipActionHandler) + .tipImageSize(VPNTipsModel.imageSize) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -70,6 +71,7 @@ public struct TunnelControllerView: View { tipsModel.canShowTips { TipView(tipsModel.domainExclusionsTip) + .tipImageSize(VPNTipsModel.imageSize) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -203,6 +205,7 @@ public struct TunnelControllerView: View { tipsModel.canShowTips { TipView(tipsModel.geoswitchingTip) + .tipImageSize(VPNTipsModel.imageSize) .padding(.horizontal, 9) .padding(.vertical, 6) } From 3554610dd0087ff3bc7206c128a3ed8e51e6a9ee Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 22 Nov 2024 12:46:26 +0100 Subject: [PATCH 29/34] Darkens the tip background --- .../TipBackground.colorset/Contents.json | 38 +++++++++++++++++++ .../TunnelControllerView.swift | 3 ++ 2 files changed, 41 insertions(+) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json new file mode 100644 index 0000000000..815797aa51 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.030", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.030", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index c2cad0a594..537e08b807 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -60,6 +60,7 @@ public struct TunnelControllerView: View { TipView(tipsModel.autoconnectTip, action: tipsModel.autoconnectTipActionHandler) .tipImageSize(VPNTipsModel.imageSize) + .tipBackground(Color(.onboardingStepBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -72,6 +73,7 @@ public struct TunnelControllerView: View { TipView(tipsModel.domainExclusionsTip) .tipImageSize(VPNTipsModel.imageSize) + .tipBackground(Color(.onboardingStepBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -206,6 +208,7 @@ public struct TunnelControllerView: View { TipView(tipsModel.geoswitchingTip) .tipImageSize(VPNTipsModel.imageSize) + .tipBackground(Color(.onboardingStepBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) } From 6d99f53fbdf1ae641920632c928aafbb7ed91e97 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 25 Nov 2024 10:33:59 +0100 Subject: [PATCH 30/34] Updates the VPN tips background color --- .../TipBackground 1.colorset/Contents.json | 38 +++++++++++++++++++ .../TunnelControllerView.swift | 6 +-- 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json new file mode 100644 index 0000000000..4694158f85 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.100", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.200", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 537e08b807..56e412b643 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -60,7 +60,7 @@ public struct TunnelControllerView: View { TipView(tipsModel.autoconnectTip, action: tipsModel.autoconnectTipActionHandler) .tipImageSize(VPNTipsModel.imageSize) - .tipBackground(Color(.onboardingStepBackground)) + .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -73,7 +73,7 @@ public struct TunnelControllerView: View { TipView(tipsModel.domainExclusionsTip) .tipImageSize(VPNTipsModel.imageSize) - .tipBackground(Color(.onboardingStepBackground)) + .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) } @@ -208,7 +208,7 @@ public struct TunnelControllerView: View { TipView(tipsModel.geoswitchingTip) .tipImageSize(VPNTipsModel.imageSize) - .tipBackground(Color(.onboardingStepBackground)) + .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) } From 36d251c9c43d536d04042bb457669da727c0e4e8 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Mon, 25 Nov 2024 13:31:33 +0100 Subject: [PATCH 31/34] Updates the tip background color --- .../TipBackground 1.colorset/Contents.json | 38 ------------------- .../TipBackground.colorset/Contents.json | 16 ++++---- 2 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json deleted file mode 100644 index 4694158f85..0000000000 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground 1.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.100", - "blue" : "0x00", - "green" : "0x00", - "red" : "0x00" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.200", - "blue" : "0x00", - "green" : "0x00", - "red" : "0x00" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json index 815797aa51..48eecc1685 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/TipBackground.colorset/Contents.json @@ -4,10 +4,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.030", - "blue" : "0x00", - "green" : "0x00", - "red" : "0x00" + "alpha" : "0.400", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" } }, "idiom" : "universal" @@ -22,10 +22,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.030", - "blue" : "0xFF", - "green" : "0xFF", - "red" : "0xFF" + "alpha" : "0.200", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" } }, "idiom" : "universal" From 5a77984d72ff8b3e659f455857b9dd96c5ebe57e Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 26 Nov 2024 15:43:38 +0100 Subject: [PATCH 32/34] Fixes the conditions for showing our VPN tips --- .../TipViews/Model/VPNAutoconnectTip.swift | 16 ++--- .../Model/VPNDomainExclusionsTip.swift | 16 ++--- .../TipViews/Model/VPNGeoswitchingTip.swift | 8 ++- .../Views/TipViews/Model/VPNTipsModel.swift | 69 ++++++++++++------- .../TunnelControllerView.swift | 32 ++++++++- 5 files changed, 93 insertions(+), 48 deletions(-) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift index 4a795ef1b1..676fe645aa 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNAutoconnectTip.swift @@ -29,14 +29,17 @@ extension VPNAutoconnectTip: Tip { case enable = "com.duckduckgo.vpn.tip.autoconnect.action.enable" } - @Parameter - static var geolocationTipDismissed: Bool = false - @Parameter(.transient) static var vpnEnabled: Bool = false + /// This condition tries to verify that this tip is distanced from the previous tip.. + /// + /// The conditions that will trigger this are: + /// - The status view was opened when previous tip's status is invalidated. + /// - The VPN is enabled when previous tip's status is invalidated. + /// @Parameter - static var vpnEnabledWhenDomainExclusionsAlreadyDismissed: Bool = false + static var isDistancedFromPreviousTip: Bool = false var id: String { "com.duckduckgo.vpn.tip.autoconnect" @@ -64,10 +67,7 @@ extension VPNAutoconnectTip: Tip { #Rule(Self.$vpnEnabled) { $0 == true } - #Rule(Self.$vpnEnabledWhenDomainExclusionsAlreadyDismissed) { - $0 - } - #Rule(Self.$geolocationTipDismissed) { + #Rule(Self.$isDistancedFromPreviousTip) { $0 } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift index 0f3fae0d70..f166e82375 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNDomainExclusionsTip.swift @@ -31,15 +31,14 @@ extension VPNDomainExclusionsTip: Tip { @Parameter(.transient) static var hasActiveSite: Bool = false - /// This flag attempt to capture the user's intent to disable the VPN, which is the right time to show this tip. + /// This condition tries to verify that this tip is distanced from the previous tip and doesn't show right after. /// - /// If the VPN is ON when the status view is opened, there's a reasonable expectation the user might want to disable the VPN. + /// The conditions that will trigger this are: + /// - The status view was opened when previous tip's status is invalidated. + /// - The VPN is enabled when previous tip's status is invalidated. /// @Parameter - static var statusViewOpenedWhenVPNIsOn: Bool = false - - @Parameter - static var geolocationTipDismissed: Bool = false + static var isDistancedFromPreviousTip: Bool = false var id: String { "com.duckduckgo.vpn.tip.domainExclusions" @@ -64,10 +63,7 @@ extension VPNDomainExclusionsTip: Tip { #Rule(Self.$vpnEnabled) { $0 } - #Rule(Self.$statusViewOpenedWhenVPNIsOn) { - $0 - } - #Rule(Self.$geolocationTipDismissed) { + #Rule(Self.$isDistancedFromPreviousTip) { $0 } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift index 39eaab6b3a..3b5a9e5173 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNGeoswitchingTip.swift @@ -25,8 +25,12 @@ struct VPNGeoswitchingTip {} @available(macOS 14.0, *) extension VPNGeoswitchingTip: Tip { + /// Where the VPN was ever enabled. + /// + /// Once set this is never unset. The tip doesn't need to be hidden when the user is disconnected. + /// @Parameter - static var vpnEnabledAtLeastOnce: Bool = false + static var vpnEnabledOnce: Bool = false var id: String { "com.duckduckgo.vpn.tip.geoswitching" @@ -45,7 +49,7 @@ extension VPNGeoswitchingTip: Tip { } var rules: [Rule] { - #Rule(Self.$vpnEnabledAtLeastOnce) { + #Rule(Self.$vpnEnabledOnce) { $0 } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 2623d7f63a..46a8bc1152 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -72,6 +72,10 @@ public final class VPNTipsModel: ObservableObject { self.logger = logger self.vpnSettings = vpnSettings + guard !isMenuApp else { + return + } + if #available(macOS 14.0, *) { handleActiveSiteInfoChanged(newValue: activeSiteInfo) handleConnectionStatusChanged(oldValue: connectionStatus, newValue: connectionStatus) @@ -79,11 +83,14 @@ public final class VPNTipsModel: ObservableObject { subscribeToConnectionStatusChanges(statusObserver) subscribeToFeatureFlagChanges(featureFlagPublisher) subscribeToActiveSiteChanges(activeSitePublisher) - - subscribeToStatusChanges(for: geoswitchingTip) } } + deinit { + geoswitchingStatusUpdateTask?.cancel() + geoswitchingStatusUpdateTask = nil + } + var canShowTips: Bool { !isMenuApp && featureFlag } @@ -124,6 +131,8 @@ public final class VPNTipsModel: ObservableObject { let domainExclusionsTip = VPNDomainExclusionsTip() let geoswitchingTip = VPNGeoswitchingTip() + var geoswitchingStatusUpdateTask: Task? + // MARK: - Tip Action handling @available(macOS 14.0, *) @@ -135,40 +144,24 @@ public final class VPNTipsModel: ObservableObject { } } - // MARK: - Subscriptions: Tips - - @available(macOS 14.0, *) - func subscribeToStatusChanges(for tip: VPNGeoswitchingTip) { - Task { - for await status in tip.statusUpdates { - if case .invalidated = status { - VPNDomainExclusionsTip.geolocationTipDismissed = true - VPNAutoconnectTip.geolocationTipDismissed = true - } - } - } - } - // MARK: - Handle Refreshing @available(macOS 14.0, *) private func handleActiveSiteInfoChanged(newValue: ActiveSiteInfo?) { - Logger.networkProtection.debug("🧉 Active site info changed: \(String(describing: newValue))") + guard !isMenuApp else { return } return VPNDomainExclusionsTip.hasActiveSite = (activeSiteInfo != nil) } @available(macOS 14.0, *) private func handleConnectionStatusChanged(oldValue: ConnectionStatus, newValue: ConnectionStatus) { + guard !isMenuApp else { return } switch newValue { case .connected: if case oldValue = .connecting { - VPNGeoswitchingTip.vpnEnabledAtLeastOnce = true - - if case .invalidated = domainExclusionsTip.status { - VPNAutoconnectTip.vpnEnabledWhenDomainExclusionsAlreadyDismissed = true - } + handleTipDistanceConditionsCheckpoint() } + VPNGeoswitchingTip.vpnEnabledOnce = true VPNAutoconnectTip.vpnEnabled = true VPNDomainExclusionsTip.vpnEnabled = true default: @@ -177,22 +170,46 @@ public final class VPNTipsModel: ObservableObject { } } + @available(macOS 14.0, *) + private func handleTipDistanceConditionsCheckpoint() { + if case .invalidated = geoswitchingTip.status { + VPNDomainExclusionsTip.isDistancedFromPreviousTip = true + } + + if case .invalidated = domainExclusionsTip.status { + VPNAutoconnectTip.isDistancedFromPreviousTip = true + } + } + // MARK: - UI Events + @available(macOS 14.0, *) + func handleGeoswitchingTipInvalidated(_ reason: Tip.InvalidationReason) { + switch reason { + case .actionPerformed: + Logger.networkProtection.log("🧉 Geo-switching tip actioned") + break + default: + Logger.networkProtection.log("🧉 Geo-switching tip dismissed") + } + } + @available(macOS 14.0, *) func handleLocationsShown() { + guard !isMenuApp else { return } geoswitchingTip.invalidate(reason: .actionPerformed) } @available(macOS 14.0, *) func handleSiteExcluded() { - geoswitchingTip.invalidate(reason: .actionPerformed) + guard !isMenuApp else { return } + domainExclusionsTip.invalidate(reason: .actionPerformed) } @available(macOS 14.0, *) func handleTunnelControllerShown() { - if case .connected = connectionStatus { - VPNDomainExclusionsTip.statusViewOpenedWhenVPNIsOn = true - } + guard !isMenuApp else { return } + + handleTipDistanceConditionsCheckpoint() } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 56e412b643..7754adab40 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -56,7 +56,8 @@ public struct TunnelControllerView: View { featureToggleRow() if #available(macOS 14.0, *), - tipsModel.canShowTips { + tipsModel.canShowTips, + case .invalidated = tipsModel.domainExclusionsTip.status { TipView(tipsModel.autoconnectTip, action: tipsModel.autoconnectTipActionHandler) .tipImageSize(VPNTipsModel.imageSize) @@ -69,13 +70,27 @@ public struct TunnelControllerView: View { .padding(.top, 5) if #available(macOS 14.0, *), - tipsModel.canShowTips { + tipsModel.canShowTips, + case .invalidated = tipsModel.geoswitchingTip.status { TipView(tipsModel.domainExclusionsTip) .tipImageSize(VPNTipsModel.imageSize) .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) + .task { + var previousStatus = tipsModel.domainExclusionsTip.status + + for await status in tipsModel.domainExclusionsTip.statusUpdates { + if case .invalidated = status { + if case .available = previousStatus { + Logger.networkProtection.log("🧉 Domain exclusions tip dismissed") + } + + previousStatus = status + } + } + } } Divider() @@ -211,6 +226,19 @@ public struct TunnelControllerView: View { .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) + .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 + } + } } dividerRow() From 5cd2212ea3690e504262f58c5c3b29933cb3feed Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 26 Nov 2024 17:08:34 +0100 Subject: [PATCH 33/34] Implements pixels for our VPN tips --- .../Views/TipViews/Model/VPNTipsModel.swift | 67 +++++++++++++++++-- .../TunnelControllerView.swift | 37 ++++++++-- .../Sources/VPNPixels/VPNTipPixel.swift | 52 ++++++++++++++ 3 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNTipPixel.swift diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift index 46a8bc1152..fd7a2518dc 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TipViews/Model/VPNTipsModel.swift @@ -22,6 +22,8 @@ import Common import NetworkProtection import os.log import TipKit +import PixelKit +import VPNPixels @MainActor public final class VPNTipsModel: ObservableObject { @@ -183,14 +185,33 @@ public final class VPNTipsModel: ObservableObject { // MARK: - UI Events + @available(macOS 14.0, *) + func handleAutoconnectTipInvalidated(_ reason: Tip.InvalidationReason) { + switch reason { + case .actionPerformed: + PixelKit.fire(VPNTipPixel.autoconnectTip(step: .actioned)) + default: + PixelKit.fire(VPNTipPixel.autoconnectTip(step: .dismissed)) + } + } + + @available(macOS 14.0, *) + func handleDomainExclusionTipInvalidated(_ reason: Tip.InvalidationReason) { + switch reason { + case .actionPerformed: + PixelKit.fire(VPNTipPixel.domainExclusionsTip(step: .actioned)) + default: + PixelKit.fire(VPNTipPixel.domainExclusionsTip(step: .dismissed)) + } + } + @available(macOS 14.0, *) func handleGeoswitchingTipInvalidated(_ reason: Tip.InvalidationReason) { switch reason { case .actionPerformed: - Logger.networkProtection.log("🧉 Geo-switching tip actioned") - break + PixelKit.fire(VPNTipPixel.geoswitchingTip(step: .actioned)) default: - Logger.networkProtection.log("🧉 Geo-switching tip dismissed") + PixelKit.fire(VPNTipPixel.geoswitchingTip(step: .dismissed)) } } @@ -207,9 +228,47 @@ public final class VPNTipsModel: ObservableObject { } @available(macOS 14.0, *) - func handleTunnelControllerShown() { + func handleTunnelControllerAppear() { guard !isMenuApp else { return } handleTipDistanceConditionsCheckpoint() } + + @available(macOS 14.0, *) + func handleTunnelControllerDisappear() { + guard !isMenuApp else { return } + + if case .available = autoconnectTip.status { + PixelKit.fire(VPNTipPixel.autoconnectTip(step: .ignored)) + } + + if case .available = domainExclusionsTip.status { + PixelKit.fire(VPNTipPixel.domainExclusionsTip(step: .ignored)) + } + + if case .available = geoswitchingTip.status { + PixelKit.fire(VPNTipPixel.geoswitchingTip(step: .ignored)) + } + } + + @available(macOS 14.0, *) + func handleAutoconnectionTipShown() { + guard !isMenuApp else { return } + + PixelKit.fire(VPNTipPixel.autoconnectTip(step: .shown)) + } + + @available(macOS 14.0, *) + func handleDomainExclusionsTipShown() { + guard !isMenuApp else { return } + + PixelKit.fire(VPNTipPixel.domainExclusionsTip(step: .shown)) + } + + @available(macOS 14.0, *) + func handleGeoswitchingTipShown() { + guard !isMenuApp else { return } + + PixelKit.fire(VPNTipPixel.geoswitchingTip(step: .shown)) + } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift index 7754adab40..d786816b5c 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/TunnelControllerView/TunnelControllerView.swift @@ -64,6 +64,22 @@ public struct TunnelControllerView: View { .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) + .onAppear { + tipsModel.handleAutoconnectionTipShown() + } + .task { + var previousStatus = tipsModel.autoconnectTip.status + + for await status in tipsModel.autoconnectTip.statusUpdates { + if case .invalidated(let reason) = status { + if case .available = previousStatus { + tipsModel.handleAutoconnectTipInvalidated(reason) + } + } + + previousStatus = status + } + } } SiteTroubleshootingView() @@ -78,17 +94,20 @@ public struct TunnelControllerView: View { .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) + .onAppear { + tipsModel.handleDomainExclusionsTipShown() + } .task { var previousStatus = tipsModel.domainExclusionsTip.status for await status in tipsModel.domainExclusionsTip.statusUpdates { - if case .invalidated = status { + if case .invalidated(let reason) = status { if case .available = previousStatus { - Logger.networkProtection.log("🧉 Domain exclusions tip dismissed") + tipsModel.handleDomainExclusionTipInvalidated(reason) } - - previousStatus = status } + + previousStatus = status } } } @@ -105,7 +124,12 @@ public struct TunnelControllerView: View { } .onAppear { if #available(macOS 14.0, *) { - tipsModel.handleTunnelControllerShown() + tipsModel.handleTunnelControllerAppear() + } + } + .onDisappear { + if #available(macOS 14.0, *) { + tipsModel.handleTunnelControllerDisappear() } } } @@ -226,6 +250,9 @@ public struct TunnelControllerView: View { .tipBackground(Color(.tipBackground)) .padding(.horizontal, 9) .padding(.vertical, 6) + .onAppear { + tipsModel.handleGeoswitchingTipShown() + } .task { var previousStatus = tipsModel.geoswitchingTip.status diff --git a/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNTipPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNTipPixel.swift new file mode 100644 index 0000000000..99d314ec42 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNTipPixel.swift @@ -0,0 +1,52 @@ +// +// VPNTipPixel.swift +// +// Copyright © 2024 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 PixelKit + +public enum VPNTipStep: String { + case shown + case ignored + case actioned + case dismissed +} + +public enum VPNTipPixel: VPNPixel { + case autoconnectTip(step: VPNTipStep) + case domainExclusionsTip(step: VPNTipStep) + case geoswitchingTip(step: VPNTipStep) + + public var unscopedPixelName: String { + switch self { + case .autoconnectTip(let step): + return "tip_autoconnect_\(step)" + case .domainExclusionsTip(let step): + return "tip_site-exclusion_\(step)" + case .geoswitchingTip(let step): + return "tip_geoswitching_\(step)" + } + } + + public var error: (any Error)? { + nil + } + + public var parameters: [String: String]? { + nil + } +} From d1bb000957dff09f86a6bd1a5417a99d709f9d1e Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 28 Nov 2024 22:35:43 +0100 Subject: [PATCH 34/34] Updates some log messages that were off. --- DuckDuckGo/TipKit/TipKitAppEventHandling.swift | 2 +- DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index f5b14f3f01..da802fd49f 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -52,7 +52,7 @@ struct TipKitAppEventHandler: TipKitAppEventHandling { .datastoreLocation(.applicationDefault) ]) } else { - logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + logger.log("TipKit initialization skipped: macOS 14.0 or later is required.") } } } diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift index eb2279b608..2ff60e2e4c 100644 --- a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -40,7 +40,7 @@ struct TipKitDebugOptionsUIActionHandler: TipKitDebugOptionsUIActionHandling { if #available(macOS 14.0, *) { controller.resetTipKitOnNextAppLaunch() } else { - logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + logger.log("TipKit initialization skipped: macOS 14.0 or later is required.") } } }