Skip to content

Commit

Permalink
iOS VPN Settings part 1 (#2109)
Browse files Browse the repository at this point in the history
  • Loading branch information
graeme authored Oct 19, 2023
1 parent 523f14d commit 5c5ee6d
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 64 deletions.
12 changes: 12 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -2332,6 +2333,7 @@
EE7917902A83DE93008DFF28 /* CombineTestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTestUtilities.swift; sourceTree = "<group>"; };
EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNotificationIdentifier.swift; sourceTree = "<group>"; };
EE8594982A44791C008A6D06 /* NetworkProtectionTunnelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionTunnelController.swift; sourceTree = "<group>"; };
EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNSettingsView.swift; sourceTree = "<group>"; };
EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Configuration-Alpha.xcconfig"; path = "Configuration/Configuration-Alpha.xcconfig"; sourceTree = "<group>"; };
EEDFE2DB2AC6ED4F00F0E19C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
EEDFE2DD2AC6ED5B00F0E19C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4413,9 +4415,18 @@
name = NetworkProtection;
sourceTree = "<group>";
};
EE9D68CF2AE00CE000B55EF4 /* VPNSettings */ = {
isa = PBXGroup;
children = (
EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */,
);
name = VPNSettings;
sourceTree = "<group>";
};
EECD94B22A28B8580085C66E /* NetworkProtection */ = {
isa = PBXGroup;
children = (
EE9D68CF2AE00CE000B55EF4 /* VPNSettings */,
EE458D122ABB651500FC651A /* Debug */,
EE0153E22A6FE031002A8B26 /* Root */,
EE0153DF2A6EABAF002A8B26 /* Helpers */,
Expand Down Expand Up @@ -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 */,
Expand Down
6 changes: 4 additions & 2 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 1 addition & 6 deletions DuckDuckGo/NetworkProtectionRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import SwiftUI
import NetworkProtection

@available(iOS 15, *)
struct NetworkProtectionRootView: View {
let model = NetworkProtectionRootViewModel()
let inviteCompletion: () -> Void
Expand All @@ -42,10 +43,4 @@ struct NetworkProtectionRootView: View {
}
}

struct NetworkProtectionRootView_Previews: PreviewProvider {
static var previews: some View {
NetworkProtectionRootView { }
}
}

#endif
2 changes: 2 additions & 0 deletions DuckDuckGo/NetworkProtectionRootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import SwiftUI

@available(iOS 15, *)
final class NetworkProtectionRootViewController: UIHostingController<NetworkProtectionRootView> {

init(inviteCompletion: @escaping () -> Void = { }) {
Expand All @@ -39,6 +40,7 @@ final class NetworkProtectionRootViewController: UIHostingController<NetworkProt
}
}

@available(iOS 15, *)
extension NetworkProtectionRootViewController: Themable {

func decorate(with theme: Theme) {
Expand Down
68 changes: 20 additions & 48 deletions DuckDuckGo/NetworkProtectionStatusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import SwiftUI
import NetworkProtection

@available(iOS 15, *)
struct NetworkProtectionStatusView: View {
@ObservedObject public var statusModel: NetworkProtectionStatusViewModel

Expand All @@ -37,11 +38,12 @@ struct NetworkProtectionStatusView: View {
if statusModel.shouldShowConnectionDetails {
connectionDetails()
}
settings()
}
.animation(.default, value: statusModel.shouldShowError)
.padding(.top, statusModel.error == nil ? 0 : -20)
.animation(.default, value: statusModel.shouldShowConnectionDetails)
.applyListStyle()
.applyInsetGroupedListStyle()
.navigationTitle(UserText.netPNavTitle)
}

Expand Down Expand Up @@ -72,11 +74,8 @@ struct NetworkProtectionStatusView: View {
.listRowBackground(Color.cellBackground)
} header: {
header()
} footer: {
if !statusModel.shouldShowConnectionDetails {
inviteOnlyFooter()
}
}.increaseHeaderProminence()
}
.increaseHeaderProminence()
}

@ViewBuilder
Expand Down Expand Up @@ -125,7 +124,21 @@ struct NetworkProtectionStatusView: View {
)
}
} header: {
Text(UserText.netPStatusViewConnectionDetails).foregroundColor(.textPrimary)
Text(UserText.netPStatusViewConnectionDetails).foregroundColor(.textSecondary)
}
}

@ViewBuilder
private func settings() -> 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()
}
Expand Down Expand Up @@ -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)
Expand All @@ -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
77 changes: 77 additions & 0 deletions DuckDuckGo/NetworkProtectionVPNSettingsView.swift
Original file line number Diff line number Diff line change
@@ -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
18 changes: 10 additions & 8 deletions DuckDuckGo/SettingsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -428,7 +429,7 @@ class SettingsViewController: UITableViewController {
}
pushNetP(rootViewController)
}

@available(iOS 15, *)
private func pushNetP(_ rootViewController: NetworkProtectionRootViewController) {
navigationController?.pushViewController(
rootViewController,
Expand All @@ -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()

Expand All @@ -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
}

Expand Down
7 changes: 7 additions & 0 deletions DuckDuckGo/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
38 changes: 38 additions & 0 deletions DuckDuckGo/ViewExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
Loading

0 comments on commit 5c5ee6d

Please sign in to comment.