From 21a255be7e2aee0827098b7ca1fe521b4c356ca1 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Thu, 19 Oct 2023 15:21:44 +0200 Subject: [PATCH 01/23] Add NotificationsView with just the settings link --- DuckDuckGo.xcodeproj/project.pbxproj | 16 ++++++++ DuckDuckGo/NetworkProtectionStatusView.swift | 2 +- ...etworkProtectionVPNNotificationsView.swift | 41 +++++++++++++++++++ DuckDuckGo/UIApplicationExtension.swift | 41 +++++++++++++++++++ DuckDuckGo/UserText.swift | 1 + DuckDuckGo/en.lproj/Localizable.strings | 3 ++ 6 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 DuckDuckGo/NetworkProtectionVPNNotificationsView.swift create mode 100644 DuckDuckGo/UIApplicationExtension.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cd69e916e0..61886f9bda 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -765,6 +765,8 @@ EE8594992A44791C008A6D06 /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8594982A44791C008A6D06 /* NetworkProtectionTunnelController.swift */; }; EE8E568A2A56BCE400F11DCA /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE8E56892A56BCE400F11DCA /* NetworkProtection */; }; EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */; }; + EE9D68D52AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D42AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift */; }; + EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */; }; EEDFE2DA2AC6ED4F00F0E19C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EEDFE2DC2AC6ED4F00F0E19C /* Localizable.strings */; }; EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */; }; EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF0F8CB2ABC832200630031 /* NetworkProtectionDebugFeatures.swift */; }; @@ -2334,6 +2336,8 @@ EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNotificationIdentifier.swift; sourceTree = ""; }; EE8594982A44791C008A6D06 /* NetworkProtectionTunnelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionTunnelController.swift; sourceTree = ""; }; EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNSettingsView.swift; sourceTree = ""; }; + EE9D68D42AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNNotificationsView.swift; sourceTree = ""; }; + EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = ""; }; EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Configuration-Alpha.xcconfig"; path = "Configuration/Configuration-Alpha.xcconfig"; sourceTree = ""; }; EEDFE2DB2AC6ED4F00F0E19C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; EEDFE2DD2AC6ED5B00F0E19C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; @@ -4423,9 +4427,18 @@ name = VPNSettings; sourceTree = ""; }; + EE9D68D62AE1527F00B55EF4 /* VPNNotifications */ = { + isa = PBXGroup; + children = ( + EE9D68D42AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift */, + ); + name = VPNNotifications; + sourceTree = ""; + }; EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + EE9D68D62AE1527F00B55EF4 /* VPNNotifications */, EE9D68CF2AE00CE000B55EF4 /* VPNSettings */, EE458D122ABB651500FC651A /* Debug */, EE0153E22A6FE031002A8B26 /* Root */, @@ -5108,6 +5121,7 @@ F1DE78591E5CD2A70058895A /* UIViewExtension.swift */, F1F5337B1F26A9EF00D80D4F /* UserText.swift */, 986DA94924884B18004A7E39 /* WebViewTransition.swift */, + EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */, ); name = UserInterface; sourceTree = ""; @@ -6135,6 +6149,7 @@ 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, + EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */, F13B4BD31F1822C700814661 /* Tab.swift in Sources */, F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */, 1EE52ABB28FB1D6300B750C1 /* UIImageExtension.swift in Sources */, @@ -6156,6 +6171,7 @@ C1B7B529289420830098FD6A /* RemoteMessaging.xcdatamodeld in Sources */, 986B16C425E92DF0007D23E8 /* BrowsingMenuViewController.swift in Sources */, 988AC355257E47C100793C64 /* RequeryLogic.swift in Sources */, + EE9D68D52AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift in Sources */, 1E4F4A5A297193DE00625985 /* MainViewController+CookiesManaged.swift in Sources */, 8586A10D24CBA7070049720E /* FindInPageActivity.swift in Sources */, 1E1626072968413B0004127F /* ViewExtension.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index c7b1e13dcf..62435739f8 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -134,7 +134,7 @@ struct NetworkProtectionStatusView: View { NavigationLink(UserText.netPVPNSettingsTitle, destination: NetworkProtectionVPNSettingsView()) .font(.system(size: 16)) .foregroundColor(.textPrimary) - NavigationLink(UserText.netPVPNNotificationsTitle, destination: Text("Coming soon!")) + NavigationLink(UserText.netPVPNNotificationsTitle, destination: NetworkProtectionVPNNotificationsView()) .font(.system(size: 16)) .foregroundColor(.textPrimary) } header: { diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift new file mode 100644 index 0000000000..e53d7c0081 --- /dev/null +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -0,0 +1,41 @@ +// +// NetworkProtectionVPNNotificationsView.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if NETWORK_PROTECTION + +import SwiftUI +import UIKit + +@available(iOS 15, *) +struct NetworkProtectionVPNNotificationsView: View { + var body: some View { + List { + Button(UserText.netPTurnOnNotificationsButtonTitle) { + Task { + await UIApplication.shared.openAppNotificationSettings() + } + } + .foregroundColor(Color(designSystemColor: .accent)) + } + .applyInsetGroupedListStyle() + .navigationTitle(UserText.netPVPNNotificationsTitle) + } +} + +#endif diff --git a/DuckDuckGo/UIApplicationExtension.swift b/DuckDuckGo/UIApplicationExtension.swift new file mode 100644 index 0000000000..c89cabd385 --- /dev/null +++ b/DuckDuckGo/UIApplicationExtension.swift @@ -0,0 +1,41 @@ +// +// UIApplicationExtension.swift +// DuckDuckGo +// +// Copyright © 2023 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 UIKit + +extension UIApplication { + private static let notificationSettingsURL: URL? = { + let settingsString: String + if #available(iOS 16, *) { + settingsString = UIApplication.openNotificationSettingsURLString + } else if #available(iOS 15.4, *) { + settingsString = UIApplicationOpenNotificationSettingsURLString + } else { + settingsString = UIApplication.openSettingsURLString + } + return URL(string: settingsString) + }() + + func openAppNotificationSettings() async -> Bool { + guard + let url = UIApplication.notificationSettingsURL, + self.canOpenURL(url) else { return false } + return await self.open(url) + } +} diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 4425118079..ad62036772 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -656,6 +656,7 @@ In addition to the details entered into this form, your app issue report will co static let netPAlwaysOnSettingFooter = NSLocalizedString("network.protection.vpn.always.on.setting.footer", value: "Automatically restore a VPN connection after interruption.", comment: "Footer text for the Always on VPN setting item.") static let netPSecureDNSSettingTitle = NSLocalizedString("network.protection.vpn.secure.dns.setting.title", value: "Secure DNS", comment: "Title for the Always on VPN setting item.") static let netPSecureDNSSettingFooter = NSLocalizedString("network.protection.vpn.secure.dns.setting.footer", value: "Network Protection prevents DNS leaks to your Internet Service Provider by routing DNS queries though the VPN tunnel to our own resolver.", comment: "Footer text for the Always on VPN setting item.") + static let netPTurnOnNotificationsButtonTitle = NSLocalizedString("network.protection.turn.on.notifications.button.title", value: "Turn on Notifications", comment: "Title for the button to link to the iOS app settings and enable notifications app-wide.") static let inviteDialogContinueButton = NSLocalizedString("invite.dialog.continue.button", value: "Continue", comment: "Continue button on an invite dialog") static let inviteDialogGetStartedButton = NSLocalizedString("invite.dialog.get.started.button", value: "Get Started", comment: "Get Started button on an invite dialog") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index c071c4409f..ac596c8e1e 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1438,6 +1438,9 @@ https://duckduckgo.com/mac"; /* Title label text for the status view when netP is disconnected */ "network.protection.status.view.title" = "Network Protection"; +/* Title for the button to link to the iOS app settings and enable notifications app-wide. */ +"network.protection.turn.on.notifications.button.title" = "Turn on Notifications"; + /* Footer text for the Always on VPN setting item. */ "network.protection.vpn.always.on.setting.footer" = "Automatically restore a VPN connection after interruption."; From 44212a0b8cab778b288a61d61e7de239946d0945 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 13:50:49 +0200 Subject: [PATCH 02/23] Add notifications authorization flow --- DuckDuckGo.xcodeproj/project.pbxproj | 8 ++ ...etworkProtectionVPNNotificationsView.swift | 28 +++++-- ...kProtectionVPNNotificationsViewModel.swift | 74 ++++++++++++++++ ...NotificationsAuthorizationController.swift | 84 +++++++++++++++++++ 4 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift create mode 100644 DuckDuckGo/NotificationsAuthorizationController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 61886f9bda..3163af8dcc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -767,6 +767,8 @@ EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */; }; EE9D68D52AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D42AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift */; }; EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */; }; + EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D92AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift */; }; + EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68DB2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift */; }; EEDFE2DA2AC6ED4F00F0E19C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EEDFE2DC2AC6ED4F00F0E19C /* Localizable.strings */; }; EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */; }; EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF0F8CB2ABC832200630031 /* NetworkProtectionDebugFeatures.swift */; }; @@ -2338,6 +2340,8 @@ EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNSettingsView.swift; sourceTree = ""; }; EE9D68D42AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNNotificationsView.swift; sourceTree = ""; }; EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = ""; }; + EE9D68D92AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNNotificationsViewModel.swift; sourceTree = ""; }; + EE9D68DB2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsAuthorizationController.swift; sourceTree = ""; }; EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Configuration-Alpha.xcconfig"; path = "Configuration/Configuration-Alpha.xcconfig"; sourceTree = ""; }; EEDFE2DB2AC6ED4F00F0E19C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; EEDFE2DD2AC6ED5B00F0E19C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; @@ -4352,6 +4356,7 @@ children = ( EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */, EE458D0C2AB1DA4600FC651A /* EventMapping+NetworkProtectionError.swift */, + EE9D68DB2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift */, ); name = Helpers; sourceTree = ""; @@ -4431,6 +4436,7 @@ isa = PBXGroup; children = ( EE9D68D42AE1526600B55EF4 /* NetworkProtectionVPNNotificationsView.swift */, + EE9D68D92AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift */, ); name = VPNNotifications; sourceTree = ""; @@ -6128,6 +6134,7 @@ F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, B6BA95C328891E33004ABA20 /* BrowsingMenuAnimator.swift in Sources */, + EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, @@ -6456,6 +6463,7 @@ 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, C1B7B52428941F2A0098FD6A /* RemoteMessageRequest.swift in Sources */, + EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, C1B0F6422AB08BE9001EAF05 /* MockPrivacyConfiguration.swift in Sources */, 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index e53d7c0081..0595c7bfcf 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -24,17 +24,33 @@ import UIKit @available(iOS 15, *) struct NetworkProtectionVPNNotificationsView: View { + let model = NetworkProtectionVPNNotificationsViewModel(notificationsPermissions: AppNotificationsPermissions()) + var body: some View { List { - Button(UserText.netPTurnOnNotificationsButtonTitle) { - Task { - await UIApplication.shared.openAppNotificationSettings() - } + switch model.viewKind { + case .loading: + EmptyView() + case .unauthorized: + unauthorizedView + case .authorized: + Text("Authorized") } - .foregroundColor(Color(designSystemColor: .accent)) } .applyInsetGroupedListStyle() - .navigationTitle(UserText.netPVPNNotificationsTitle) + .navigationTitle(UserText.netPVPNNotificationsTitle).onAppear { + Task { + await model.onViewAppeared() + } + } + } + + @ViewBuilder + private var unauthorizedView: some View { + Button(UserText.netPTurnOnNotificationsButtonTitle) { + model.turnOnNotifications() + } + .foregroundColor(Color(designSystemColor: .accent)) } } diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift new file mode 100644 index 0000000000..24dd9a4f58 --- /dev/null +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift @@ -0,0 +1,74 @@ +// +// NetworkProtectionVPNNotificationsViewModel.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if NETWORK_PROTECTION + +import Combine +import UserNotifications + +enum NetworkProtectionNotificationsViewKind { + case loading + case unauthorized + case authorized +} + +final class NetworkProtectionVPNNotificationsViewModel: ObservableObject { + private let notificationsAuthorization: NotificationsAuthorizationControlling + @Published var viewKind: NetworkProtectionNotificationsViewKind = .loading + + init(notificationsPermissions: NotificationsAuthorizationControlling) { + self.notificationsAuthorization = notificationsPermissions + } + + @MainActor + func onViewAppeared() async { + let status = await notificationsAuthorization.authorizationStatus + switch status { + case .notDetermined, .denied: + viewKind = .unauthorized + case .authorized, .ephemeral, .provisional: + viewKind = .authorized + @unknown default: + assertionFailure("Unhandled enum case") + } + } + + func turnOnNotifications() { + notificationsAuthorization.requestAlertAuthorization() + } + + private func updateViewKind(for authorizationStatus: UNAuthorizationStatus) { + switch authorizationStatus { + case .notDetermined, .denied: + viewKind = .unauthorized + case .authorized, .ephemeral, .provisional: + viewKind = .authorized + @unknown default: + assertionFailure("Unhandled enum case") + } + } +} + +extension NetworkProtectionVPNNotificationsViewModel: NotificationsPermissionsControllerDelegate { + func authorizationStateDidChange(toStatus status: UNAuthorizationStatus) { + updateViewKind(for: status) + } +} + +#endif diff --git a/DuckDuckGo/NotificationsAuthorizationController.swift b/DuckDuckGo/NotificationsAuthorizationController.swift new file mode 100644 index 0000000000..8bee591bf5 --- /dev/null +++ b/DuckDuckGo/NotificationsAuthorizationController.swift @@ -0,0 +1,84 @@ +// +// AppNotificationsPermissions.swift +// DuckDuckGo +// +// Copyright © 2023 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 UserNotifications +import UIKit +import Combine + +protocol NotificationsAuthorizationControlling { + var authorizationStatus: UNAuthorizationStatus { get async } + var delegate: NotificationsPermissionsControllerDelegate? { get set } + + func requestAlertAuthorization() +} + +protocol NotificationsPermissionsControllerDelegate: AnyObject { + func authorizationStateDidChange(toStatus status: UNAuthorizationStatus) +} + +final class NotificationsAuthorizationController: NotificationsAuthorizationControlling { + + weak var delegate: NotificationsPermissionsControllerDelegate? + weak var notificationCancellable: AnyCancellable? + + var authorizationStatus: UNAuthorizationStatus { + get async { + let settings: UNNotificationSettings = await UNUserNotificationCenter.current().notificationSettings() + return settings.authorizationStatus + } + } + + init() { + notificationCancellable = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) + .sink { _ in + Task { [weak self] in + await self?.updateDelegateWithNewState() + } + } + } + + func requestAlertAuthorization() { + Task { + switch await authorizationStatus { + case .notDetermined: + await requestAuthorization(options: .alert) + case .denied: + _ = await UIApplication.shared.openAppNotificationSettings() + case .authorized, .provisional, .ephemeral: + break + @unknown default: + break + } + } + } + + private func requestAuthorization(options: UNAuthorizationOptions) async { + do { + let authorized = try await UNUserNotificationCenter.current().requestAuthorization(options: options) + if authorized { + await updateDelegateWithNewState() + } + } catch { } + } + + private func updateDelegateWithNewState() async { + let newState = await authorizationStatus + delegate?.authorizationStateDidChange(toStatus: newState) + } +} From 1ed58784faf5458e090f7fd84d0cac55cdbd9cd1 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 13:58:18 +0200 Subject: [PATCH 03/23] Fix init typo --- DuckDuckGo/NetworkProtectionVPNNotificationsView.swift | 2 +- DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index 0595c7bfcf..31266c2bd6 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -24,7 +24,7 @@ import UIKit @available(iOS 15, *) struct NetworkProtectionVPNNotificationsView: View { - let model = NetworkProtectionVPNNotificationsViewModel(notificationsPermissions: AppNotificationsPermissions()) + let model = NetworkProtectionVPNNotificationsViewModel(notificationsPermissions: NotificationsAuthorizationController()) var body: some View { List { diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift index 24dd9a4f58..2eb54c1764 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift @@ -32,8 +32,8 @@ final class NetworkProtectionVPNNotificationsViewModel: ObservableObject { private let notificationsAuthorization: NotificationsAuthorizationControlling @Published var viewKind: NetworkProtectionNotificationsViewKind = .loading - init(notificationsPermissions: NotificationsAuthorizationControlling) { - self.notificationsAuthorization = notificationsPermissions + init(notificationsAuthorization: NotificationsAuthorizationControlling) { + self.notificationsAuthorization = notificationsAuthorization } @MainActor From b8565bf10901e1b26f3916355e29d313a8b82f87 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 13:58:43 +0200 Subject: [PATCH 04/23] Point to dev BSK commit --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3163af8dcc..d11db0900f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9001,8 +9001,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 81.4.0; + kind = revision; + revision = ddd47b1ba026bca6a0512471ce4bb0aa6989da27; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c4f9329696..cc02b3c6de 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "4cf8e857cd78e15c64ba37839634970fc675947c", - "version": "81.4.0" + "revision": "ddd47b1ba026bca6a0512471ce4bb0aa6989da27", + "version": null } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/duckduckgo/content-scope-scripts", "state": { "branch": null, - "revision": "aa279a3b006a0b1e009707311283c7fcaed24fb7", - "version": "4.39.0" + "revision": "254b23cf292140498650421bb31fd05740f4579b", + "version": "4.40.0" } }, { From 1454f88a0f7c2807f1372f5c4b2db75f698b9834 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:16:10 +0200 Subject: [PATCH 05/23] Fix header --- DuckDuckGo/NotificationsAuthorizationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/NotificationsAuthorizationController.swift b/DuckDuckGo/NotificationsAuthorizationController.swift index 8bee591bf5..c1166c9470 100644 --- a/DuckDuckGo/NotificationsAuthorizationController.swift +++ b/DuckDuckGo/NotificationsAuthorizationController.swift @@ -1,5 +1,5 @@ // -// AppNotificationsPermissions.swift +// NotificationsAuthorizationController.swift // DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. From e6653dd4a07cda5b13af9b15557b5ae71de107fc Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:16:28 +0200 Subject: [PATCH 06/23] Add NetP User defaults --- Core/UserDefaults+NetworkProtection.swift | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Core/UserDefaults+NetworkProtection.swift diff --git a/Core/UserDefaults+NetworkProtection.swift b/Core/UserDefaults+NetworkProtection.swift new file mode 100644 index 0000000000..fb9def1004 --- /dev/null +++ b/Core/UserDefaults+NetworkProtection.swift @@ -0,0 +1,34 @@ +// +// UserDefaults+NetworkProtection.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if NETWORK_PROTECTION + +import Foundation + +public extension UserDefaults { + static var networkProtectionGroupDefaults: UserDefaults { + let suiteName = "\(Global.groupIdPrefix).netp" + guard let defaults = UserDefaults(suiteName: suiteName) else { + fatalError("Failed to create netP UserDefaults") + } + return defaults + } +} + +#endif From 0967bd2b5b41ccf8bb3ee07ad76c61e59eb97d50 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:16:55 +0200 Subject: [PATCH 07/23] NetP US project changes --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d11db0900f..770396c26f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -769,6 +769,7 @@ EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */; }; EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68D92AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift */; }; EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68DB2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift */; }; + EE9D68DE2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D68DD2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift */; }; EEDFE2DA2AC6ED4F00F0E19C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EEDFE2DC2AC6ED4F00F0E19C /* Localizable.strings */; }; EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */; }; EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF0F8CB2ABC832200630031 /* NetworkProtectionDebugFeatures.swift */; }; @@ -2342,6 +2343,7 @@ EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = ""; }; EE9D68D92AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNNotificationsViewModel.swift; sourceTree = ""; }; EE9D68DB2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsAuthorizationController.swift; sourceTree = ""; }; + EE9D68DD2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtection.swift"; sourceTree = ""; }; EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Configuration-Alpha.xcconfig"; path = "Configuration/Configuration-Alpha.xcconfig"; sourceTree = ""; }; EEDFE2DB2AC6ED4F00F0E19C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; EEDFE2DD2AC6ED5B00F0E19C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; @@ -4420,6 +4422,7 @@ isa = PBXGroup; children = ( EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */, + EE9D68DD2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift */, ); name = NetworkProtection; sourceTree = ""; @@ -6723,6 +6726,7 @@ CB2A7EF4285383B300885F67 /* AppLastCompiledRulesStore.swift in Sources */, 4B75EA9226A266CB00018634 /* PrintingUserScript.swift in Sources */, 37445F972A155F7C0029F789 /* SyncDataProviders.swift in Sources */, + EE9D68DE2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift in Sources */, CB258D1F29A52B2500DEBA24 /* Configuration.swift in Sources */, 9847C00027A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift in Sources */, F143C3281E4A9A0E00CFDE3A /* StringExtension.swift in Sources */, From 57f896ee25bce0f5019edd316ca74f96e3ac5650 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:18:28 +0200 Subject: [PATCH 08/23] Inject notificationsPresenter w/ NetP defaults --- .../NetworkProtectionPacketTunnelProvider.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index ccb15449ae..4e9fe535ae 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -167,8 +167,13 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { errorEvents: nil) let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() + let notificationsSettingsStore = NetworkProtectionNotificationsSettingsUserDefaultsStore(userDefaults: .networkProtectionGroupDefaults) + let nofificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( + notificationSettingsStore: notificationsSettingsStore, + wrappee: notificationsPresenter + ) notificationsPresenter.requestAuthorization() - super.init(notificationsPresenter: notificationsPresenter, + super.init(notificationsPresenter: nofificationsPresenterDecorator, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, keychainType: .dataProtection(.unspecified), From 778204e22d890f5af9f84537caa6a10855faf20f Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:19:21 +0200 Subject: [PATCH 09/23] Add authorized view with notifications toggling --- ...etworkProtectionVPNNotificationsView.swift | 35 +++++++++++++++++-- ...kProtectionVPNNotificationsViewModel.swift | 27 ++++++++------ DuckDuckGo/UserText.swift | 2 ++ DuckDuckGo/en.lproj/Localizable.strings | 6 ++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index 31266c2bd6..82147b75c2 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -21,10 +21,15 @@ import SwiftUI import UIKit +import NetworkProtection +import Core @available(iOS 15, *) struct NetworkProtectionVPNNotificationsView: View { - let model = NetworkProtectionVPNNotificationsViewModel(notificationsPermissions: NotificationsAuthorizationController()) + @ObservedObject var model = NetworkProtectionVPNNotificationsViewModel( + notificationsAuthorization: NotificationsAuthorizationController(), + notificationsSettingsStore: NetworkProtectionNotificationsSettingsUserDefaultsStore(userDefaults: .networkProtectionGroupDefaults) + ) var body: some View { List { @@ -34,7 +39,7 @@ struct NetworkProtectionVPNNotificationsView: View { case .unauthorized: unauthorizedView case .authorized: - Text("Authorized") + authorizedView } } .applyInsetGroupedListStyle() @@ -50,8 +55,32 @@ struct NetworkProtectionVPNNotificationsView: View { Button(UserText.netPTurnOnNotificationsButtonTitle) { model.turnOnNotifications() } - .foregroundColor(Color(designSystemColor: .accent)) + .foregroundColor(.controlColor) } + + @ViewBuilder + private var authorizedView: some View { + Section { + Toggle(UserText.netPVPNAlertsToggleTitle, isOn: Binding( + get: { model.alertsEnabled }, + set: model.didToggleAlerts(to:) + )) + .toggleStyle(SwitchToggleStyle(tint: Color(designSystemColor: .accent))) + } footer: { + Text(UserText.netPVPNAlertsToggleSectionFooter) + .foregroundColor(.textSecondary) + .accentColor(.controlColor) + .font(.system(size: 13)) + .padding(.top, 6) + } + } +} + +private extension Color { + static let textPrimary = Color(designSystemColor: .textPrimary) + static let textSecondary = Color(designSystemColor: .textSecondary) + static let cellBackground = Color(designSystemColor: .surface) + static let controlColor = Color(designSystemColor: .accent) } #endif diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift index 2eb54c1764..66f3e623a5 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift @@ -21,6 +21,7 @@ import Combine import UserNotifications +import NetworkProtection enum NetworkProtectionNotificationsViewKind { case loading @@ -29,30 +30,35 @@ enum NetworkProtectionNotificationsViewKind { } final class NetworkProtectionVPNNotificationsViewModel: ObservableObject { - private let notificationsAuthorization: NotificationsAuthorizationControlling + private var notificationsAuthorization: NotificationsAuthorizationControlling + private var notificationsSettingsStore: NetworkProtectionNotificationsSettingsStore @Published var viewKind: NetworkProtectionNotificationsViewKind = .loading + var alertsEnabled: Bool { + self.notificationsSettingsStore.alertsEnabled + } - init(notificationsAuthorization: NotificationsAuthorizationControlling) { + init(notificationsAuthorization: NotificationsAuthorizationControlling, + notificationsSettingsStore: NetworkProtectionNotificationsSettingsStore) { self.notificationsAuthorization = notificationsAuthorization + self.notificationsSettingsStore = notificationsSettingsStore + self.notificationsAuthorization.delegate = self } @MainActor func onViewAppeared() async { let status = await notificationsAuthorization.authorizationStatus - switch status { - case .notDetermined, .denied: - viewKind = .unauthorized - case .authorized, .ephemeral, .provisional: - viewKind = .authorized - @unknown default: - assertionFailure("Unhandled enum case") - } + updateViewKind(for: status) } func turnOnNotifications() { notificationsAuthorization.requestAlertAuthorization() } + func didToggleAlerts(to enabled: Bool) { + notificationsSettingsStore.alertsEnabled = enabled + } + + @MainActor private func updateViewKind(for authorizationStatus: UNAuthorizationStatus) { switch authorizationStatus { case .notDetermined, .denied: @@ -66,6 +72,7 @@ final class NetworkProtectionVPNNotificationsViewModel: ObservableObject { } extension NetworkProtectionVPNNotificationsViewModel: NotificationsPermissionsControllerDelegate { + @MainActor func authorizationStateDidChange(toStatus status: UNAuthorizationStatus) { updateViewKind(for: status) } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index ad62036772..32b10f98e2 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -657,6 +657,8 @@ In addition to the details entered into this form, your app issue report will co static let netPSecureDNSSettingTitle = NSLocalizedString("network.protection.vpn.secure.dns.setting.title", value: "Secure DNS", comment: "Title for the Always on VPN setting item.") static let netPSecureDNSSettingFooter = NSLocalizedString("network.protection.vpn.secure.dns.setting.footer", value: "Network Protection prevents DNS leaks to your Internet Service Provider by routing DNS queries though the VPN tunnel to our own resolver.", comment: "Footer text for the Always on VPN setting item.") static let netPTurnOnNotificationsButtonTitle = NSLocalizedString("network.protection.turn.on.notifications.button.title", value: "Turn on Notifications", comment: "Title for the button to link to the iOS app settings and enable notifications app-wide.") + static let netPVPNAlertsToggleTitle = NSLocalizedString("network.protection.vpn.alerts.toggle.title", value: "VPN Alerts", comment: "Title for the toggle for VPN alerts.") + static let netPVPNAlertsToggleSectionFooter = NSLocalizedString("network.protection.vpn.alerts.toggle.section.footer", value: "Get notified if your connection drops or VPN status changes.", comment: "List section footer for the toggle for VPN alerts.") static let inviteDialogContinueButton = NSLocalizedString("invite.dialog.continue.button", value: "Continue", comment: "Continue button on an invite dialog") static let inviteDialogGetStartedButton = NSLocalizedString("invite.dialog.get.started.button", value: "Get Started", comment: "Get Started button on an invite dialog") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index ac596c8e1e..097bae8235 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1441,6 +1441,12 @@ https://duckduckgo.com/mac"; /* Title for the button to link to the iOS app settings and enable notifications app-wide. */ "network.protection.turn.on.notifications.button.title" = "Turn on Notifications"; +/* List section footer for the toggle for VPN alerts. */ +"network.protection.vpn.alerts.toggle.section.footer" = "Get notified if your connection drops or VPN status changes."; + +/* Title for the toggle for VPN alerts. */ +"network.protection.vpn.alerts.toggle.title" = "VPN Alerts"; + /* Footer text for the Always on VPN setting item. */ "network.protection.vpn.always.on.setting.footer" = "Automatically restore a VPN connection after interruption."; From 062ddc6cd6304d6f1d022a41a0301228b0cf0b80 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:42:21 +0200 Subject: [PATCH 10/23] Add animation --- DuckDuckGo/NetworkProtectionVPNNotificationsView.swift | 1 + DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index 82147b75c2..4243659486 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -42,6 +42,7 @@ struct NetworkProtectionVPNNotificationsView: View { authorizedView } } + .animation(.default, value: model.viewKind) .applyInsetGroupedListStyle() .navigationTitle(UserText.netPVPNNotificationsTitle).onAppear { Task { diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift index 66f3e623a5..f1301e17ea 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift @@ -23,7 +23,7 @@ import Combine import UserNotifications import NetworkProtection -enum NetworkProtectionNotificationsViewKind { +enum NetworkProtectionNotificationsViewKind: Equatable { case loading case unauthorized case authorized From 6280ca1920edec515242997d690df7bb8dfd4539 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:42:45 +0200 Subject: [PATCH 11/23] Fix issue with delegation --- DuckDuckGo/NotificationsAuthorizationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/NotificationsAuthorizationController.swift b/DuckDuckGo/NotificationsAuthorizationController.swift index c1166c9470..91fb8ff225 100644 --- a/DuckDuckGo/NotificationsAuthorizationController.swift +++ b/DuckDuckGo/NotificationsAuthorizationController.swift @@ -35,7 +35,7 @@ protocol NotificationsPermissionsControllerDelegate: AnyObject { final class NotificationsAuthorizationController: NotificationsAuthorizationControlling { weak var delegate: NotificationsPermissionsControllerDelegate? - weak var notificationCancellable: AnyCancellable? + var notificationCancellable: AnyCancellable? var authorizationStatus: UNAuthorizationStatus { get async { From 026091dc00295f5e03c2700b37957371d11e18bd Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:43:11 +0200 Subject: [PATCH 12/23] Fix threading issues --- DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift | 2 -- DuckDuckGo/NotificationsAuthorizationController.swift | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift index f1301e17ea..62833eb25a 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsViewModel.swift @@ -58,7 +58,6 @@ final class NetworkProtectionVPNNotificationsViewModel: ObservableObject { notificationsSettingsStore.alertsEnabled = enabled } - @MainActor private func updateViewKind(for authorizationStatus: UNAuthorizationStatus) { switch authorizationStatus { case .notDetermined, .denied: @@ -72,7 +71,6 @@ final class NetworkProtectionVPNNotificationsViewModel: ObservableObject { } extension NetworkProtectionVPNNotificationsViewModel: NotificationsPermissionsControllerDelegate { - @MainActor func authorizationStateDidChange(toStatus status: UNAuthorizationStatus) { updateViewKind(for: status) } diff --git a/DuckDuckGo/NotificationsAuthorizationController.swift b/DuckDuckGo/NotificationsAuthorizationController.swift index 91fb8ff225..fe1f122ebf 100644 --- a/DuckDuckGo/NotificationsAuthorizationController.swift +++ b/DuckDuckGo/NotificationsAuthorizationController.swift @@ -79,6 +79,8 @@ final class NotificationsAuthorizationController: NotificationsAuthorizationCont private func updateDelegateWithNewState() async { let newState = await authorizationStatus - delegate?.authorizationStateDidChange(toStatus: newState) + DispatchQueue.main.async { [weak self] in + self?.delegate?.authorizationStateDidChange(toStatus: newState) + } } } From e91dc807dc098c9dd39dd3f593fd44f99981fe9e Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:43:25 +0200 Subject: [PATCH 13/23] Add an explanatory comment --- DuckDuckGo/NotificationsAuthorizationController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/NotificationsAuthorizationController.swift b/DuckDuckGo/NotificationsAuthorizationController.swift index fe1f122ebf..a5524a4cc9 100644 --- a/DuckDuckGo/NotificationsAuthorizationController.swift +++ b/DuckDuckGo/NotificationsAuthorizationController.swift @@ -45,6 +45,7 @@ final class NotificationsAuthorizationController: NotificationsAuthorizationCont } init() { + // To handle navigating back from iOS Settings after changing the authorization notificationCancellable = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) .sink { _ in Task { [weak self] in From 22372ebcb7e188f9a24d2ae95f3337c172d872af Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 15:55:31 +0200 Subject: [PATCH 14/23] Fix issue where reload reverts to empty --- DuckDuckGo/NetworkProtectionVPNNotificationsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index 4243659486..93cc491bf2 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -26,7 +26,7 @@ import Core @available(iOS 15, *) struct NetworkProtectionVPNNotificationsView: View { - @ObservedObject var model = NetworkProtectionVPNNotificationsViewModel( + @StateObject var model = NetworkProtectionVPNNotificationsViewModel( notificationsAuthorization: NotificationsAuthorizationController(), notificationsSettingsStore: NetworkProtectionNotificationsSettingsUserDefaultsStore(userDefaults: .networkProtectionGroupDefaults) ) From 5e6f49fb65bd87671f4e414bbe86b08b18b8f4cd Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 16:09:49 +0200 Subject: [PATCH 15/23] Add the netP group ID to main entitlements --- DuckDuckGo/DuckDuckGo.entitlements | 1 + PacketTunnelProvider/PacketTunnelProvider.entitlements | 1 + 2 files changed, 2 insertions(+) diff --git a/DuckDuckGo/DuckDuckGo.entitlements b/DuckDuckGo/DuckDuckGo.entitlements index 3b229351b9..82bd4ed6cd 100644 --- a/DuckDuckGo/DuckDuckGo.entitlements +++ b/DuckDuckGo/DuckDuckGo.entitlements @@ -15,6 +15,7 @@ $(GROUP_ID_PREFIX).statistics $(GROUP_ID_PREFIX).database $(GROUP_ID_PREFIX).apptp + $(GROUP_ID_PREFIX).netp diff --git a/PacketTunnelProvider/PacketTunnelProvider.entitlements b/PacketTunnelProvider/PacketTunnelProvider.entitlements index 86c503b63c..5e171bb76b 100644 --- a/PacketTunnelProvider/PacketTunnelProvider.entitlements +++ b/PacketTunnelProvider/PacketTunnelProvider.entitlements @@ -9,6 +9,7 @@ com.apple.security.application-groups $(GROUP_ID_PREFIX).apptp + $(GROUP_ID_PREFIX).netp From b25ec34da5a247d06554ac790fd17eee639641d9 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 16:35:33 +0200 Subject: [PATCH 16/23] Fix animation bug on status view --- DuckDuckGo/NetworkProtectionStatusView.swift | 7 +++++-- DuckDuckGo/NetworkProtectionStatusViewModel.swift | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 62435739f8..de8c854726 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -40,9 +40,12 @@ struct NetworkProtectionStatusView: View { } settings() } - .animation(.default, value: statusModel.shouldShowError) .padding(.top, statusModel.error == nil ? 0 : -20) - .animation(.default, value: statusModel.shouldShowConnectionDetails) + .if(statusModel.animationsOn, transform: { + $0 + .animation(.default, value: statusModel.shouldShowConnectionDetails) + .animation(.default, value: statusModel.shouldShowError) + }) .applyInsetGroupedListStyle() .navigationTitle(UserText.netPNavTitle) } diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 3ee41e6b67..dc8924c38b 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -65,6 +65,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @Published public var location: String? @Published public var ipAddress: String? + @Published public var animationsOn: Bool = false + public init(tunnelController: TunnelController = NetworkProtectionTunnelController(), statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), @@ -166,7 +168,11 @@ final class NetworkProtectionStatusViewModel: ObservableObject { .store(in: &cancellables) } + @MainActor func didToggleNetP(to enabled: Bool) async { + // This is to prevent weird looking animations on navigating to the screen. + // It makes sense as animations should mostly only happen when a user has interacted. + animationsOn = true if enabled { await enableNetP() } else { From f6a738660dfccddc8d50f949589d453cb209c518 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 17:30:11 +0200 Subject: [PATCH 17/23] Add footer text for turning on notifications --- .../NetworkProtectionVPNNotificationsView.swift | 14 ++++++++++---- DuckDuckGo/UserText.swift | 1 + DuckDuckGo/en.lproj/Localizable.strings | 3 +++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index 93cc491bf2..80099303a7 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -53,10 +53,17 @@ struct NetworkProtectionVPNNotificationsView: View { @ViewBuilder private var unauthorizedView: some View { - Button(UserText.netPTurnOnNotificationsButtonTitle) { - model.turnOnNotifications() + Section { + Button(UserText.netPTurnOnNotificationsButtonTitle) { + model.turnOnNotifications() + } + .foregroundColor(.controlColor) + } footer: { + Text(UserText.netPTurnOnNotificationsSectionFooter) + .foregroundColor(.textSecondary) + .font(.system(size: 13)) + .padding(.top, 6) } - .foregroundColor(.controlColor) } @ViewBuilder @@ -70,7 +77,6 @@ struct NetworkProtectionVPNNotificationsView: View { } footer: { Text(UserText.netPVPNAlertsToggleSectionFooter) .foregroundColor(.textSecondary) - .accentColor(.controlColor) .font(.system(size: 13)) .padding(.top, 6) } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 32b10f98e2..c9ea308a37 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -657,6 +657,7 @@ In addition to the details entered into this form, your app issue report will co static let netPSecureDNSSettingTitle = NSLocalizedString("network.protection.vpn.secure.dns.setting.title", value: "Secure DNS", comment: "Title for the Always on VPN setting item.") static let netPSecureDNSSettingFooter = NSLocalizedString("network.protection.vpn.secure.dns.setting.footer", value: "Network Protection prevents DNS leaks to your Internet Service Provider by routing DNS queries though the VPN tunnel to our own resolver.", comment: "Footer text for the Always on VPN setting item.") static let netPTurnOnNotificationsButtonTitle = NSLocalizedString("network.protection.turn.on.notifications.button.title", value: "Turn on Notifications", comment: "Title for the button to link to the iOS app settings and enable notifications app-wide.") + static let netPTurnOnNotificationsSectionFooter = NSLocalizedString("network.protection.turn.on.notifications.section.footer", value: "Allow DuckDuckGo to notify you if your connection drops or VPN status changes.", comment: "Footer text under the button to link to the iOS app settings and enable notifications app-wide.") static let netPVPNAlertsToggleTitle = NSLocalizedString("network.protection.vpn.alerts.toggle.title", value: "VPN Alerts", comment: "Title for the toggle for VPN alerts.") static let netPVPNAlertsToggleSectionFooter = NSLocalizedString("network.protection.vpn.alerts.toggle.section.footer", value: "Get notified if your connection drops or VPN status changes.", comment: "List section footer for the toggle for VPN alerts.") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 097bae8235..775a09b0ad 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1441,6 +1441,9 @@ https://duckduckgo.com/mac"; /* Title for the button to link to the iOS app settings and enable notifications app-wide. */ "network.protection.turn.on.notifications.button.title" = "Turn on Notifications"; +/* Footer text under the button to link to the iOS app settings and enable notifications app-wide. */ +"network.protection.turn.on.notifications.section.footer" = "Allow DuckDuckGo to notify you if your connection drops or VPN status changes."; + /* List section footer for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.section.footer" = "Get notified if your connection drops or VPN status changes."; From e26bb988be6135b21acb7760bdfba1f0453d2159 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 17:34:00 +0200 Subject: [PATCH 18/23] Point to latest BSK version --- 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 770396c26f..6b79365747 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9006,7 +9006,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = revision; - revision = ddd47b1ba026bca6a0512471ce4bb0aa6989da27; + revision = f2a5d102da34842b3ef02c876a1a539648bd5930; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cc02b3c6de..dfe1b5d0fd 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,7 +15,7 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "ddd47b1ba026bca6a0512471ce4bb0aa6989da27", + "revision": "f2a5d102da34842b3ef02c876a1a539648bd5930", "version": null } }, From e2d08fc05af6f18625df1da50a01dde48d355634 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 18:10:48 +0200 Subject: [PATCH 19/23] Move view model init into convenience init --- .../NetworkProtectionConvenienceInitialisers.swift | 10 ++++++++++ DuckDuckGo/NetworkProtectionVPNNotificationsView.swift | 5 +---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 248b52aa81..0587a405b8 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -58,4 +58,14 @@ extension NetworkProtectionCodeRedemptionCoordinator { } } +extension NetworkProtectionVPNNotificationsViewModel { + convenience init() { + let notificationsSettingsStore = NetworkProtectionNotificationsSettingsUserDefaultsStore(userDefaults: .networkProtectionGroupDefaults) + self.init( + notificationsAuthorization: NotificationsAuthorizationController(), + notificationsSettingsStore: notificationsSettingsStore + ) + } +} + #endif diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index 80099303a7..cd97c48950 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -26,10 +26,7 @@ import Core @available(iOS 15, *) struct NetworkProtectionVPNNotificationsView: View { - @StateObject var model = NetworkProtectionVPNNotificationsViewModel( - notificationsAuthorization: NotificationsAuthorizationController(), - notificationsSettingsStore: NetworkProtectionNotificationsSettingsUserDefaultsStore(userDefaults: .networkProtectionGroupDefaults) - ) + @StateObject var model = NetworkProtectionVPNNotificationsViewModel() var body: some View { List { From 691a990590f5c473415e8b0e45085a52bfd7ee03 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 20 Oct 2023 18:11:05 +0200 Subject: [PATCH 20/23] Wrap the whole PTP in a NetP check to fix release --- .../NetworkProtectionPacketTunnelProvider.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 4e9fe535ae..5db8d2c3c5 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -17,6 +17,8 @@ // limitations under the License. // +#if NETWORK_PROTECTION + import Foundation import NetworkProtection import Common @@ -208,3 +210,5 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } } } + +#endif From f8e01610bb770cb66f6819de70b93b29e7d8bf6f Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Tue, 31 Oct 2023 16:46:49 +0100 Subject: [PATCH 21/23] Update DuckDuckGo/NetworkProtectionVPNNotificationsView.swift Co-authored-by: Sam Symons --- DuckDuckGo/NetworkProtectionVPNNotificationsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift index cd97c48950..d5b0d22fd2 100644 --- a/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNNotificationsView.swift @@ -70,7 +70,7 @@ struct NetworkProtectionVPNNotificationsView: View { get: { model.alertsEnabled }, set: model.didToggleAlerts(to:) )) - .toggleStyle(SwitchToggleStyle(tint: Color(designSystemColor: .accent))) + .toggleStyle(SwitchToggleStyle(tint: .controlColor)) } footer: { Text(UserText.netPVPNAlertsToggleSectionFooter) .foregroundColor(.textSecondary) From 303ccdd510bcb28b52fcd77ee805a549d11c7e3e Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Tue, 31 Oct 2023 17:12:45 +0100 Subject: [PATCH 22/23] Point to 82.1.0 --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6b79365747..0412c11a04 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9005,8 +9005,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = revision; - revision = f2a5d102da34842b3ef02c876a1a539648bd5930; + kind = exactVersion; + version = 82.1.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dfe1b5d0fd..44f204ace6 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "f2a5d102da34842b3ef02c876a1a539648bd5930", - "version": null + "revision": "71e916d070cedcba9ccb3ce9431797260bf5cbea", + "version": "82.1.0" } }, { From b146678cd08e23c56cd9531755c3331f7cce1b91 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Tue, 31 Oct 2023 17:47:01 +0100 Subject: [PATCH 23/23] Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 44f204ace6..0836aa38a9 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", "state": { "branch": null, - "revision": "6dd7d696d4e666cedb2f1890a46fe53615226646", - "version": "8.4.2" + "revision": "c8e895c8fd50dc76e8d8dc827a636ad77b7f46ff", + "version": "9.0.0" } }, { @@ -105,8 +105,8 @@ "repositoryURL": "https://github.com/duckduckgo/privacy-dashboard", "state": { "branch": null, - "revision": "51e2b46f413bf3ef18afefad631ca70f2c25ef70", - "version": "1.4.0" + "revision": "b4ac92a444e79d5651930482623b9f6dc9265667", + "version": "2.0.0" } }, {