From 4d26d2a0463bf12aaa74b5375578d483ccf7958c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 24 Sep 2024 12:21:19 +0200 Subject: [PATCH 01/26] Add VPN tips --- DuckDuckGo.xcodeproj/project.pbxproj | 20 ++++++++ DuckDuckGo/AppDelegate.swift | 10 ++++ DuckDuckGo/NetworkProtectionStatusView.swift | 22 +++++++++ DuckDuckGo/VPNAddWidgetTip.swift | 52 ++++++++++++++++++++ DuckDuckGo/VPNChangeLocationTip.swift | 46 +++++++++++++++++ DuckDuckGo/VPNUseSnoozeTip.swift | 51 +++++++++++++++++++ DuckDuckGo/ViewExtension.swift | 6 +++ 7 files changed, 207 insertions(+) create mode 100644 DuckDuckGo/VPNAddWidgetTip.swift create mode 100644 DuckDuckGo/VPNChangeLocationTip.swift create mode 100644 DuckDuckGo/VPNUseSnoozeTip.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 11934ff00d..9100d0ca15 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -359,6 +359,9 @@ 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; + 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; + 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; + 7BFD5FD92C9DBC24000FF959 /* VPNUseSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E872193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift */; }; @@ -1635,6 +1638,9 @@ 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutItemView.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; + 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; + 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; + 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUseSnoozeTip.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerBrowsingMenuExtension.swift; sourceTree = ""; }; @@ -4009,6 +4015,16 @@ name = AdAttribution; sourceTree = ""; }; + 7BFD5FD32C9DA235000FF959 /* TipKit */ = { + isa = PBXGroup; + children = ( + 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */, + 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */, + 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */, + ); + name = TipKit; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -5539,6 +5555,7 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7BFD5FD32C9DA235000FF959 /* TipKit */, 4BD96E072C4DCCD1003BC32C /* LiveActivity */, 4B37E04E2B928C91009E81CA /* Resources */, EE01EB412AFC1DE10096AAC9 /* PreferredLocation */, @@ -7325,6 +7342,7 @@ 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, + 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, @@ -7397,6 +7415,7 @@ 9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */, 310D091B2799F54900DC0060 /* DownloadManager.swift in Sources */, 98D98A7425ED88D100D8E3DF /* BrowsingMenuEntryViewCell.swift in Sources */, + 7BFD5FD92C9DBC24000FF959 /* VPNUseSnoozeTip.swift in Sources */, 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */, 4B2C79612C5B27AC00A240CC /* VPNSnoozeActivityAttributes.swift in Sources */, CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, @@ -7651,6 +7670,7 @@ 31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, + 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 0a5d2886ca..95d38f8276 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -38,6 +38,7 @@ import Subscription import NetworkProtection import WebKit import os.log +import TipKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -384,6 +385,15 @@ import os.log didCrashDuringCrashHandlersSetUp = false } + if #available(iOS 17.0, *) { + Task { + try Tips.configure([ + .displayFrequency(.immediate), + .datastoreLocation(.applicationDefault) + ]) + } + } + return true } diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index f8508a2482..43e4c7c41b 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -19,12 +19,21 @@ import SwiftUI import NetworkProtection +import TipKit struct NetworkProtectionStatusView: View { @Environment(\.colorScheme) var colorScheme @StateObject public var statusModel: NetworkProtectionStatusViewModel + // MARK: - Tips + + private let geoswitchingTip = VPNGeoswitchingTip() + private let snoozeTip = VPNSnoozeTip() + private let widgetTip = VPNWidgetTip() + + // MARK: - View + var body: some View { List { if let errorItem = statusModel.error { @@ -35,8 +44,21 @@ struct NetworkProtectionStatusView: View { } toggle() + + if #available(iOS 17.0, *) { + //TipView(widgetTip) + //.removeGroupedListStyleInsets() + TipView(snoozeTip) + .removeGroupedListStyleInsets() + } + locationDetails() + if #available(iOS 17.0, *) { + TipView(geoswitchingTip) + .removeGroupedListStyleInsets() + } + if statusModel.isNetPEnabled && statusModel.hasServerInfo && !statusModel.isSnoozing { connectionDetails() } diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift new file mode 100644 index 0000000000..a9ae150c7b --- /dev/null +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -0,0 +1,52 @@ +// +// 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(iOS 17.0, *) +extension VPNAddWidgetTip: Tip { + + 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(title: "Add widget") { + //WidgetEducationView() + }] + } +} diff --git a/DuckDuckGo/VPNChangeLocationTip.swift b/DuckDuckGo/VPNChangeLocationTip.swift new file mode 100644 index 0000000000..6b167b5075 --- /dev/null +++ b/DuckDuckGo/VPNChangeLocationTip.swift @@ -0,0 +1,46 @@ +// +// 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 {} + +/// Necessary split to support older iOS versions. +/// +@available(iOS 17.0, *) +extension VPNChangeLocationTip: Tip { + + 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") + } +} diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNUseSnoozeTip.swift new file mode 100644 index 0000000000..ef820b75d2 --- /dev/null +++ b/DuckDuckGo/VPNUseSnoozeTip.swift @@ -0,0 +1,51 @@ +// +// 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(iOS 17.0, *) +extension VPNUseSnoozeTip: Tip { + 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(title: "Learn more") { + //WidgetEducationView() + }] + } +} diff --git a/DuckDuckGo/ViewExtension.swift b/DuckDuckGo/ViewExtension.swift index ca1afc56cc..a0894870ee 100644 --- a/DuckDuckGo/ViewExtension.swift +++ b/DuckDuckGo/ViewExtension.swift @@ -77,6 +77,12 @@ extension View { .applyBackground() } + /// Removes the grouped list style insets for a single row. + /// + func removeGroupedListStyleInsets() -> some View { + listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } + @ViewBuilder func applyBackground() -> some View { hideScrollContentBackground() From b1c4384d696bf525b08d50e153f52c86cb848e49 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 24 Sep 2024 12:34:12 +0200 Subject: [PATCH 02/26] Add VPN tips --- DuckDuckGo.xcodeproj/project.pbxproj | 24 ++++++++++++ DuckDuckGo/AppDelegate.swift | 19 ++++------ .../AppIntegration/VPNAppEventsHandler.swift | 37 +++++++++++++++++++ DuckDuckGo/TipKit/TipKitAppEventHandler.swift | 35 ++++++++++++++++++ DuckDuckGo/VPNAddWidgetTip.swift | 2 +- 5 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift create mode 100644 DuckDuckGo/TipKit/TipKitAppEventHandler.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9100d0ca15..5f2aaaf926 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -359,6 +359,8 @@ 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; + 7BF78DFF2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */; }; + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandler.swift */; }; 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; 7BFD5FD92C9DBC24000FF959 /* VPNUseSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */; }; @@ -1638,6 +1640,8 @@ 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutItemView.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; + 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAppEventsHandler.swift; sourceTree = ""; }; + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandler.swift; sourceTree = ""; }; 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUseSnoozeTip.swift; sourceTree = ""; }; @@ -4015,6 +4019,22 @@ name = AdAttribution; sourceTree = ""; }; + 7BF78DFD2CA2CAD10026A1FC /* AppIntegration */ = { + isa = PBXGroup; + children = ( + 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */, + ); + path = AppIntegration; + sourceTree = ""; + }; + 7BF78E002CA2CC100026A1FC /* TipKit */ = { + isa = PBXGroup; + children = ( + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandler.swift */, + ); + path = TipKit; + sourceTree = ""; + }; 7BFD5FD32C9DA235000FF959 /* TipKit */ = { isa = PBXGroup; children = ( @@ -4253,6 +4273,7 @@ F13B4BF41F18C74500814661 /* Tabs */, F1386BA21E6846320062FC3C /* TabSwitcher */, 98F3A1D6217B36EE0011A0D4 /* Themes */, + 7BF78E002CA2CC100026A1FC /* TipKit */, F11CEF581EBB66C80088E4D7 /* Tutorials */, CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */, F1D796ED1E7AE4090019D451 /* UserInterface */, @@ -5555,6 +5576,7 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7BF78DFD2CA2CAD10026A1FC /* AppIntegration */, 7BFD5FD32C9DA235000FF959 /* TipKit */, 4BD96E072C4DCCD1003BC32C /* LiveActivity */, 4B37E04E2B928C91009E81CA /* Resources */, @@ -7473,6 +7495,7 @@ D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */, BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */, F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */, + 7BF78DFF2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift in Sources */, 6FE1273A2C204BD000EB5724 /* NewTabPageView.swift in Sources */, 986DA94A24884B18004A7E39 /* WebViewTransition.swift in Sources */, 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, @@ -7820,6 +7843,7 @@ F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */, D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandler.swift in Sources */, 1DEAADF62BA4809400E25A97 /* CookiePopUpProtectionView.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 95d38f8276..c2ace31658 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -57,7 +57,6 @@ import TipKit private lazy var privacyStore = PrivacyUserDefaults() private var bookmarksDatabase: CoreDataDatabase = BookmarksDatabase.make() - private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @MainActor @@ -90,6 +89,11 @@ import TipKit var privacyProDataReporter: PrivacyProDataReporting! + // MARK: - Feature specific app event handlers + + private let tipKitAppEventsHandler = TipKitAppEventHandler() + private let vpnAppEventsHandler = VPNAppEventsHandler() + // MARK: lifecycle @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) @@ -374,7 +378,7 @@ import TipKit NewTabPageIntroMessageSetup().perform() - widgetRefreshModel.beginObservingVPNStatus() + vpnAppEventsHandler.appDidFinishLaunching() AppDependencyProvider.shared.subscriptionManager.loadInitialData() @@ -385,14 +389,7 @@ import TipKit didCrashDuringCrashHandlersSetUp = false } - if #available(iOS 17.0, *) { - Task { - try Tips.configure([ - .displayFrequency(.immediate), - .datastoreLocation(.applicationDefault) - ]) - } - } + tipKitAppEventsHandler.appDidFinishLaunching() return true } @@ -544,7 +541,7 @@ import TipKit fireFailedCompilationsPixelIfNeeded() - widgetRefreshModel.refreshVPNWidget() + VPNAppEventsHandler().appDidBecomeActive() if tunnelDefaults.showEntitlementAlert { presentExpiredEntitlementAlert() diff --git a/DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift b/DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift new file mode 100644 index 0000000000..5928647f52 --- /dev/null +++ b/DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift @@ -0,0 +1,37 @@ +// +// VPNAppEventsHandler.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 + +struct VPNAppEventsHandler { + + private let widgetRefreshModel: NetworkProtectionWidgetRefreshModel + + init(widgetRefreshModel: NetworkProtectionWidgetRefreshModel = .init()) { + self.widgetRefreshModel = widgetRefreshModel + } + + func appDidFinishLaunching() { + widgetRefreshModel.beginObservingVPNStatus() + } + + func appDidBecomeActive() { + widgetRefreshModel.beginObservingVPNStatus() + } +} diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandler.swift b/DuckDuckGo/TipKit/TipKitAppEventHandler.swift new file mode 100644 index 0000000000..d43d84e248 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitAppEventHandler.swift @@ -0,0 +1,35 @@ +// +// TipKitAppEventHandler.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 TipKit + +struct TipKitAppEventHandler { + + func appDidFinishLaunching() { + if #available(iOS 17.0, *) { + Task { + try Tips.configure([ + .displayFrequency(.immediate), + .datastoreLocation(.applicationDefault) + ]) + } + } + } +} diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index a9ae150c7b..2fef897318 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -46,7 +46,7 @@ extension VPNAddWidgetTip: Tip { var actions: [Action] { [Action(title: "Add widget") { - //WidgetEducationView() + // WidgetEducationView() }] } } From d2ecf8f2168935c1db9beae1fcc41315dbe8b327 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Sun, 6 Oct 2024 11:34:32 +0200 Subject: [PATCH 03/26] WIP --- DuckDuckGo/NetworkProtectionStatusView.swift | 6 +++--- DuckDuckGo/VPNChangeLocationTip.swift | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 43e4c7c41b..c7db2f6a91 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -28,9 +28,9 @@ struct NetworkProtectionStatusView: View { // MARK: - Tips - private let geoswitchingTip = VPNGeoswitchingTip() - private let snoozeTip = VPNSnoozeTip() - private let widgetTip = VPNWidgetTip() + private let geoswitchingTip = VPNChangeLocationTip() + private let snoozeTip = VPNUseSnoozeTip() + private let widgetTip = VPNAddWidgetTip() // MARK: - View diff --git a/DuckDuckGo/VPNChangeLocationTip.swift b/DuckDuckGo/VPNChangeLocationTip.swift index 6b167b5075..baec29762c 100644 --- a/DuckDuckGo/VPNChangeLocationTip.swift +++ b/DuckDuckGo/VPNChangeLocationTip.swift @@ -28,6 +28,17 @@ struct VPNChangeLocationTip {} @available(iOS 17.0, *) extension VPNChangeLocationTip: Tip { + /// Whether the VPN is running. + /// + @Parameter + static var vpnIsRunning: Bool = false + + /// Whether the tip has been dismissed at least once by tapping on X to close it, or by going into + /// GeoSwitching while the tip was being shown. + /// + @Parameter + static var tipDismissedOnce: Bool = false + var id: String { "com.duckduckgo.tipkit.VPNChangeLocationTip" } @@ -43,4 +54,13 @@ extension VPNChangeLocationTip: Tip { var image: Image? { Image(systemName: "globe.americas.fill") } + + var rules: [Rule] { + [#Rule(Self.$vpnIsRunning) { + $0 == true + }, + #Rule(Self.$tipDismissedOnce) { + $0 == false + }] + } } From 1d52ac98ea016e9ce7713ddfdcb9583169fbab2a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 10 Oct 2024 16:16:45 +0200 Subject: [PATCH 04/26] Implemented the presentation logic for all vpn tips --- Core/Pixel.swift | 2 +- Core/UserDefaultsPropertyWrapper.swift | 4 +- DuckDuckGo.xcodeproj/project.pbxproj | 24 ++- DuckDuckGo/Debug.storyboard | 31 ++- DuckDuckGo/NetworkProtectionStatusView.swift | 81 +++++--- .../NetworkProtectionStatusViewModel.swift | 35 +++- DuckDuckGo/RootDebugViewController.swift | 23 ++- DuckDuckGo/TipKit/Logger+TipKit.swift | 28 +++ DuckDuckGo/TipKit/TipGroup.swift | 189 ++++++++++++++++++ ...ler.swift => TipKitAppEventHandling.swift} | 18 +- DuckDuckGo/TipKit/TipKitController.swift | 97 +++++++++ .../TipKitDebugOptionsUIActionHandling.swift | 49 +++++ DuckDuckGo/VPNAddWidgetTip.swift | 11 + DuckDuckGo/VPNChangeLocationTip.swift | 26 +-- DuckDuckGo/VPNUseSnoozeTip.swift | 13 +- 15 files changed, 543 insertions(+), 88 deletions(-) create mode 100644 DuckDuckGo/TipKit/Logger+TipKit.swift create mode 100644 DuckDuckGo/TipKit/TipGroup.swift rename DuckDuckGo/TipKit/{TipKitAppEventHandler.swift => TipKitAppEventHandling.swift} (65%) create mode 100644 DuckDuckGo/TipKit/TipKitController.swift create mode 100644 DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift diff --git a/Core/Pixel.swift b/Core/Pixel.swift index eb3770f776..827904b472 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -212,7 +212,7 @@ public class Pixel { onComplete(nil) } } - + private static func updatePixelLastFireDate(pixel: Pixel.Event) { storage.set(Date(), forKey: pixel.name) } diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 6df337d168..b57d2fdbd5 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -179,7 +179,9 @@ public struct UserDefaultsWrapper { case duckPlayerPixelExperimentLastDayPixelFired = "com.duckduckgo.ios.duckplayer.pixel.experiment.last.day.pixel.fired.v2" case duckPlayerPixelExperimentLastVideoIDRendered = "com.duckduckgo.ios.duckplayer.pixel.experiment.last.videoID.rendered.v2" case duckPlayerPixelExperimentOverride = "com.duckduckgo.ios.duckplayer.pixel.experiment.override.v2" - + + // TipKit + case resetTipKitOnNextLaunch = "com.duckduckgo.ios.tipKit.resetOnNextLaunch" } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5f2aaaf926..331a1f3279 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -357,10 +357,14 @@ 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */; }; 6FE1274B2C20943500EB5724 /* ShortcutItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; + 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */; }; + 7B1604EC2CB68BDA00A44EC6 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController.swift */; }; + 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; + 7BC97CC42CB7ECDE00FF521F /* TipGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */; }; 7BF78DFF2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */; }; - 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandler.swift */; }; + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; 7BFD5FD92C9DBC24000FF959 /* VPNUseSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */; }; @@ -1639,9 +1643,13 @@ 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageViewController.swift; sourceTree = ""; }; 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutItemView.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; + 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; + 7B1604EB2CB68BDA00A44EC6 /* TipKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitController.swift; sourceTree = ""; }; + 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; + 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipGroup.swift; sourceTree = ""; }; 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAppEventsHandler.swift; sourceTree = ""; }; - 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandler.swift; sourceTree = ""; }; + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUseSnoozeTip.swift; sourceTree = ""; }; @@ -4030,7 +4038,11 @@ 7BF78E002CA2CC100026A1FC /* TipKit */ = { isa = PBXGroup; children = ( - 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandler.swift */, + 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */, + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */, + 7B1604EB2CB68BDA00A44EC6 /* TipKitController.swift */, + 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */, + 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */, ); path = TipKit; sourceTree = ""; @@ -7361,6 +7373,7 @@ EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, + 7B1604EC2CB68BDA00A44EC6 /* TipKitController.swift in Sources */, 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, @@ -7538,6 +7551,7 @@ 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */, + 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, @@ -7550,6 +7564,7 @@ D63FF8982C1B6A45006DE24D /* DuckPlayer.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, C1935A102C88D131001AD72D /* AutofillSurveyManager.swift in Sources */, + 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, 1DDF40202BA049FA006850D9 /* SettingsRootView.swift in Sources */, @@ -7843,7 +7858,7 @@ F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */, D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, - 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandler.swift in Sources */, + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */, 1DEAADF62BA4809400E25A97 /* CookiePopUpProtectionView.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */, @@ -7873,6 +7888,7 @@ 85B9814E2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift in Sources */, 985AAE4524899369007A43EC /* HomeScreenTransition.swift in Sources */, 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, + 7BC97CC42CB7ECDE00FF521F /* TipGroup.swift in Sources */, 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 6FB1FEA22C256ACD0075B68B /* NewTabPageManager.swift in Sources */, diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 313b40911f..a0c1107184 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -1,9 +1,9 @@ - + - + @@ -383,9 +383,18 @@ - + + + + + + + + + + @@ -974,34 +983,34 @@ - + - + - + - + diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index c7db2f6a91..6b2a75544d 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -26,12 +26,6 @@ struct NetworkProtectionStatusView: View { @StateObject public var statusModel: NetworkProtectionStatusViewModel - // MARK: - Tips - - private let geoswitchingTip = VPNChangeLocationTip() - private let snoozeTip = VPNUseSnoozeTip() - private let widgetTip = VPNAddWidgetTip() - // MARK: - View var body: some View { @@ -45,20 +39,8 @@ struct NetworkProtectionStatusView: View { toggle() - if #available(iOS 17.0, *) { - //TipView(widgetTip) - //.removeGroupedListStyleInsets() - TipView(snoozeTip) - .removeGroupedListStyleInsets() - } - locationDetails() - if #available(iOS 17.0, *) { - TipView(geoswitchingTip) - .removeGroupedListStyleInsets() - } - if statusModel.isNetPEnabled && statusModel.hasServerInfo && !statusModel.isSnoozing { connectionDetails() } @@ -107,7 +89,29 @@ struct NetworkProtectionStatusView: View { } .padding([.top, .bottom], 2) + if #available(iOS 17.0, *), + !statusModel.isNetPEnabled { + + if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { + TipView(tip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + snooze() + + if #available(iOS 17.0, *), + statusModel.hasServerInfo { + + if let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { + TipView(tip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } } header: { header() } @@ -173,8 +177,8 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func locationDetails() -> some View { - if !statusModel.isSnoozing, let location = statusModel.location { - Section { + Section { + if !statusModel.isSnoozing, let location = statusModel.location { var locationAttributedString: AttributedString { var attributedString = AttributedString( statusModel.preferredLocation.isNearest ? "\(location) \(UserText.netPVPNLocationNearest)" : location @@ -186,16 +190,10 @@ struct NetworkProtectionStatusView: View { return attributedString } - NavigationLink(destination: NetworkProtectionVPNLocationView()) { + NavigationLink(destination: locationView()) { NetworkProtectionLocationItemView(title: locationAttributedString, imageName: nil) } - } header: { - Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) - .foregroundColor(.init(designSystemColor: .textSecondary)) - } - .listRowBackground(Color(designSystemColor: .surface)) - } else { - Section { + } else { let imageName = statusModel.preferredLocation.isNearest ? "VPNLocation" : nil var nearestLocationAttributedString: AttributedString { var attributedString = AttributedString(statusModel.preferredLocation.title) @@ -203,15 +201,32 @@ struct NetworkProtectionStatusView: View { return attributedString } - NavigationLink(destination: NetworkProtectionVPNLocationView()) { + NavigationLink(destination: locationView()) { NetworkProtectionLocationItemView(title: nearestLocationAttributedString, imageName: imageName) } - } header: { - Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) - .foregroundColor(.init(designSystemColor: .textSecondary)) } - .listRowBackground(Color(designSystemColor: .surface)) + + if #available(iOS 17.0, *), + let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + + TipView(changeLocationTip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } header: { + Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) + .foregroundColor(.init(designSystemColor: .textSecondary)) } + .listRowBackground(Color(designSystemColor: .surface)) + } + + @ViewBuilder + private func locationView() -> some View { + NetworkProtectionVPNLocationView() + .onAppear { + statusModel.handleUserOpenedVPNLocations() + } } @ViewBuilder diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 7e7e5dc51f..ad02654c48 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -24,6 +24,7 @@ import WidgetKit import BrowserServicesKit import Core import Subscription +import TipKit struct NetworkProtectionLocationStatusModel { enum LocationIcon { @@ -100,6 +101,15 @@ final class NetworkProtectionStatusViewModel: ObservableObject { private let errorObserver: ConnectionErrorObserver private var cancellables: Set = [] + // MARK: - Tips + + @Published + var vpnEnabledTips = TipGroup(.ordered) { + VPNChangeLocationTip() + VPNUseSnoozeTip() + VPNAddWidgetTip() + } + // MARK: Error struct ErrorItem { @@ -121,7 +131,19 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: Toggle Item - @Published public var isNetPEnabled = false + @Published public var isNetPEnabled = false { + didSet { + if #available(iOS 17.0, *) { + if isNetPEnabled { + VPNChangeLocationTip.donateVPNConnectedEvent() + } + + VPNUseSnoozeTip.vpnEnabled = isNetPEnabled + VPNAddWidgetTip.vpnEnabled = isNetPEnabled + } + } + } + @Published public var isSnoozing = false { didSet { snoozeRequestPending = false @@ -546,6 +568,17 @@ final class NetworkProtectionStatusViewModel: ObservableObject { self.downloadTotal = nil } + // MARK: - UI Events handling + + /// The user opened the VPN locations view + /// + func handleUserOpenedVPNLocations() { + if #available(iOS 17.0, *) { + Task { @MainActor in + (vpnEnabledTips.currentTip as? VPNChangeLocationTip)?.invalidate(reason: .actionPerformed) + } + } + } } private extension ConnectionStatus { diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 287ef73822..31ed317aec 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -49,6 +49,7 @@ class RootDebugViewController: UITableViewController { case resetSyncPromoPrompts = 677 case resetDuckPlayerExperiment = 678 case overrideDuckPlayerExperiment = 679 + case resetTipKit = 680 } @IBOutlet weak var shareButton: UIBarButtonItem! @@ -63,6 +64,7 @@ class RootDebugViewController: UITableViewController { private var sync: DDGSyncing? private var internalUserDecider: DefaultInternalUserDecider? var tabManager: TabManager? + private var tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling? @UserDefaultsWrapper(key: .lastConfigurationRefreshDate, defaultValue: .distantPast) private var lastConfigurationRefreshDate: Date @@ -71,24 +73,29 @@ class RootDebugViewController: UITableViewController { sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, - tabManager: TabManager) { + tabManager: TabManager, + tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling = TipKitDebugOptionsUIActionHandler()) { self.sync = sync self.bookmarksDatabase = bookmarksDatabase self.internalUserDecider = internalUserDecider as? DefaultInternalUserDecider self.tabManager = tabManager + self.tipKitUIActionHandler = tipKitUIActionHandler + + super.init(coder: coder) + } + + required init?(coder: NSCoder) { super.init(coder: coder) } - - func configure(sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, tabManager: TabManager) { + + func configure(sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, tabManager: TabManager, tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling = TipKitDebugOptionsUIActionHandler()) { + self.sync = sync self.bookmarksDatabase = bookmarksDatabase self.internalUserDecider = internalUserDecider as? DefaultInternalUserDecider self.tabManager = tabManager - } - - required init?(coder: NSCoder) { - super.init(coder: coder) + self.tipKitUIActionHandler = tipKitUIActionHandler } @IBSegueAction func onCreateImageCacheDebugScreen(_ coder: NSCoder) -> ImageCacheDebugViewController? { @@ -186,6 +193,8 @@ class RootDebugViewController: UITableViewController { case .resetDuckPlayerExperiment: DuckPlayerLaunchExperiment().cleanup() ActionMessageView.present(message: "Experiment Settings deleted. You'll be assigned a random cohort") + case .resetTipKit: + tipKitUIActionHandler?.resetTipKitTapped() case .overrideDuckPlayerExperiment: DuckPlayerLaunchExperiment().override() ActionMessageView.present(message: "Overriding experiment. You are now in the 'experiment' group. Restart the app to complete") 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/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift new file mode 100644 index 0000000000..9485c56972 --- /dev/null +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -0,0 +1,189 @@ +// +// TipGroup.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 TipKit + +/// Backport of TipKit's TipGroup to iOS versions lower than iOS 18. +/// +/// In iOS 17: this class should provide the same functionality as TipKit's `TipGroup`. +/// Before iOS 17: this class should be a glorified no-op that compiles correctly. +/// +@available(iOS, obsoleted: 18.0, renamed: "LegacyTipGroup") +struct TipGroup { + + public enum Priority: Sendable { + + /// Shows the first tip eligible for display. + case firstAvailable + + /// Shows an eligible tip when all of the previous tips have been [`invalidated`](doc:Tips/Status/invalidated(_:)). + case ordered + } + + private let priority: Priority + private let tips: [Any] + + /// Initializers for iOS versions below 17.0 + /// + @available(iOS, obsoleted: 17.0) + public init(_ priority: Priority = .firstAvailable, @LegacyTipGroupBuilder _ builder: () -> [Any]) { + + self.priority = priority + self.tips = builder() + } + + /// Initializers for iOS 17.0 + /// + @available(iOS 17.0, *) + public init(_ priority: Priority = .firstAvailable, @LegacyTipGroupBuilder _ builder: () -> [any Tip]) { + + self.priority = priority + self.tips = builder() + } + + @available(iOS 17.0, *) + @MainActor + var currentTip: (any Tip)? { + guard let tips = tips as? [any Tip] else { + return nil + } + + return tips.first { + switch $0.status { + case .available: + return true + case .invalidated: + return false + case .pending: + return priority == .ordered + @unknown default: + // Since this code is limited to iOS 17 and deprecated in iOS 18, we shouldn't + // need to worry about unknown cases. + fatalError("This path should never be called") + } + } + } +} + +@available(iOS, obsoleted: 18.0) +@resultBuilder public struct LegacyTipGroupBuilder { + public static func buildBlock() -> [Any] { + [] + } + + public static func buildBlock(_ components: Any...) -> [Any] { + components + } + + public static func buildPartialBlock(first: Any) -> [Any] { + [first] + } + + public static func buildPartialBlock(first: [Any]) -> [Any] { + first + } + + public static func buildPartialBlock(accumulated: [Any], next: Any) -> [Any] { + accumulated + [next] + } + + public static func buildPartialBlock(accumulated: [Any], next: [Any]) -> [Any] { + + accumulated + next + } + + public static func buildPartialBlock(first: Void) -> [Any] { + [] + } + + public static func buildPartialBlock(first: Never) -> [Any] { + // This will never be called + } + + public static func buildIf(_ element: [Any]?) -> [Any] { + element ?? [] + } + + public static func buildEither(first: [Any]) -> [Any] { + first + } + + public static func buildEither(second: [Any]) -> [Any] { + second + } + + public static func buildArray(_ components: [[Any]]) -> [Any] { + components.flatMap { $0 } + } +} +/* +@available(iOS 17.0, *) +@available(iOS, obsoleted: 18.0) +@resultBuilder public struct LegacyTipGroupBuilder { + + public static func buildBlock() -> [any Tip] { + [] + } + + public static func buildBlock(_ components: any Tip...) -> [any Tip] { + components + } + + public static func buildPartialBlock(first: any Tip) -> [any Tip] { + [first] + } + + public static func buildPartialBlock(first: [any Tip]) -> [any Tip] { + first + } + + public static func buildPartialBlock(accumulated: [any Tip], next: any Tip) -> [any Tip] { + accumulated + [next] + } + + public static func buildPartialBlock(accumulated: [any Tip], next: [any Tip]) -> [any Tip] { + + accumulated + next + } + + public static func buildPartialBlock(first: Void) -> [any Tip] { + [] + } + + public static func buildPartialBlock(first: Never) -> [any Tip] { + // This will never be called + } + + public static func buildIf(_ element: [any Tip]?) -> [any Tip] { + element ?? [] + } + + public static func buildEither(first: [any Tip]) -> [any Tip] { + first + } + + public static func buildEither(second: [any Tip]) -> [any Tip] { + second + } + + public static func buildArray(_ components: [[any Tip]]) -> [any Tip] { + components.flatMap { $0 } + } +}*/ diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandler.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift similarity index 65% rename from DuckDuckGo/TipKit/TipKitAppEventHandler.swift rename to DuckDuckGo/TipKit/TipKitAppEventHandling.swift index d43d84e248..0ba13636d8 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandler.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -1,5 +1,5 @@ // -// TipKitAppEventHandler.swift +// TipKitAppEventHandling.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -17,19 +17,11 @@ // limitations under the License. // +import Core import Foundation +import os.log import TipKit -struct TipKitAppEventHandler { - - func appDidFinishLaunching() { - if #available(iOS 17.0, *) { - Task { - try Tips.configure([ - .displayFrequency(.immediate), - .datastoreLocation(.applicationDefault) - ]) - } - } - } +protocol TipKitAppEventHandling { + func appDidFinishLaunching() } diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift new file mode 100644 index 0000000000..953b623c12 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController.swift @@ -0,0 +1,97 @@ +// +// 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 Core +import Foundation +import os.log +import TipKit + +protocol TipKitControlling { + @available(iOS 17.0, *) + func configureTipKit() + + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() +} + +typealias TipKitAppEventHandler = TipKitController + +final class TipKitController { + @UserDefaultsWrapper(key: .resetTipKitOnNextLaunch, defaultValue: false) + private var resetTipKitOnNextLaunch: Bool + private let logger: Logger + + init(logger: Logger = .tipKit) { + self.logger = logger + } + + @available(iOS 17.0, *) + func configureTipKit() { + do { + if resetTipKitOnNextLaunch { + resetTipKit() + resetTipKitOnNextLaunch = false + } + + try Tips.configure([ + .displayFrequency(.immediate), + .datastoreLocation(.applicationDefault) + ]) + + logger.debug("TipKit initialized") + } catch { + logger.error("Failed to initialize TipKit: \(error)") + } + } + + @available(iOS 17.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") + } +} + +extension TipKitController: TipKitAppEventHandling { + + func appDidFinishLaunching() { + if #available(iOS 17.0, *) { + configureTipKit() + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift new file mode 100644 index 0000000000..a969252f5a --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -0,0 +1,49 @@ +// +// 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 + +protocol TipKitDebugOptionsUIActionHandling { + /// Resets TipKit + func resetTipKitTapped() +} + +struct TipKitDebugOptionsUIActionHandler: TipKitDebugOptionsUIActionHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController? = nil, + logger: Logger = .tipKit) { + + self.controller = controller ?? TipKitController(logger: logger) + self.logger = logger + } + + func resetTipKitTapped() { + if #available(iOS 17.0, *) { + controller.resetTipKitOnNextAppLaunch() + + ActionMessageView.present(message: "TipKit will reset on next app launch.") + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index 2fef897318..fc24629a8a 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -28,6 +28,11 @@ struct VPNAddWidgetTip {} @available(iOS 17.0, *) extension VPNAddWidgetTip: Tip { + @Parameter(.transient) + static var vpnEnabled: Bool = false + + private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNChangeLocationTip.vpnDisconnectedEvent") + var id: String { "com.duckduckgo.tipkit.VPNAddWidgetTip" } @@ -49,4 +54,10 @@ extension VPNAddWidgetTip: Tip { // WidgetEducationView() }] } + + var rules: [Rule] { + #Rule(Self.$vpnEnabled) { + $0 == false + } + } } diff --git a/DuckDuckGo/VPNChangeLocationTip.swift b/DuckDuckGo/VPNChangeLocationTip.swift index baec29762c..4be4c4a6f0 100644 --- a/DuckDuckGo/VPNChangeLocationTip.swift +++ b/DuckDuckGo/VPNChangeLocationTip.swift @@ -28,16 +28,7 @@ struct VPNChangeLocationTip {} @available(iOS 17.0, *) extension VPNChangeLocationTip: Tip { - /// Whether the VPN is running. - /// - @Parameter - static var vpnIsRunning: Bool = false - - /// Whether the tip has been dismissed at least once by tapping on X to close it, or by going into - /// GeoSwitching while the tip was being shown. - /// - @Parameter - static var tipDismissedOnce: Bool = false + private static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNChangeLocationTip.vpnConnectedEvent") var id: String { "com.duckduckgo.tipkit.VPNChangeLocationTip" @@ -56,11 +47,14 @@ extension VPNChangeLocationTip: Tip { } var rules: [Rule] { - [#Rule(Self.$vpnIsRunning) { - $0 == true - }, - #Rule(Self.$tipDismissedOnce) { - $0 == false - }] + #Rule(Self.vpnConnectedEvent) { + $0.donations.donatedWithin(.week).count > 0 + } + } + + static func donateVPNConnectedEvent() { + Task { + await vpnConnectedEvent.donate() + } } } diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNUseSnoozeTip.swift index ef820b75d2..1f4dafd693 100644 --- a/DuckDuckGo/VPNUseSnoozeTip.swift +++ b/DuckDuckGo/VPNUseSnoozeTip.swift @@ -27,6 +27,10 @@ struct VPNUseSnoozeTip {} /// @available(iOS 17.0, *) extension VPNUseSnoozeTip: Tip { + + @Parameter(.transient) + static var vpnEnabled: Bool = false + var id: String { "com.duckduckgo.tipkit.VPNUseSnoozeTip" } @@ -45,7 +49,14 @@ extension VPNUseSnoozeTip: Tip { var actions: [Action] { [Action(title: "Learn more") { - //WidgetEducationView() + let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + UIApplication.shared.open(url, options: [:], completionHandler: nil) }] } + + var rules: [Rule] { + #Rule(Self.$vpnEnabled) { + $0 == true + } + } } From c65665ad5365b62a4a0a3511d4c9f40e0439729c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 10 Oct 2024 17:02:22 +0200 Subject: [PATCH 05/26] Makes several improvements to the the VPN tips --- DuckDuckGo/NetworkProtectionStatusView.swift | 40 ++++++++++++++----- .../NetworkProtectionStatusViewModel.swift | 3 ++ DuckDuckGo/VPNAddWidgetTip.swift | 6 +-- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 6b2a75544d..9cf860ab41 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -89,15 +89,8 @@ struct NetworkProtectionStatusView: View { } .padding([.top, .bottom], 2) - if #available(iOS 17.0, *), - !statusModel.isNetPEnabled { - - if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { - TipView(tip) - .removeGroupedListStyleInsets() - .tipCornerRadius(0) - .tipBackground(Color(designSystemColor: .surface)) - } + if #available(iOS 17.0, *) { + addWidgetTip() } snooze() @@ -154,6 +147,35 @@ struct NetworkProtectionStatusView: View { } } + @available(iOS 17.0, *) + @ViewBuilder + private func addWidgetTip() -> some View { + if !statusModel.isNetPEnabled { + + if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { + TipView(tip) { action in + if action.id == VPNAddWidgetTip.addWidgetActionId { + statusModel.showAddWidgetEducationView = true + } + }.removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + .sheet(isPresented: $statusModel.showAddWidgetEducationView) { + NavigationView { + WidgetEducationView() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.subscriptionCloseButton) { + statusModel.showAddWidgetEducationView = false + } + } + } + } + } + } + } + } + @ViewBuilder private func snooze() -> some View { if statusModel.isSnoozing { diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index ad02654c48..d006e9e5bb 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -103,6 +103,9 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: - Tips + @Published + var showAddWidgetEducationView: Bool = false + @Published var vpnEnabledTips = TipGroup(.ordered) { VPNChangeLocationTip() diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index fc24629a8a..b4bf8112ad 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -28,6 +28,8 @@ struct VPNAddWidgetTip {} @available(iOS 17.0, *) extension VPNAddWidgetTip: Tip { + static let addWidgetActionId = "com.duckduckgo.tipkit.VPNChangeLocationTip.addWidgetActionId" + @Parameter(.transient) static var vpnEnabled: Bool = false @@ -50,9 +52,7 @@ extension VPNAddWidgetTip: Tip { } var actions: [Action] { - [Action(title: "Add widget") { - // WidgetEducationView() - }] + [Action(id: Self.addWidgetActionId, title: "Add widget")] } var rules: [Rule] { From 54299130464d7be5461aeaebc7cddb93130cf319 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 10 Oct 2024 19:04:17 +0200 Subject: [PATCH 06/26] WIP --- DuckDuckGo/NetworkProtectionRootView.swift | 10 +++++----- DuckDuckGo/NetworkProtectionStatusView.swift | 14 +++++++------- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 9 +++++++++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 267be495cf..c20c193c9d 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -24,20 +24,20 @@ import Core struct NetworkProtectionRootView: View { - let statusViewModel: NetworkProtectionStatusViewModel - - init() { + @StateObject + var statusViewModel: NetworkProtectionStatusViewModel = { let accountManager = AppDependencyProvider.shared.subscriptionManager.accountManager let subscriptionFeatureAvailability = AppDependencyProvider.shared.subscriptionFeatureAvailability let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager) let usesUnifiedFeedbackForm = accountManager.isUserAuthenticated && subscriptionFeatureAvailability.usesUnifiedFeedbackForm - statusViewModel = NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, + + return NetworkProtectionStatusViewModel(tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController, settings: AppDependencyProvider.shared.vpnSettings, statusObserver: AppDependencyProvider.shared.connectionObserver, serverInfoObserver: AppDependencyProvider.shared.serverInfoObserver, locationListRepository: locationListRepository, usesUnifiedFeedbackForm: usesUnifiedFeedbackForm) - } + }() var body: some View { NetworkProtectionStatusView(statusModel: statusViewModel) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 9cf860ab41..9deb14aaaf 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -24,7 +24,9 @@ import TipKit struct NetworkProtectionStatusView: View { @Environment(\.colorScheme) var colorScheme - @StateObject public var statusModel: NetworkProtectionStatusViewModel + @ObservedObject + public var statusModel: NetworkProtectionStatusViewModel + @State var forceRedraw: Bool = false // MARK: - View @@ -153,11 +155,8 @@ struct NetworkProtectionStatusView: View { if !statusModel.isNetPEnabled { if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { - TipView(tip) { action in - if action.id == VPNAddWidgetTip.addWidgetActionId { - statusModel.showAddWidgetEducationView = true - } - }.removeGroupedListStyleInsets() + TipView(tip, action: statusModel.addWidgetActionHandler(action:)) + .removeGroupedListStyleInsets() .tipCornerRadius(0) .tipBackground(Color(designSystemColor: .surface)) .sheet(isPresented: $statusModel.showAddWidgetEducationView) { @@ -165,8 +164,9 @@ struct NetworkProtectionStatusView: View { WidgetEducationView() .toolbar { ToolbarItem(placement: .navigationBarTrailing) { - Button(UserText.subscriptionCloseButton) { + Button(UserText.navigationTitleDone) { statusModel.showAddWidgetEducationView = false + forceRedraw.toggle() } } } diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index d006e9e5bb..b8711b0e13 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -573,6 +573,15 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: - UI Events handling + @available(iOS 17.0, *) + @MainActor + func addWidgetActionHandler(action: Tips.Action) { + if action.id == VPNAddWidgetTip.addWidgetActionId { + showAddWidgetEducationView = true + (vpnEnabledTips.currentTip as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) + } + } + /// The user opened the VPN locations view /// func handleUserOpenedVPNLocations() { From 0b7ab76f8e96c53f46d7f31c6b2f619fa7ec28e0 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:03:39 +0200 Subject: [PATCH 07/26] Code cleanup --- Core/Pixel.swift | 2 +- DuckDuckGo/NetworkProtectionStatusView.swift | 101 ++++++++++-------- .../NetworkProtectionStatusViewModel.swift | 2 +- DuckDuckGo/VPNChangeLocationTip.swift | 2 - 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/Core/Pixel.swift b/Core/Pixel.swift index 827904b472..eb3770f776 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -212,7 +212,7 @@ public class Pixel { onComplete(nil) } } - + private static func updatePixelLastFireDate(pixel: Pixel.Event) { storage.set(Date(), forKey: pixel.name) } diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 9deb14aaaf..49650cb191 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -26,7 +26,6 @@ struct NetworkProtectionStatusView: View { @ObservedObject public var statusModel: NetworkProtectionStatusViewModel - @State var forceRedraw: Bool = false // MARK: - View @@ -57,6 +56,18 @@ struct NetworkProtectionStatusView: View { .animation(.easeOut, value: statusModel.shouldShowError) }) .applyInsetGroupedListStyle() + .sheet(isPresented: $statusModel.showAddWidgetEducationView) { + NavigationView { + WidgetEducationView() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.navigationTitleDone) { + statusModel.showAddWidgetEducationView = false + } + } + } + } + } } @ViewBuilder @@ -92,20 +103,13 @@ struct NetworkProtectionStatusView: View { .padding([.top, .bottom], 2) if #available(iOS 17.0, *) { - addWidgetTip() + widgetTip() } snooze() - if #available(iOS 17.0, *), - statusModel.hasServerInfo { - - if let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { - TipView(tip) - .removeGroupedListStyleInsets() - .tipCornerRadius(0) - .tipBackground(Color(designSystemColor: .surface)) - } + if #available(iOS 17.0, *) { + snoozeTip() } } header: { header() @@ -149,33 +153,6 @@ struct NetworkProtectionStatusView: View { } } - @available(iOS 17.0, *) - @ViewBuilder - private func addWidgetTip() -> some View { - if !statusModel.isNetPEnabled { - - if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { - TipView(tip, action: statusModel.addWidgetActionHandler(action:)) - .removeGroupedListStyleInsets() - .tipCornerRadius(0) - .tipBackground(Color(designSystemColor: .surface)) - .sheet(isPresented: $statusModel.showAddWidgetEducationView) { - NavigationView { - WidgetEducationView() - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(UserText.navigationTitleDone) { - statusModel.showAddWidgetEducationView = false - forceRedraw.toggle() - } - } - } - } - } - } - } - } - @ViewBuilder private func snooze() -> some View { if statusModel.isSnoozing { @@ -228,13 +205,8 @@ struct NetworkProtectionStatusView: View { } } - if #available(iOS 17.0, *), - let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { - - TipView(changeLocationTip) - .removeGroupedListStyleInsets() - .tipCornerRadius(0) - .tipBackground(Color(designSystemColor: .surface)) + if #available(iOS 17.0, *) { + geolocationTip() } } header: { Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) @@ -326,6 +298,45 @@ struct NetworkProtectionStatusView: View { isAnimating: $statusModel.isNetPEnabled ) } + + // MARK: - Tips + + @available(iOS 17.0, *) + @ViewBuilder + private func geolocationTip() -> some View { + if let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + TipView(changeLocationTip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + @available(iOS 17.0, *) + @ViewBuilder + private func snoozeTip() -> some View { + if statusModel.hasServerInfo, + let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { + + TipView(tip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + @available(iOS 17.0, *) + @ViewBuilder + private func widgetTip() -> some View { + if !statusModel.isNetPEnabled { + if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { + TipView(tip, action: statusModel.widgetActionHandler(action:)) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + } } private struct NetworkProtectionErrorView: View { diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index b8711b0e13..c5358cf651 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -575,7 +575,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @available(iOS 17.0, *) @MainActor - func addWidgetActionHandler(action: Tips.Action) { + func widgetActionHandler(action: Tips.Action) { if action.id == VPNAddWidgetTip.addWidgetActionId { showAddWidgetEducationView = true (vpnEnabledTips.currentTip as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) diff --git a/DuckDuckGo/VPNChangeLocationTip.swift b/DuckDuckGo/VPNChangeLocationTip.swift index 4be4c4a6f0..101453e574 100644 --- a/DuckDuckGo/VPNChangeLocationTip.swift +++ b/DuckDuckGo/VPNChangeLocationTip.swift @@ -23,8 +23,6 @@ import TipKit /// struct VPNChangeLocationTip {} -/// Necessary split to support older iOS versions. -/// @available(iOS 17.0, *) extension VPNChangeLocationTip: Tip { From 5e67a211477ea47eb2acbac86a9a0e495bcdc8e5 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:07:02 +0200 Subject: [PATCH 08/26] Code cleanup --- DuckDuckGo/TipKit/TipGroup.swift | 54 ------------------------ DuckDuckGo/TipKit/TipKitController.swift | 19 +++++++-- 2 files changed, 16 insertions(+), 57 deletions(-) diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index 9485c56972..05a341bb77 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -133,57 +133,3 @@ struct TipGroup { components.flatMap { $0 } } } -/* -@available(iOS 17.0, *) -@available(iOS, obsoleted: 18.0) -@resultBuilder public struct LegacyTipGroupBuilder { - - public static func buildBlock() -> [any Tip] { - [] - } - - public static func buildBlock(_ components: any Tip...) -> [any Tip] { - components - } - - public static func buildPartialBlock(first: any Tip) -> [any Tip] { - [first] - } - - public static func buildPartialBlock(first: [any Tip]) -> [any Tip] { - first - } - - public static func buildPartialBlock(accumulated: [any Tip], next: any Tip) -> [any Tip] { - accumulated + [next] - } - - public static func buildPartialBlock(accumulated: [any Tip], next: [any Tip]) -> [any Tip] { - - accumulated + next - } - - public static func buildPartialBlock(first: Void) -> [any Tip] { - [] - } - - public static func buildPartialBlock(first: Never) -> [any Tip] { - // This will never be called - } - - public static func buildIf(_ element: [any Tip]?) -> [any Tip] { - element ?? [] - } - - public static func buildEither(first: [any Tip]) -> [any Tip] { - first - } - - public static func buildEither(second: [any Tip]) -> [any Tip] { - second - } - - public static func buildArray(_ components: [[any Tip]]) -> [any Tip] { - components.flatMap { $0 } - } -}*/ diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift index 953b623c12..0eb656c5f1 100644 --- a/DuckDuckGo/TipKit/TipKitController.swift +++ b/DuckDuckGo/TipKit/TipKitController.swift @@ -33,12 +33,25 @@ protocol TipKitControlling { typealias TipKitAppEventHandler = TipKitController final class TipKitController { - @UserDefaultsWrapper(key: .resetTipKitOnNextLaunch, defaultValue: false) - private var resetTipKitOnNextLaunch: Bool + 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 = .tipKit, + userDefaults: UserDefaults = .standard) { - init(logger: Logger = .tipKit) { self.logger = logger + self.userDefaults = userDefaults } @available(iOS 17.0, *) From a6954916df6c90a8dcab145d0a54785dafa4bf2e Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:11:48 +0200 Subject: [PATCH 09/26] Rolls back some unrelated changes --- DuckDuckGo.xcodeproj/project.pbxproj | 12 ------ DuckDuckGo/AppDelegate.swift | 6 +-- .../AppIntegration/VPNAppEventsHandler.swift | 37 ------------------- 3 files changed, 3 insertions(+), 52 deletions(-) delete mode 100644 DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 331a1f3279..0300c74ac7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -363,7 +363,6 @@ 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC97CC42CB7ECDE00FF521F /* TipGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */; }; - 7BF78DFF2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */; }; 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; @@ -1648,7 +1647,6 @@ 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipGroup.swift; sourceTree = ""; }; - 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAppEventsHandler.swift; sourceTree = ""; }; 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; @@ -4027,14 +4025,6 @@ name = AdAttribution; sourceTree = ""; }; - 7BF78DFD2CA2CAD10026A1FC /* AppIntegration */ = { - isa = PBXGroup; - children = ( - 7BF78DFE2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift */, - ); - path = AppIntegration; - sourceTree = ""; - }; 7BF78E002CA2CC100026A1FC /* TipKit */ = { isa = PBXGroup; children = ( @@ -5588,7 +5578,6 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( - 7BF78DFD2CA2CAD10026A1FC /* AppIntegration */, 7BFD5FD32C9DA235000FF959 /* TipKit */, 4BD96E072C4DCCD1003BC32C /* LiveActivity */, 4B37E04E2B928C91009E81CA /* Resources */, @@ -7508,7 +7497,6 @@ D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */, BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */, F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */, - 7BF78DFF2CA2CAEE0026A1FC /* VPNAppEventsHandler.swift in Sources */, 6FE1273A2C204BD000EB5724 /* NewTabPageView.swift in Sources */, 986DA94A24884B18004A7E39 /* WebViewTransition.swift in Sources */, 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index c2ace31658..9a8e6509bb 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -57,6 +57,7 @@ import TipKit private lazy var privacyStore = PrivacyUserDefaults() private var bookmarksDatabase: CoreDataDatabase = BookmarksDatabase.make() + private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @MainActor @@ -92,7 +93,6 @@ import TipKit // MARK: - Feature specific app event handlers private let tipKitAppEventsHandler = TipKitAppEventHandler() - private let vpnAppEventsHandler = VPNAppEventsHandler() // MARK: lifecycle @@ -378,7 +378,7 @@ import TipKit NewTabPageIntroMessageSetup().perform() - vpnAppEventsHandler.appDidFinishLaunching() + widgetRefreshModel.beginObservingVPNStatus() AppDependencyProvider.shared.subscriptionManager.loadInitialData() @@ -541,7 +541,7 @@ import TipKit fireFailedCompilationsPixelIfNeeded() - VPNAppEventsHandler().appDidBecomeActive() + widgetRefreshModel.refreshVPNWidget() if tunnelDefaults.showEntitlementAlert { presentExpiredEntitlementAlert() diff --git a/DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift b/DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift deleted file mode 100644 index 5928647f52..0000000000 --- a/DuckDuckGo/AppIntegration/VPNAppEventsHandler.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// VPNAppEventsHandler.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 - -struct VPNAppEventsHandler { - - private let widgetRefreshModel: NetworkProtectionWidgetRefreshModel - - init(widgetRefreshModel: NetworkProtectionWidgetRefreshModel = .init()) { - self.widgetRefreshModel = widgetRefreshModel - } - - func appDidFinishLaunching() { - widgetRefreshModel.beginObservingVPNStatus() - } - - func appDidBecomeActive() { - widgetRefreshModel.beginObservingVPNStatus() - } -} From c0885b6e3a9c6dfca3fd5298355b59ba03dee32a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:12:31 +0200 Subject: [PATCH 10/26] Rolls back an unnecessary change --- DuckDuckGo/AppDelegate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 9a8e6509bb..b5945ee644 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -38,7 +38,6 @@ import Subscription import NetworkProtection import WebKit import os.log -import TipKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { From 3e95e053085a29c0803d769c117b1569c646760a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:23:53 +0200 Subject: [PATCH 11/26] Code cleanup --- DuckDuckGo/NetworkProtectionStatusView.swift | 2 +- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 8 +++++++- DuckDuckGo/VPNAddWidgetTip.swift | 4 ++-- DuckDuckGo/VPNUseSnoozeTip.swift | 7 +++---- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 49650cb191..ea3a06105d 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -318,7 +318,7 @@ struct NetworkProtectionStatusView: View { if statusModel.hasServerInfo, let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { - TipView(tip) + TipView(tip, action: statusModel.snoozeActionHandler(action:)) .removeGroupedListStyleInsets() .tipCornerRadius(0) .tipBackground(Color(designSystemColor: .surface)) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index c5358cf651..86a792b33d 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -573,10 +573,16 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: - UI Events handling + @available(iOS 17.0, *) + func snoozeActionHandler(action: Tips.Action) { + let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + @available(iOS 17.0, *) @MainActor func widgetActionHandler(action: Tips.Action) { - if action.id == VPNAddWidgetTip.addWidgetActionId { + if action.id == VPNAddWidgetTip.widgetActionId { showAddWidgetEducationView = true (vpnEnabledTips.currentTip as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) } diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index b4bf8112ad..532da35a5a 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -28,7 +28,7 @@ struct VPNAddWidgetTip {} @available(iOS 17.0, *) extension VPNAddWidgetTip: Tip { - static let addWidgetActionId = "com.duckduckgo.tipkit.VPNChangeLocationTip.addWidgetActionId" + static let widgetActionId = "com.duckduckgo.tipkit.VPNChangeLocationTip.widgetActionId" @Parameter(.transient) static var vpnEnabled: Bool = false @@ -52,7 +52,7 @@ extension VPNAddWidgetTip: Tip { } var actions: [Action] { - [Action(id: Self.addWidgetActionId, title: "Add widget")] + [Action(id: Self.widgetActionId, title: "Add widget")] } var rules: [Rule] { diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNUseSnoozeTip.swift index 1f4dafd693..b5d5378976 100644 --- a/DuckDuckGo/VPNUseSnoozeTip.swift +++ b/DuckDuckGo/VPNUseSnoozeTip.swift @@ -28,6 +28,8 @@ struct VPNUseSnoozeTip {} @available(iOS 17.0, *) extension VPNUseSnoozeTip: Tip { + static let learnMoreId = "com.duckduckgo.tipkit.VPNChangeLocationTip.learnMoreId" + @Parameter(.transient) static var vpnEnabled: Bool = false @@ -48,10 +50,7 @@ extension VPNUseSnoozeTip: Tip { } var actions: [Action] { - [Action(title: "Learn more") { - let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! - UIApplication.shared.open(url, options: [:], completionHandler: nil) - }] + [Action(id: Self.learnMoreId, title: "Learn more")] } var rules: [Rule] { From 7bb757fa0b5aa48a910eb7ca8f1182eefbb74995 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:26:15 +0200 Subject: [PATCH 12/26] Code cleanup --- DuckDuckGo/NetworkProtectionStatusView.swift | 26 ++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index ea3a06105d..252f5e3b41 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -57,16 +57,7 @@ struct NetworkProtectionStatusView: View { }) .applyInsetGroupedListStyle() .sheet(isPresented: $statusModel.showAddWidgetEducationView) { - NavigationView { - WidgetEducationView() - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(UserText.navigationTitleDone) { - statusModel.showAddWidgetEducationView = false - } - } - } - } + widgetEducationSheet() } } @@ -337,6 +328,21 @@ struct NetworkProtectionStatusView: View { } } } + + // MARK: - Sheets + + private func widgetEducationSheet() -> some View { + NavigationView { + WidgetEducationView() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.navigationTitleDone) { + statusModel.showAddWidgetEducationView = false + } + } + } + } + } } private struct NetworkProtectionErrorView: View { From fc2c5fb3dada652fdfa5537d6d93a6020002e2bc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 11:32:23 +0200 Subject: [PATCH 13/26] Code cleanup --- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 8 +++++--- DuckDuckGo/VPNAddWidgetTip.swift | 8 +++++--- DuckDuckGo/VPNUseSnoozeTip.swift | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 86a792b33d..09fa13d316 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -575,14 +575,16 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @available(iOS 17.0, *) func snoozeActionHandler(action: Tips.Action) { - let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! - UIApplication.shared.open(url, options: [:], completionHandler: nil) + if action.id == VPNUseSnoozeTip.ActionIdentifiers.learnMore.rawValue { + let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } } @available(iOS 17.0, *) @MainActor func widgetActionHandler(action: Tips.Action) { - if action.id == VPNAddWidgetTip.widgetActionId { + if action.id == VPNAddWidgetTip.ActionIdentifiers.addWidget.rawValue { showAddWidgetEducationView = true (vpnEnabledTips.currentTip as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) } diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index 532da35a5a..cc3d88cb8c 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -28,12 +28,14 @@ struct VPNAddWidgetTip {} @available(iOS 17.0, *) extension VPNAddWidgetTip: Tip { - static let widgetActionId = "com.duckduckgo.tipkit.VPNChangeLocationTip.widgetActionId" + 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.VPNChangeLocationTip.vpnDisconnectedEvent") + private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNAddWidgetTip.vpnDisconnectedEvent") var id: String { "com.duckduckgo.tipkit.VPNAddWidgetTip" @@ -52,7 +54,7 @@ extension VPNAddWidgetTip: Tip { } var actions: [Action] { - [Action(id: Self.widgetActionId, title: "Add widget")] + [Action(id: ActionIdentifiers.addWidget.rawValue, title: "Add widget")] } var rules: [Rule] { diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNUseSnoozeTip.swift index b5d5378976..ae0bf68f08 100644 --- a/DuckDuckGo/VPNUseSnoozeTip.swift +++ b/DuckDuckGo/VPNUseSnoozeTip.swift @@ -28,7 +28,9 @@ struct VPNUseSnoozeTip {} @available(iOS 17.0, *) extension VPNUseSnoozeTip: Tip { - static let learnMoreId = "com.duckduckgo.tipkit.VPNChangeLocationTip.learnMoreId" + enum ActionIdentifiers: String { + case learnMore = "com.duckduckgo.tipkit.VPNUseSnoozeTip.learnMoreId" + } @Parameter(.transient) static var vpnEnabled: Bool = false @@ -50,7 +52,7 @@ extension VPNUseSnoozeTip: Tip { } var actions: [Action] { - [Action(id: Self.learnMoreId, title: "Learn more")] + [Action(id: ActionIdentifiers.learnMore.rawValue, title: "Learn more")] } var rules: [Rule] { From 696c0aa2a34514d2f44ca4220b1ccfd1530696e6 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 12:12:58 +0200 Subject: [PATCH 14/26] Improved TipGroup support for older iOS versions --- DuckDuckGo/NetworkProtectionStatusView.swift | 6 +- .../NetworkProtectionStatusViewModel.swift | 22 +++-- DuckDuckGo/TipKit/TipGroup.swift | 80 +++++++++++-------- 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 252f5e3b41..3570947b76 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -295,7 +295,7 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder private func geolocationTip() -> some View { - if let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + if let changeLocationTip = statusModel.vpnEnabledTips.current as? VPNChangeLocationTip { TipView(changeLocationTip) .removeGroupedListStyleInsets() .tipCornerRadius(0) @@ -307,7 +307,7 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func snoozeTip() -> some View { if statusModel.hasServerInfo, - let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { + let tip = statusModel.vpnEnabledTips.current as? VPNUseSnoozeTip { TipView(tip, action: statusModel.snoozeActionHandler(action:)) .removeGroupedListStyleInsets() @@ -320,7 +320,7 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func widgetTip() -> some View { if !statusModel.isNetPEnabled { - if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { + if let tip = statusModel.vpnEnabledTips.current as? VPNAddWidgetTip { TipView(tip, action: statusModel.widgetActionHandler(action:)) .removeGroupedListStyleInsets() .tipCornerRadius(0) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 09fa13d316..0259f3b12a 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -107,11 +107,23 @@ final class NetworkProtectionStatusViewModel: ObservableObject { var showAddWidgetEducationView: Bool = false @Published - var vpnEnabledTips = TipGroup(.ordered) { - VPNChangeLocationTip() - VPNUseSnoozeTip() - VPNAddWidgetTip() - } + var vpnEnabledTips: TipGrouping = { + if #available(iOS 18.0, *) { + return TipGroup(.ordered) { + VPNChangeLocationTip() + VPNUseSnoozeTip() + VPNAddWidgetTip() + } + } else if #available(iOS 17.0, *) { + return LegacyTipGroup(.ordered) { + VPNChangeLocationTip() + VPNUseSnoozeTip() + VPNAddWidgetTip() + } + } else { + return EmptyTipGroup() + } + }() // MARK: Error diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index 05a341bb77..b9df8ddd8c 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -20,15 +20,39 @@ import Foundation import TipKit +protocol TipGrouping { + @MainActor + var current: Any? { get } +} + +@available(iOS 18.0, *) +extension TipGroup: TipGrouping { + @MainActor + var current: Any? { + currentTip + } +} + +/// A glorified no-op to be able to compile TipGrouping in iOS versions below 17 +/// +struct EmptyTipGroup: TipGrouping { + var current: Any? { + return nil + } +} + /// Backport of TipKit's TipGroup to iOS versions lower than iOS 18. /// /// In iOS 17: this class should provide the same functionality as TipKit's `TipGroup`. /// Before iOS 17: this class should be a glorified no-op that compiles correctly. /// -@available(iOS, obsoleted: 18.0, renamed: "LegacyTipGroup") -struct TipGroup { +@available(iOS 17.0, *) +@available(iOS, obsoleted: 18.0) +struct LegacyTipGroup: TipGrouping { - public enum Priority: Sendable { + /// This is an implementation of TipGroup.Priority for iOS versions below 18. + /// + enum Priority { /// Shows the first tip eligible for display. case firstAvailable @@ -38,33 +62,15 @@ struct TipGroup { } private let priority: Priority - private let tips: [Any] - - /// Initializers for iOS versions below 17.0 - /// - @available(iOS, obsoleted: 17.0) - public init(_ priority: Priority = .firstAvailable, @LegacyTipGroupBuilder _ builder: () -> [Any]) { + private let tips: [any Tip] + init(_ priority: Priority, @LegacyTipGroupBuilder _ builder: () -> [any Tip]) { self.priority = priority self.tips = builder() } - /// Initializers for iOS 17.0 - /// - @available(iOS 17.0, *) - public init(_ priority: Priority = .firstAvailable, @LegacyTipGroupBuilder _ builder: () -> [any Tip]) { - - self.priority = priority - self.tips = builder() - } - - @available(iOS 17.0, *) @MainActor var currentTip: (any Tip)? { - guard let tips = tips as? [any Tip] else { - return nil - } - return tips.first { switch $0.status { case .available: @@ -80,56 +86,62 @@ struct TipGroup { } } } + + @MainActor + var current: Any? { + currentTip + } } +@available(iOS 17.0, *) @available(iOS, obsoleted: 18.0) @resultBuilder public struct LegacyTipGroupBuilder { public static func buildBlock() -> [Any] { [] } - public static func buildBlock(_ components: Any...) -> [Any] { + public static func buildBlock(_ components: any Tip...) -> [any Tip] { components } - public static func buildPartialBlock(first: Any) -> [Any] { + public static func buildPartialBlock(first: any Tip) -> [any Tip] { [first] } - public static func buildPartialBlock(first: [Any]) -> [Any] { + public static func buildPartialBlock(first: [any Tip]) -> [any Tip] { first } - public static func buildPartialBlock(accumulated: [Any], next: Any) -> [Any] { + public static func buildPartialBlock(accumulated: [any Tip], next: any Tip) -> [any Tip] { accumulated + [next] } - public static func buildPartialBlock(accumulated: [Any], next: [Any]) -> [Any] { + public static func buildPartialBlock(accumulated: [any Tip], next: [any Tip]) -> [any Tip] { accumulated + next } - public static func buildPartialBlock(first: Void) -> [Any] { + public static func buildPartialBlock(first: Void) -> [any Tip] { [] } - public static func buildPartialBlock(first: Never) -> [Any] { + public static func buildPartialBlock(first: Never) -> [any Tip] { // This will never be called } - public static func buildIf(_ element: [Any]?) -> [Any] { + public static func buildIf(_ element: [any Tip]?) -> [any Tip] { element ?? [] } - public static func buildEither(first: [Any]) -> [Any] { + public static func buildEither(first: [any Tip]) -> [any Tip] { first } - public static func buildEither(second: [Any]) -> [Any] { + public static func buildEither(second: [any Tip]) -> [any Tip] { second } - public static func buildArray(_ components: [[Any]]) -> [Any] { + public static func buildArray(_ components: [[any Tip]]) -> [any Tip] { components.flatMap { $0 } } } From b05febb7b4df8882c91116f8f678ce6bee0bae40 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 12:17:11 +0200 Subject: [PATCH 15/26] Further improvements to TipGroup --- DuckDuckGo/NetworkProtectionStatusView.swift | 6 +++--- .../NetworkProtectionStatusViewModel.swift | 4 ++-- DuckDuckGo/TipKit/TipGroup.swift | 16 +++++----------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 3570947b76..252f5e3b41 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -295,7 +295,7 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder private func geolocationTip() -> some View { - if let changeLocationTip = statusModel.vpnEnabledTips.current as? VPNChangeLocationTip { + if let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { TipView(changeLocationTip) .removeGroupedListStyleInsets() .tipCornerRadius(0) @@ -307,7 +307,7 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func snoozeTip() -> some View { if statusModel.hasServerInfo, - let tip = statusModel.vpnEnabledTips.current as? VPNUseSnoozeTip { + let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { TipView(tip, action: statusModel.snoozeActionHandler(action:)) .removeGroupedListStyleInsets() @@ -320,7 +320,7 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func widgetTip() -> some View { if !statusModel.isNetPEnabled { - if let tip = statusModel.vpnEnabledTips.current as? VPNAddWidgetTip { + if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { TipView(tip, action: statusModel.widgetActionHandler(action:)) .removeGroupedListStyleInsets() .tipCornerRadius(0) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 0259f3b12a..14f90baefa 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -598,7 +598,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { func widgetActionHandler(action: Tips.Action) { if action.id == VPNAddWidgetTip.ActionIdentifiers.addWidget.rawValue { showAddWidgetEducationView = true - (vpnEnabledTips.currentTip as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) + (vpnEnabledTips.current as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) } } @@ -607,7 +607,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { func handleUserOpenedVPNLocations() { if #available(iOS 17.0, *) { Task { @MainActor in - (vpnEnabledTips.currentTip as? VPNChangeLocationTip)?.invalidate(reason: .actionPerformed) + (vpnEnabledTips.current as? VPNChangeLocationTip)?.invalidate(reason: .actionPerformed) } } } diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index b9df8ddd8c..cb28e72e9a 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -21,22 +21,16 @@ import Foundation import TipKit protocol TipGrouping { + @available(iOS 17.0, *) @MainActor - var current: Any? { get } + var currentTip: (any Tip)? { get } } -@available(iOS 18.0, *) -extension TipGroup: TipGrouping { - @MainActor - var current: Any? { - currentTip - } -} - -/// A glorified no-op to be able to compile TipGrouping in iOS versions below 17 +/// A glorified no-op to be able to compile TipGrouping in iOS versions below 17. /// struct EmptyTipGroup: TipGrouping { - var current: Any? { + @available(iOS 17.0, *) + var currentTip: (any Tip)? { return nil } } From c83510e832b5f9a54f58a23e0e081f878cb022b3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 12:18:42 +0200 Subject: [PATCH 16/26] Fixes a compilation warning --- DuckDuckGo/TipKit/TipGroup.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index cb28e72e9a..4d0eba352e 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -26,6 +26,9 @@ protocol TipGrouping { var currentTip: (any Tip)? { get } } +@available(iOS 18.0, *) +extension TipGroup: TipGrouping {} + /// A glorified no-op to be able to compile TipGrouping in iOS versions below 17. /// struct EmptyTipGroup: TipGrouping { From b8e8393aa2597c32fbe939f99597fbf63f277fa3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 12:19:06 +0200 Subject: [PATCH 17/26] Fixes some additional build errors --- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 14f90baefa..0259f3b12a 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -598,7 +598,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { func widgetActionHandler(action: Tips.Action) { if action.id == VPNAddWidgetTip.ActionIdentifiers.addWidget.rawValue { showAddWidgetEducationView = true - (vpnEnabledTips.current as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) + (vpnEnabledTips.currentTip as? VPNAddWidgetTip)?.invalidate(reason: .actionPerformed) } } @@ -607,7 +607,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { func handleUserOpenedVPNLocations() { if #available(iOS 17.0, *) { Task { @MainActor in - (vpnEnabledTips.current as? VPNChangeLocationTip)?.invalidate(reason: .actionPerformed) + (vpnEnabledTips.currentTip as? VPNChangeLocationTip)?.invalidate(reason: .actionPerformed) } } } From f26b6eb1757ca74c0f4ec4fb577456c500df5bf8 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 12:46:41 +0200 Subject: [PATCH 18/26] Some final minor fixes --- DuckDuckGo/NetworkProtectionStatusView.swift | 2 +- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 4 ++++ DuckDuckGo/TipKit/TipGroup.swift | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 252f5e3b41..52559935d7 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -319,7 +319,7 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder private func widgetTip() -> some View { - if !statusModel.isNetPEnabled { + if !statusModel.isNetPEnabled && !statusModel.isSnoozing { if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { TipView(tip, action: statusModel.widgetActionHandler(action:)) .removeGroupedListStyleInsets() diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 0259f3b12a..1781562194 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -489,6 +489,10 @@ final class NetworkProtectionStatusViewModel: ObservableObject { return } + if #available(iOS 17.0, *) { + (vpnEnabledTips.currentTip as? VPNUseSnoozeTip)?.invalidate(reason: .actionPerformed) + } + let defaultDuration: TimeInterval = .minutes(20) snoozeRequestPending = true try? await activeSession.sendProviderMessage(.startSnooze(defaultDuration)) diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index 4d0eba352e..0cc8a817e9 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -38,10 +38,9 @@ struct EmptyTipGroup: TipGrouping { } } -/// Backport of TipKit's TipGroup to iOS versions lower than iOS 18. +/// Backport of TipKit's TipGroup to iOS 17. /// /// In iOS 17: this class should provide the same functionality as TipKit's `TipGroup`. -/// Before iOS 17: this class should be a glorified no-op that compiles correctly. /// @available(iOS 17.0, *) @available(iOS, obsoleted: 18.0) From 2e99922aab024e73dbd259bc342e0023a0c9acdc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 13:50:19 +0200 Subject: [PATCH 19/26] Wires in the remote feature flag --- DuckDuckGo/NetworkProtectionStatusView.swift | 11 ++++++++--- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 52559935d7..4cf52c9f11 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -295,7 +295,9 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder private func geolocationTip() -> some View { - if let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + if statusModel.canShowTips, + let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + TipView(changeLocationTip) .removeGroupedListStyleInsets() .tipCornerRadius(0) @@ -306,7 +308,8 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder private func snoozeTip() -> some View { - if statusModel.hasServerInfo, + if statusModel.canShowTips, + statusModel.hasServerInfo, let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { TipView(tip, action: statusModel.snoozeActionHandler(action:)) @@ -319,7 +322,9 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder private func widgetTip() -> some View { - if !statusModel.isNetPEnabled && !statusModel.isSnoozing { + if statusModel.canShowTips, + !statusModel.isNetPEnabled && !statusModel.isSnoozing { + if let tip = statusModel.vpnEnabledTips.currentTip as? VPNAddWidgetTip { TipView(tip, action: statusModel.widgetActionHandler(action:)) .removeGroupedListStyleInsets() diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 1781562194..700ee8e92d 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -95,6 +95,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { return formatter }() + private let featureFlagger = AppDependencyProvider.shared.featureFlagger private let tunnelController: (TunnelController & TunnelSessionProvider) private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver @@ -103,6 +104,10 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: - Tips + var canShowTips: Bool { + featureFlagger.isFeatureOn(.networkProtectionUserTips) + } + @Published var showAddWidgetEducationView: Bool = false From 89f2a213e66ed3706b6bcd271ac775258bd99c72 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 17:10:55 +0200 Subject: [PATCH 20/26] Temporarily comments some Xcode 16-only code --- .../NetworkProtectionStatusViewModel.swift | 19 ++++++++++--------- DuckDuckGo/TipKit/TipGroup.swift | 6 ++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 700ee8e92d..a47aaa25cc 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -111,15 +111,16 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @Published var showAddWidgetEducationView: Bool = false - @Published - var vpnEnabledTips: TipGrouping = { - if #available(iOS 18.0, *) { - return TipGroup(.ordered) { - VPNChangeLocationTip() - VPNUseSnoozeTip() - VPNAddWidgetTip() - } - } else if #available(iOS 17.0, *) { + let vpnEnabledTips: TipGrouping = { + // This is temporarily disabled until Xcode 16 is available. + // Ref: + // if #available(iOS 18.0, *) { + // return TipGroup(.ordered) { + // VPNChangeLocationTip() + // VPNUseSnoozeTip() + // VPNAddWidgetTip() + // } + if #available(iOS 17.0, *) { return LegacyTipGroup(.ordered) { VPNChangeLocationTip() VPNUseSnoozeTip() diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index 0cc8a817e9..01dcad7cc8 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -26,8 +26,10 @@ protocol TipGrouping { var currentTip: (any Tip)? { get } } -@available(iOS 18.0, *) -extension TipGroup: TipGrouping {} +// This only compiles in Xcode 16 and needs to be re-enalbed once we move to it. +// +//@available(iOS 18.0, *) +//extension TipGroup: TipGrouping {} /// A glorified no-op to be able to compile TipGrouping in iOS versions below 17. /// From ab8e06ce980c174ad6525f0f9864ad5e1fc703a3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 11 Oct 2024 17:19:21 +0200 Subject: [PATCH 21/26] Makes some changes to fix swiflint errors and document disabled code --- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 3 ++- DuckDuckGo/TipKit/TipGroup.swift | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index a47aaa25cc..307bf60764 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -113,7 +113,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject { let vpnEnabledTips: TipGrouping = { // This is temporarily disabled until Xcode 16 is available. - // Ref: + // Ref: https://app.asana.com/0/414235014887631/1208528787265444/f + // // if #available(iOS 18.0, *) { // return TipGroup(.ordered) { // VPNChangeLocationTip() diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index 01dcad7cc8..4da7815a61 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -27,9 +27,10 @@ protocol TipGrouping { } // This only compiles in Xcode 16 and needs to be re-enalbed once we move to it. +// Ref: https://app.asana.com/0/414235014887631/1208528787265444/f // -//@available(iOS 18.0, *) -//extension TipGroup: TipGrouping {} +// @available(iOS 18.0, *) +// extension TipGroup: TipGrouping {} /// A glorified no-op to be able to compile TipGrouping in iOS versions below 17. /// From 888b67a289d3e770aab0b8ef9760683ba8d8b399 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 15 Oct 2024 14:39:08 +0200 Subject: [PATCH 22/26] Several changes to VPN tips --- DuckDuckGo/NetworkProtectionStatusView.swift | 29 ++++++++++++-------- DuckDuckGo/TipKit/TipGroup.swift | 11 +++++--- DuckDuckGo/VPNAddWidgetTip.swift | 5 +++- DuckDuckGo/VPNUseSnoozeTip.swift | 5 +++- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 4cf52c9f11..202cd2d765 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -93,19 +93,23 @@ struct NetworkProtectionStatusView: View { } .padding([.top, .bottom], 2) + snooze() + + } header: { + header() + } + .increaseHeaderProminence() + .listRowBackground(Color(designSystemColor: .surface)) + + Section { if #available(iOS 17.0, *) { widgetTip() } - snooze() - if #available(iOS 17.0, *) { snoozeTip() } - } header: { - header() } - .increaseHeaderProminence() .listRowBackground(Color(designSystemColor: .surface)) } @@ -195,15 +199,18 @@ struct NetworkProtectionStatusView: View { NetworkProtectionLocationItemView(title: nearestLocationAttributedString, imageName: imageName) } } - - if #available(iOS 17.0, *) { - geolocationTip() - } } header: { Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) .foregroundColor(.init(designSystemColor: .textSecondary)) } .listRowBackground(Color(designSystemColor: .surface)) + + Section { + if #available(iOS 17.0, *) { + geolocationTip() + } + } + .listRowBackground(Color(designSystemColor: .surface)) } @ViewBuilder @@ -296,9 +303,9 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func geolocationTip() -> some View { if statusModel.canShowTips, - let changeLocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + let geolocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { - TipView(changeLocationTip) + TipView(geolocationTip) .removeGroupedListStyleInsets() .tipCornerRadius(0) .tipBackground(Color(designSystemColor: .surface)) diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift index 4da7815a61..89949a17b6 100644 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ b/DuckDuckGo/TipKit/TipGroup.swift @@ -61,16 +61,19 @@ struct LegacyTipGroup: TipGrouping { } private let priority: Priority - private let tips: [any Tip] - init(_ priority: Priority, @LegacyTipGroupBuilder _ builder: () -> [any Tip]) { + @LegacyTipGroupBuilder + private let tipBuilder: () -> [any Tip] + + init(_ priority: Priority = .ordered, @LegacyTipGroupBuilder _ tipBuilder: @escaping () -> [any Tip]) { + self.priority = priority - self.tips = builder() + self.tipBuilder = tipBuilder } @MainActor var currentTip: (any Tip)? { - return tips.first { + return tipBuilder().first { switch $0.status { case .available: return true diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index cc3d88cb8c..459f51803a 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -54,7 +54,10 @@ extension VPNAddWidgetTip: Tip { } var actions: [Action] { - [Action(id: ActionIdentifiers.addWidget.rawValue, title: "Add widget")] + [Action(id: ActionIdentifiers.addWidget.rawValue) { + Text("Add widget") + .foregroundStyle(Color(designSystemColor: .accent)) + }] } var rules: [Rule] { diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNUseSnoozeTip.swift index ae0bf68f08..a97e397705 100644 --- a/DuckDuckGo/VPNUseSnoozeTip.swift +++ b/DuckDuckGo/VPNUseSnoozeTip.swift @@ -52,7 +52,10 @@ extension VPNUseSnoozeTip: Tip { } var actions: [Action] { - [Action(id: ActionIdentifiers.learnMore.rawValue, title: "Learn more")] + [Action(id: ActionIdentifiers.learnMore.rawValue) { + Text("Learn more") + .foregroundStyle(Color(designSystemColor: .accent)) + }] } var rules: [Rule] { From 953f864b62ce42691663237702791c41eef17672 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 15 Oct 2024 15:40:10 +0200 Subject: [PATCH 23/26] Moves some TipKit code to BSK to share with macOS --- DuckDuckGo.xcodeproj/project.pbxproj | 23 +-- .../xcshareddata/swiftpm/Package.resolved | 11 +- .../NetworkProtectionStatusViewModel.swift | 1 + DuckDuckGo/TipKit/TipGroup.swift | 149 ------------------ .../TipKit/TipKitAppEventHandling.swift | 26 ++- ...itController+ConvenienceInitializers.swift | 31 ++++ DuckDuckGo/TipKit/TipKitController.swift | 110 ------------- .../TipKitDebugOptionsUIActionHandling.swift | 5 +- 8 files changed, 74 insertions(+), 282 deletions(-) delete mode 100644 DuckDuckGo/TipKit/TipGroup.swift create mode 100644 DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift delete mode 100644 DuckDuckGo/TipKit/TipKitController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d06ae5872e..a5b3eb2b23 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -358,11 +358,11 @@ 6FEC0B882C999961006B4F6E /* FavoriteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */; }; - 7B1604EC2CB68BDA00A44EC6 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController.swift */; }; + 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */; }; 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */; }; + 7B94A5102CBE9DEB0083AB69 /* TipKitUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 7B94A50F2CBE9DEB0083AB69 /* TipKitUtils */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; - 7BC97CC42CB7ECDE00FF521F /* TipGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */; }; 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; @@ -1648,10 +1648,9 @@ 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteDataSource.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; - 7B1604EB2CB68BDA00A44EC6 /* TipKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitController.swift; sourceTree = ""; }; + 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; - 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipGroup.swift; sourceTree = ""; }; 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; @@ -3066,6 +3065,7 @@ F1D43AFA2B99C1D300BAB743 /* BareBonesBrowserKit in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, 31E69A63280F4CB600478327 /* DuckUI in Frameworks */, + 7B94A5102CBE9DEB0083AB69 /* TipKitUtils in Frameworks */, CB941A6E2B96AB08000F9E7A /* PrivacyDashboard in Frameworks */, F42D541D29DCA40B004C4FF1 /* DesignResourcesKit in Frameworks */, 85875B6129912A9900115F05 /* SyncUI in Frameworks */, @@ -4032,9 +4032,8 @@ children = ( 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */, 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */, - 7B1604EB2CB68BDA00A44EC6 /* TipKitController.swift */, + 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */, 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */, - 7BC97CC32CB7ECDE00FF521F /* TipGroup.swift */, ); path = TipKit; sourceTree = ""; @@ -6572,6 +6571,7 @@ F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */, 9F8FE9482BAE50E50071E372 /* Lottie */, 9F96F73A2C9144D5009E45D5 /* Onboarding */, + 7B94A50F2CBE9DEB0083AB69 /* TipKitUtils */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -7377,7 +7377,7 @@ EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, - 7B1604EC2CB68BDA00A44EC6 /* TipKitController.swift in Sources */, + 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */, 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, @@ -7892,7 +7892,6 @@ 85B9814E2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift in Sources */, 985AAE4524899369007A43EC /* HomeScreenTransition.swift in Sources */, 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, - 7BC97CC42CB7ECDE00FF521F /* TipGroup.swift in Sources */, 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, 6FB1FEA22C256ACD0075B68B /* NewTabPageManager.swift in Sources */, @@ -10994,8 +10993,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 199.1.0; + kind = revision; + revision = 970af02afd67ac8cbd37c21196569a7af610a081; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { @@ -11156,6 +11155,10 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Common; }; + 7B94A50F2CBE9DEB0083AB69 /* TipKitUtils */ = { + isa = XCSwiftPackageProductDependency; + productName = TipKitUtils; + }; 851481872A600EFC00ABC65F /* RemoteMessaging */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8d60ee5c9f..5efd617471 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", @@ -138,7 +129,7 @@ { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser", + "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", "version" : "1.4.0" diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 307bf60764..87e8db6575 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -25,6 +25,7 @@ import BrowserServicesKit import Core import Subscription import TipKit +import TipKitUtils struct NetworkProtectionLocationStatusModel { enum LocationIcon { diff --git a/DuckDuckGo/TipKit/TipGroup.swift b/DuckDuckGo/TipKit/TipGroup.swift deleted file mode 100644 index 89949a17b6..0000000000 --- a/DuckDuckGo/TipKit/TipGroup.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// TipGroup.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 TipKit - -protocol TipGrouping { - @available(iOS 17.0, *) - @MainActor - var currentTip: (any Tip)? { get } -} - -// This only compiles in Xcode 16 and needs to be re-enalbed once we move to it. -// Ref: https://app.asana.com/0/414235014887631/1208528787265444/f -// -// @available(iOS 18.0, *) -// extension TipGroup: TipGrouping {} - -/// A glorified no-op to be able to compile TipGrouping in iOS versions below 17. -/// -struct EmptyTipGroup: TipGrouping { - @available(iOS 17.0, *) - var currentTip: (any Tip)? { - return nil - } -} - -/// Backport of TipKit's TipGroup to iOS 17. -/// -/// In iOS 17: this class should provide the same functionality as TipKit's `TipGroup`. -/// -@available(iOS 17.0, *) -@available(iOS, obsoleted: 18.0) -struct LegacyTipGroup: TipGrouping { - - /// This is an implementation of TipGroup.Priority for iOS versions below 18. - /// - enum Priority { - - /// Shows the first tip eligible for display. - case firstAvailable - - /// Shows an eligible tip when all of the previous tips have been [`invalidated`](doc:Tips/Status/invalidated(_:)). - case ordered - } - - private let priority: Priority - - @LegacyTipGroupBuilder - private let tipBuilder: () -> [any Tip] - - init(_ priority: Priority = .ordered, @LegacyTipGroupBuilder _ tipBuilder: @escaping () -> [any Tip]) { - - self.priority = priority - self.tipBuilder = tipBuilder - } - - @MainActor - var currentTip: (any Tip)? { - return tipBuilder().first { - switch $0.status { - case .available: - return true - case .invalidated: - return false - case .pending: - return priority == .ordered - @unknown default: - // Since this code is limited to iOS 17 and deprecated in iOS 18, we shouldn't - // need to worry about unknown cases. - fatalError("This path should never be called") - } - } - } - - @MainActor - var current: Any? { - currentTip - } -} - -@available(iOS 17.0, *) -@available(iOS, obsoleted: 18.0) -@resultBuilder public struct LegacyTipGroupBuilder { - public static func buildBlock() -> [Any] { - [] - } - - public static func buildBlock(_ components: any Tip...) -> [any Tip] { - components - } - - public static func buildPartialBlock(first: any Tip) -> [any Tip] { - [first] - } - - public static func buildPartialBlock(first: [any Tip]) -> [any Tip] { - first - } - - public static func buildPartialBlock(accumulated: [any Tip], next: any Tip) -> [any Tip] { - accumulated + [next] - } - - public static func buildPartialBlock(accumulated: [any Tip], next: [any Tip]) -> [any Tip] { - - accumulated + next - } - - public static func buildPartialBlock(first: Void) -> [any Tip] { - [] - } - - public static func buildPartialBlock(first: Never) -> [any Tip] { - // This will never be called - } - - public static func buildIf(_ element: [any Tip]?) -> [any Tip] { - element ?? [] - } - - public static func buildEither(first: [any Tip]) -> [any Tip] { - first - } - - public static func buildEither(second: [any Tip]) -> [any Tip] { - second - } - - public static func buildArray(_ components: [[any Tip]]) -> [any Tip] { - components.flatMap { $0 } - } -} diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift index 0ba13636d8..651837bd40 100644 --- a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -20,8 +20,32 @@ import Core import Foundation import os.log -import TipKit +import TipKitUtils 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(iOS 17.0, *) { + controller.configureTipKit([ + .displayFrequency(.immediate), + .datastoreLocation(.applicationDefault) + ]) + } 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..c10141baa8 --- /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 = .networkProtectionGroupDefaults) -> Self { + + self.init(logger: logger, userDefaults: userDefaults) + } +} diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift deleted file mode 100644 index 0eb656c5f1..0000000000 --- a/DuckDuckGo/TipKit/TipKitController.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// 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 Core -import Foundation -import os.log -import TipKit - -protocol TipKitControlling { - @available(iOS 17.0, *) - func configureTipKit() - - @available(iOS 17.0, *) - func resetTipKitOnNextAppLaunch() -} - -typealias TipKitAppEventHandler = TipKitController - -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 = .tipKit, - userDefaults: UserDefaults = .standard) { - - self.logger = logger - self.userDefaults = userDefaults - } - - @available(iOS 17.0, *) - func configureTipKit() { - do { - if resetTipKitOnNextLaunch { - resetTipKit() - resetTipKitOnNextLaunch = false - } - - try Tips.configure([ - .displayFrequency(.immediate), - .datastoreLocation(.applicationDefault) - ]) - - logger.debug("TipKit initialized") - } catch { - logger.error("Failed to initialize TipKit: \(error)") - } - } - - @available(iOS 17.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") - } -} - -extension TipKitController: TipKitAppEventHandling { - - func appDidFinishLaunching() { - if #available(iOS 17.0, *) { - configureTipKit() - } else { - logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") - } - } -} diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift index a969252f5a..5af57fe57f 100644 --- a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -19,6 +19,7 @@ import Foundation import os.log +import TipKitUtils protocol TipKitDebugOptionsUIActionHandling { /// Resets TipKit @@ -30,10 +31,10 @@ struct TipKitDebugOptionsUIActionHandler: TipKitDebugOptionsUIActionHandling { private let controller: TipKitController private let logger: Logger - init(controller: TipKitController? = nil, + init(controller: TipKitController = .make(), logger: Logger = .tipKit) { - self.controller = controller ?? TipKitController(logger: logger) + self.controller = controller self.logger = logger } From 5965eade81772973ee03c668eb5b178391660ce1 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 16 Oct 2024 10:48:16 +0200 Subject: [PATCH 24/26] Updated the VPN tip icons --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++++ .../xcshareddata/swiftpm/Package.resolved | 8 ++++++++ DuckDuckGo/VPN.xcassets/Contents.json | 6 ++++++ .../VPNAddWidgetTipIcon.imageset/Contents.json | 12 ++++++++++++ .../Widget-Add-32.pdf | Bin 0 -> 1763 bytes .../Contents.json | 12 ++++++++++++ .../Map-Pin-32.pdf | Bin 0 -> 2190 bytes .../VPNUseSnoozeTipIcon.imageset/Contents.json | 12 ++++++++++++ .../VPNUseSnoozeTipIcon.imageset/Moon-32.pdf | Bin 0 -> 1407 bytes DuckDuckGo/VPNAddWidgetTip.swift | 2 +- DuckDuckGo/VPNChangeLocationTip.swift | 2 +- DuckDuckGo/VPNUseSnoozeTip.swift | 2 +- 12 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 DuckDuckGo/VPN.xcassets/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32.pdf create mode 100644 DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Map-Pin-32.pdf create mode 100644 DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json create mode 100644 DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-32.pdf diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a5b3eb2b23..7800e91a85 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -363,6 +363,7 @@ 7B94A5102CBE9DEB0083AB69 /* TipKitUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 7B94A50F2CBE9DEB0083AB69 /* TipKitUtils */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; + 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */; }; 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; @@ -1651,6 +1652,7 @@ 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; + 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = VPN.xcassets; sourceTree = ""; }; 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; @@ -3746,6 +3748,7 @@ children = ( 4B37E04F2B928CA6009E81CA /* vpn-light-mode.json */, 4B6ED9442B992FE4007F5CAA /* vpn-dark-mode.json */, + 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */, ); name = Resources; sourceTree = ""; @@ -6994,6 +6997,7 @@ 85F98F98296F4CB100742F4A /* SyncAssets.xcassets in Resources */, 31BC5F412C2B0B540004DF37 /* DuckPlayer.xcassets in Resources */, AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, + 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, D65CEA702B6AC6C9008A759B /* Subscription.xcassets in Resources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5efd617471..0bbd07fceb 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" : "970af02afd67ac8cbd37c21196569a7af610a081" + } + }, { "identity" : "content-scope-scripts", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/VPN.xcassets/Contents.json b/DuckDuckGo/VPN.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..a6eeb0d6e7 --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Widget-Add-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32.pdf b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32.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/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..fd80834e95 --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Map-Pin-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Map-Pin-32.pdf b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Map-Pin-32.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/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..4b8e1d769c --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Moon-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-32.pdf b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-32.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/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index 459f51803a..3d668bea87 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -50,7 +50,7 @@ extension VPNAddWidgetTip: Tip { } var image: Image? { - Image(systemName: "rectangle.and.hand.point.up.left.fill") + Image(.vpnAddWidgetTipIcon) } var actions: [Action] { diff --git a/DuckDuckGo/VPNChangeLocationTip.swift b/DuckDuckGo/VPNChangeLocationTip.swift index 101453e574..7bc8d297dd 100644 --- a/DuckDuckGo/VPNChangeLocationTip.swift +++ b/DuckDuckGo/VPNChangeLocationTip.swift @@ -41,7 +41,7 @@ extension VPNChangeLocationTip: Tip { } var image: Image? { - Image(systemName: "globe.americas.fill") + Image(.vpnChangeLocationTipIcon) } var rules: [Rule] { diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNUseSnoozeTip.swift index a97e397705..b1cafc70b6 100644 --- a/DuckDuckGo/VPNUseSnoozeTip.swift +++ b/DuckDuckGo/VPNUseSnoozeTip.swift @@ -48,7 +48,7 @@ extension VPNUseSnoozeTip: Tip { } var image: Image? { - Image(systemName: "powersleep") + Image(.vpnUseSnoozeTipIcon) } var actions: [Action] { From e8a3a96e4bc278fa587a7e3df71639030abe3a29 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 16 Oct 2024 17:02:11 +0200 Subject: [PATCH 25/26] Standardized some names and identifiers --- DuckDuckGo.xcodeproj/project.pbxproj | 16 ++++++++-------- DuckDuckGo/NetworkProtectionStatusView.swift | 10 +++++----- .../NetworkProtectionStatusViewModel.swift | 16 +++++++++------- ...ipKitController+ConvenienceInitializers.swift | 2 +- DuckDuckGo/VPNAddWidgetTip.swift | 6 +++--- ...ocationTip.swift => VPNGeoswitchingTip.swift} | 10 +++++----- ...{VPNUseSnoozeTip.swift => VPNSnoozeTip.swift} | 10 +++++----- 7 files changed, 36 insertions(+), 34 deletions(-) rename DuckDuckGo/{VPNChangeLocationTip.swift => VPNGeoswitchingTip.swift} (86%) rename DuckDuckGo/{VPNUseSnoozeTip.swift => VPNSnoozeTip.swift} (88%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7800e91a85..40eb2d1e02 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -366,8 +366,8 @@ 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */; }; 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; - 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */; }; - 7BFD5FD92C9DBC24000FF959 /* VPNUseSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */; }; + 7BFD5FD72C9DB9D7000FF959 /* VPNGeoswitchingTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */; }; + 7BFD5FD92C9DBC24000FF959 /* VPNSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E872193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift */; }; @@ -1655,8 +1655,8 @@ 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = VPN.xcassets; sourceTree = ""; }; 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; - 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNChangeLocationTip.swift; sourceTree = ""; }; - 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUseSnoozeTip.swift; sourceTree = ""; }; + 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNGeoswitchingTip.swift; sourceTree = ""; }; + 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSnoozeTip.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerBrowsingMenuExtension.swift; sourceTree = ""; }; @@ -4045,8 +4045,8 @@ isa = PBXGroup; children = ( 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */, - 7BFD5FD62C9DB9D7000FF959 /* VPNChangeLocationTip.swift */, - 7BFD5FD82C9DBC24000FF959 /* VPNUseSnoozeTip.swift */, + 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */, + 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */, ); name = TipKit; sourceTree = ""; @@ -7385,7 +7385,7 @@ 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, - 7BFD5FD72C9DB9D7000FF959 /* VPNChangeLocationTip.swift in Sources */, + 7BFD5FD72C9DB9D7000FF959 /* VPNGeoswitchingTip.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, @@ -7458,7 +7458,7 @@ 9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */, 310D091B2799F54900DC0060 /* DownloadManager.swift in Sources */, 98D98A7425ED88D100D8E3DF /* BrowsingMenuEntryViewCell.swift in Sources */, - 7BFD5FD92C9DBC24000FF959 /* VPNUseSnoozeTip.swift in Sources */, + 7BFD5FD92C9DBC24000FF959 /* VPNSnoozeTip.swift in Sources */, 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */, 4B2C79612C5B27AC00A240CC /* VPNSnoozeActivityAttributes.swift in Sources */, CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 202cd2d765..942012373f 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -207,7 +207,7 @@ struct NetworkProtectionStatusView: View { Section { if #available(iOS 17.0, *) { - geolocationTip() + geoswitchingTip() } } .listRowBackground(Color(designSystemColor: .surface)) @@ -301,11 +301,11 @@ struct NetworkProtectionStatusView: View { @available(iOS 17.0, *) @ViewBuilder - private func geolocationTip() -> some View { + private func geoswitchingTip() -> some View { if statusModel.canShowTips, - let geolocationTip = statusModel.vpnEnabledTips.currentTip as? VPNChangeLocationTip { + let tip = statusModel.vpnEnabledTips.currentTip as? VPNGeoswitchingTip { - TipView(geolocationTip) + TipView(tip) .removeGroupedListStyleInsets() .tipCornerRadius(0) .tipBackground(Color(designSystemColor: .surface)) @@ -317,7 +317,7 @@ struct NetworkProtectionStatusView: View { private func snoozeTip() -> some View { if statusModel.canShowTips, statusModel.hasServerInfo, - let tip = statusModel.vpnEnabledTips.currentTip as? VPNUseSnoozeTip { + let tip = statusModel.vpnEnabledTips.currentTip as? VPNSnoozeTip { TipView(tip, action: statusModel.snoozeActionHandler(action:)) .removeGroupedListStyleInsets() diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 87e8db6575..9a5da6e7a0 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -109,6 +109,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject { featureFlagger.isFeatureOn(.networkProtectionUserTips) } + /// Whether the "Add Widget" education sheet should be presented to the user. + /// @Published var showAddWidgetEducationView: Bool = false @@ -124,8 +126,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // } if #available(iOS 17.0, *) { return LegacyTipGroup(.ordered) { - VPNChangeLocationTip() - VPNUseSnoozeTip() + VPNGeoswitchingTip() + VPNSnoozeTip() VPNAddWidgetTip() } } else { @@ -158,10 +160,10 @@ final class NetworkProtectionStatusViewModel: ObservableObject { didSet { if #available(iOS 17.0, *) { if isNetPEnabled { - VPNChangeLocationTip.donateVPNConnectedEvent() + VPNGeoswitchingTip.donateVPNConnectedEvent() } - VPNUseSnoozeTip.vpnEnabled = isNetPEnabled + VPNSnoozeTip.vpnEnabled = isNetPEnabled VPNAddWidgetTip.vpnEnabled = isNetPEnabled } } @@ -498,7 +500,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { } if #available(iOS 17.0, *) { - (vpnEnabledTips.currentTip as? VPNUseSnoozeTip)?.invalidate(reason: .actionPerformed) + (vpnEnabledTips.currentTip as? VPNSnoozeTip)?.invalidate(reason: .actionPerformed) } let defaultDuration: TimeInterval = .minutes(20) @@ -599,7 +601,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @available(iOS 17.0, *) func snoozeActionHandler(action: Tips.Action) { - if action.id == VPNUseSnoozeTip.ActionIdentifiers.learnMore.rawValue { + if action.id == VPNSnoozeTip.ActionIdentifiers.learnMore.rawValue { let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! UIApplication.shared.open(url, options: [:], completionHandler: nil) } @@ -619,7 +621,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { func handleUserOpenedVPNLocations() { if #available(iOS 17.0, *) { Task { @MainActor in - (vpnEnabledTips.currentTip as? VPNChangeLocationTip)?.invalidate(reason: .actionPerformed) + (vpnEnabledTips.currentTip as? VPNGeoswitchingTip)?.invalidate(reason: .actionPerformed) } } } diff --git a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift index c10141baa8..4e3d05a987 100644 --- a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift +++ b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift @@ -24,7 +24,7 @@ import os extension TipKitController { static func make(logger: Logger = .tipKit, - userDefaults: UserDefaults = .networkProtectionGroupDefaults) -> Self { + userDefaults: UserDefaults = .standard) -> Self { self.init(logger: logger, userDefaults: userDefaults) } diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift index 3d668bea87..b36ace1c58 100644 --- a/DuckDuckGo/VPNAddWidgetTip.swift +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -29,16 +29,16 @@ struct VPNAddWidgetTip {} extension VPNAddWidgetTip: Tip { enum ActionIdentifiers: String { - case addWidget = "com.duckduckgo.tipkit.VPNAddWidgetTip.addWidget" + case addWidget = "com.duckduckgo.vpn.tip.addWidget.action.addWidget" } @Parameter(.transient) static var vpnEnabled: Bool = false - private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNAddWidgetTip.vpnDisconnectedEvent") + private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.vpnDisconnectedEvent") var id: String { - "com.duckduckgo.tipkit.VPNAddWidgetTip" + "com.duckduckgo.vpn.tip.addWidget" } var title: Text { diff --git a/DuckDuckGo/VPNChangeLocationTip.swift b/DuckDuckGo/VPNGeoswitchingTip.swift similarity index 86% rename from DuckDuckGo/VPNChangeLocationTip.swift rename to DuckDuckGo/VPNGeoswitchingTip.swift index 7bc8d297dd..90cea0d62f 100644 --- a/DuckDuckGo/VPNChangeLocationTip.swift +++ b/DuckDuckGo/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(iOS 17.0, *) -extension VPNChangeLocationTip: Tip { +extension VPNGeoswitchingTip: Tip { - private static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.tipkit.VPNChangeLocationTip.vpnConnectedEvent") + private 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 { diff --git a/DuckDuckGo/VPNUseSnoozeTip.swift b/DuckDuckGo/VPNSnoozeTip.swift similarity index 88% rename from DuckDuckGo/VPNUseSnoozeTip.swift rename to DuckDuckGo/VPNSnoozeTip.swift index b1cafc70b6..7373c94d2c 100644 --- a/DuckDuckGo/VPNUseSnoozeTip.swift +++ b/DuckDuckGo/VPNSnoozeTip.swift @@ -1,5 +1,5 @@ // -// VPNUseSnoozeTip.swift +// VPNSnoozeTip.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -21,22 +21,22 @@ import TipKit /// A tip to suggest to the user to use the snooze feature to momentarily disable the VPN /// -struct VPNUseSnoozeTip {} +struct VPNSnoozeTip {} /// Necessary split to support older iOS versions. /// @available(iOS 17.0, *) -extension VPNUseSnoozeTip: Tip { +extension VPNSnoozeTip: Tip { enum ActionIdentifiers: String { - case learnMore = "com.duckduckgo.tipkit.VPNUseSnoozeTip.learnMoreId" + case learnMore = "com.duckduckgo.vpn.tip.snooze.learnMoreId" } @Parameter(.transient) static var vpnEnabled: Bool = false var id: String { - "com.duckduckgo.tipkit.VPNUseSnoozeTip" + "com.duckduckgo.vpn.tip.snooze" } var title: Text { From d6dfb0d477dc641cf82c8f70af2abb49e333c7d9 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 17 Oct 2024 14:32:00 +0200 Subject: [PATCH 26/26] Updates BSK --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 40eb2d1e02..eea89a79a3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10998,7 +10998,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = revision; - revision = 970af02afd67ac8cbd37c21196569a7af610a081; + revision = c05ef03b3320ccecdace3b1d53d945313585c170; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0bbd07fceb..d0854d58f5 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,7 +32,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "970af02afd67ac8cbd37c21196569a7af610a081" + "revision" : "c05ef03b3320ccecdace3b1d53d945313585c170" } }, {