diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f8230fccdd..cd69e916e0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -764,6 +764,7 @@ EE7A92872AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */; }; 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 */; }; 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 */; }; @@ -2332,6 +2333,7 @@ EE7917902A83DE93008DFF28 /* CombineTestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTestUtilities.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -4413,9 +4415,18 @@ name = NetworkProtection; sourceTree = ""; }; + EE9D68CF2AE00CE000B55EF4 /* VPNSettings */ = { + isa = PBXGroup; + children = ( + EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */, + ); + name = VPNSettings; + sourceTree = ""; + }; EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + EE9D68CF2AE00CE000B55EF4 /* VPNSettings */, EE458D122ABB651500FC651A /* Debug */, EE0153E22A6FE031002A8B26 /* Root */, EE0153DF2A6EABAF002A8B26 /* Helpers */, @@ -6072,6 +6083,7 @@ 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */, 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */, + EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */, 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 8707bce4f6..ee385aa81a 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -698,8 +698,10 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if NETWORK_PROTECTION private func presentNetworkProtectionStatusSettingsModal() { - let networkProtectionRoot = NetworkProtectionRootViewController() - presentSettings(with: networkProtectionRoot) + if #available(iOS 15, *) { + let networkProtectionRoot = NetworkProtectionRootViewController() + presentSettings(with: networkProtectionRoot) + } } #endif diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 3a31df02d0..0ec4d60535 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -22,6 +22,7 @@ import SwiftUI import NetworkProtection +@available(iOS 15, *) struct NetworkProtectionRootView: View { let model = NetworkProtectionRootViewModel() let inviteCompletion: () -> Void @@ -42,10 +43,4 @@ struct NetworkProtectionRootView: View { } } -struct NetworkProtectionRootView_Previews: PreviewProvider { - static var previews: some View { - NetworkProtectionRootView { } - } -} - #endif diff --git a/DuckDuckGo/NetworkProtectionRootViewController.swift b/DuckDuckGo/NetworkProtectionRootViewController.swift index 485f586f5d..8e3f21bdee 100644 --- a/DuckDuckGo/NetworkProtectionRootViewController.swift +++ b/DuckDuckGo/NetworkProtectionRootViewController.swift @@ -21,6 +21,7 @@ import SwiftUI +@available(iOS 15, *) final class NetworkProtectionRootViewController: UIHostingController { init(inviteCompletion: @escaping () -> Void = { }) { @@ -39,6 +40,7 @@ final class NetworkProtectionRootViewController: UIHostingController some View { + Section { + NavigationLink(UserText.netPVPNSettingsTitle, destination: NetworkProtectionVPNSettingsView()) + .font(.system(size: 16)) + .foregroundColor(.textPrimary) + NavigationLink(UserText.netPVPNNotificationsTitle, destination: Text("Coming soon!")) + .font(.system(size: 16)) + .foregroundColor(.textPrimary) + } header: { + Text(UserText.netPStatusViewSettingsSectionTitle).foregroundColor(.textSecondary) } footer: { inviteOnlyFooter() } @@ -183,41 +196,6 @@ private struct NetworkProtectionServerItemView: View { } } -private extension View { - @ViewBuilder - func hideScrollContentBackground() -> some View { - if #available(iOS 16, *) { - self.scrollContentBackground(.hidden) - } else { - let originalBackgroundColor = UITableView.appearance().backgroundColor - self.onAppear { - UITableView.appearance().backgroundColor = .clear - }.onDisappear { - UITableView.appearance().backgroundColor = originalBackgroundColor - } - } - } - - @ViewBuilder - func applyListStyle() -> some View { - self - .listStyle(.insetGrouped) - .hideScrollContentBackground() - .background( - Rectangle().ignoresSafeArea().foregroundColor(Color.viewBackground) - ) - } - - @ViewBuilder - func increaseHeaderProminence() -> some View { - if #available(iOS 15, *) { - self.headerProminence(.increased) - } else { - self - } - } -} - private extension Color { static let textPrimary = Color(designSystemColor: .textPrimary) static let textSecondary = Color(designSystemColor: .textSecondary) @@ -226,10 +204,4 @@ private extension Color { static let controlColor = Color(designSystemColor: .accent) } -struct NetworkProtectionStatusView_Previews: PreviewProvider { - static var previews: some View { - NetworkProtectionStatusView(statusModel: NetworkProtectionStatusViewModel()) - } -} - #endif diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift new file mode 100644 index 0000000000..b2dc110fda --- /dev/null +++ b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift @@ -0,0 +1,77 @@ +// +// NetworkProtectionVPNSettingsView.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 + +@available(iOS 15, *) +struct NetworkProtectionVPNSettingsView: View { + + var body: some View { + List { + toggleSection( + text: UserText.netPAlwaysOnSettingTitle, + footerText: UserText.netPAlwaysOnSettingFooter + ) + toggleSection( + text: UserText.netPSecureDNSSettingTitle, + footerText: UserText.netPSecureDNSSettingFooter + ) + } + .applyInsetGroupedListStyle() + .navigationTitle(UserText.netPVPNSettingsTitle) + } + + @ViewBuilder + func toggleSection(text: String, footerText: String) -> some View { + Section { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(text) + .font(.system(size: 16)) + .foregroundColor(.textPrimary.opacity(0.4)) + .font(.system(size: 13)) + .foregroundColor(.textSecondary.opacity(0.4)) + } + + // These toggles are permanantly disabled as the features are permanantly enabled. Product decision. + Toggle("", isOn: .constant(true)) + .disabled(true) + .toggleStyle(SwitchToggleStyle(tint: .controlColor)) + } + .listRowBackground(Color.cellBackground) + } footer: { + Text(footerText) + .foregroundColor(.textSecondary) + .accentColor(Color.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/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 2a48c55037..673a4da738 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -419,6 +419,7 @@ class SettingsViewController: UITableViewController { } #if NETWORK_PROTECTION + @available(iOS 15, *) private func showNetP() { // This will be tidied up as part of https://app.asana.com/0/0/1205084446087078/f let rootViewController = NetworkProtectionRootViewController { [weak self] in @@ -428,7 +429,7 @@ class SettingsViewController: UITableViewController { } pushNetP(rootViewController) } - + @available(iOS 15, *) private func pushNetP(_ rootViewController: NetworkProtectionRootViewController) { navigationController?.pushViewController( rootViewController, @@ -451,15 +452,15 @@ class SettingsViewController: UITableViewController { let cell = tableView.cellForRow(at: indexPath) switch cell { - + case defaultBrowserCell: Pixel.fire(pixel: .defaultBrowserButtonPressedSettings) guard let url = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(url) - + case emailProtectionCell: showEmailWebDashboard() - + case macBrowserWaitlistCell: showMacBrowserWaitlistViewController() @@ -471,14 +472,15 @@ class SettingsViewController: UITableViewController { case syncCell: showSync() - + case netPCell: + if #available(iOS 15, *) { #if NETWORK_PROTECTION - showNetP() + showNetP() #else - break + break #endif - + } default: break } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 9df4b09a48..4425118079 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -646,9 +646,16 @@ In addition to the details entered into this form, your app issue report will co static let netPStatusViewLocation = NSLocalizedString("network.protection.status.view.location", value: "Location", comment: "Location label shown in NetworkProtection's status view.") static let netPStatusViewIPAddress = NSLocalizedString("network.protection.status.view.ip.address", value: "IP Address", comment: "IP Address label shown in NetworkProtection's status view.") static let netPStatusViewConnectionDetails = NSLocalizedString("network.protection.status.view.connection.details", value: "Connection Details", comment: "Connection details label shown in NetworkProtection's status view.") + static let netPStatusViewSettingsSectionTitle = NSLocalizedString("network.protection.status.view.settings.section.title", value: "Manage", comment: "Label shown on the title of the settings section in NetworkProtection's status view.") + static let netPVPNSettingsTitle = NSLocalizedString("network.protection.vpn.settings.title", value: "VPN Settings", comment: "Title for the VPN Settings screen.") + static let netPVPNNotificationsTitle = NSLocalizedString("network.protection.vpn.notifications.title", value: "VPN Notifications", comment: "Title for the VPN Notifications management screen.") static let netPStatusViewShareFeedback = NSLocalizedString("network.protection.status.menu.share.feedback", value: "Share Feedback", comment: "The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text") static let netPStatusViewErrorConnectionFailedTitle = NSLocalizedString("network.protection.status.view.error.connection.failed.title", value: "Failed to Connect.", comment: "Generic connection failed error title shown in NetworkProtection's status view.") static let netPStatusViewErrorConnectionFailedMessage = NSLocalizedString("network.protection.status.view.error.connection.failed.message", value: "Please try again later.", comment: "Generic connection failed error message shown in NetworkProtection's status view.") + static let netPAlwaysOnSettingTitle = NSLocalizedString("network.protection.vpn.always.on.setting.title", value: "Always On", comment: "Title for the Always on VPN setting item.") + 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 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/ViewExtension.swift b/DuckDuckGo/ViewExtension.swift index 83c7078b90..d7f45ec31c 100644 --- a/DuckDuckGo/ViewExtension.swift +++ b/DuckDuckGo/ViewExtension.swift @@ -33,3 +33,41 @@ extension View { } } } + +/* + These exensions are needed to provide the UI styling specs for Network Protection + However, at time of writing, they are not supported in iOS <=14. As Network Protection + is not supporting iOS <=14, these are being kept separate. + */ + +@available(iOS 15, *) +extension View { + @ViewBuilder + func applyInsetGroupedListStyle() -> some View { + self + .listStyle(.insetGrouped) + .hideScrollContentBackground() + .background( + Rectangle().ignoresSafeArea().foregroundColor(Color(designSystemColor: .background)) + ) + } + + @ViewBuilder + func increaseHeaderProminence() -> some View { + self.headerProminence(.increased) + } + + @ViewBuilder + private func hideScrollContentBackground() -> some View { + if #available(iOS 16, *) { + self.scrollContentBackground(.hidden) + } else { + let originalBackgroundColor = UITableView.appearance().backgroundColor + self.onAppear { + UITableView.appearance().backgroundColor = .clear + }.onDisappear { + UITableView.appearance().backgroundColor = originalBackgroundColor + } + } + } +} diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 3ef54e37f6..c071c4409f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1432,9 +1432,30 @@ https://duckduckgo.com/mac"; /* Location label shown in NetworkProtection's status view. */ "network.protection.status.view.location" = "Location"; +/* Label shown on the title of the settings section in NetworkProtection's status view. */ +"network.protection.status.view.settings.section.title" = "Manage"; + /* Title label text for the status view when netP is disconnected */ "network.protection.status.view.title" = "Network Protection"; +/* Footer text for the Always on VPN setting item. */ +"network.protection.vpn.always.on.setting.footer" = "Automatically restore a VPN connection after interruption."; + +/* Title for the Always on VPN setting item. */ +"network.protection.vpn.always.on.setting.title" = "Always On"; + +/* Title for the VPN Notifications management screen. */ +"network.protection.vpn.notifications.title" = "VPN Notifications"; + +/* Footer text for the Always on VPN setting item. */ +"network.protection.vpn.secure.dns.setting.footer" = "Network Protection prevents DNS leaks to your Internet Service Provider by routing DNS queries though the VPN tunnel to our own resolver."; + +/* Title for the Always on VPN setting item. */ +"network.protection.vpn.secure.dns.setting.title" = "Secure DNS"; + +/* Title for the VPN Settings screen. */ +"network.protection.vpn.settings.title" = "VPN Settings"; + /* Do not translate - stringsdict entry */ "number.of.tabs" = "number.of.tabs";