From 87e7718f940033a0bf009960bd9906ede2785ac1 Mon Sep 17 00:00:00 2001 From: Soner YUKSEL Date: Fri, 28 Apr 2023 10:30:13 -0400 Subject: [PATCH] Fix #7255: VPN Protocol (Tunnel) Option (#7334) --- Sources/BraveStrings/BraveStrings.swift | 37 +++++- Sources/BraveVPN/BraveVPN.swift | 38 +++++- .../BraveVPNContactFormViewController.swift | 28 ++++- .../BraveVPNPickerViewController.swift | 116 ++++++++++++++++++ ...BraveVPNProtocolPickerViewController.swift | 108 ++++++++++++++++ .../BraveVPNRegionPickerViewController.swift | 108 ++-------------- .../BraveVPNSettingsViewController.swift | 70 +++++++---- 7 files changed, 374 insertions(+), 131 deletions(-) create mode 100644 Sources/BraveVPN/BraveVPNPickerViewController.swift create mode 100644 Sources/BraveVPN/BraveVPNProtocolPickerViewController.swift diff --git a/Sources/BraveStrings/BraveStrings.swift b/Sources/BraveStrings/BraveStrings.swift index 705e78fc031..08602b824b0 100644 --- a/Sources/BraveStrings/BraveStrings.swift +++ b/Sources/BraveStrings/BraveStrings.swift @@ -2587,18 +2587,18 @@ extension Strings { public static let settingsServerLocation = NSLocalizedString("vpn.settingsServerLocation", tableName: "BraveShared", bundle: .module, value: "Location", - comment: "Table cell title for vpn's server location") + comment: "Table cell title for vpn's server location and which open opens location select") public static let settingsResetConfiguration = NSLocalizedString("vpn.settingsResetConfiguration", tableName: "BraveShared", bundle: .module, value: "Reset Configuration", comment: "Button to reset VPN configuration") - public static let settingsChangeLocation = - NSLocalizedString("vpn.settingsChangeLocation", tableName: "BraveShared", bundle: .module, - value: "Change Location", - comment: "Button to change VPN server location") - + public static let settingsTransportProtocol = + NSLocalizedString("vpn.settingsTransportProtocol", tableName: "BraveShared", bundle: .module, + value: "Transport Protocol", + comment: "Table cell title for vpn's transport protocol and which open opens protocol select") + public static let settingsContactSupport = NSLocalizedString("vpn.settingsContactSupport", tableName: "BraveShared", bundle: .module, value: "Contact Technical Support", @@ -2869,10 +2869,35 @@ extension Strings { value: "Failed to switch servers, please try again later.", comment: "Message for error when we fail to switch vpn server for the user") + public static let protocolPickerTitle = + NSLocalizedString("vpn.protocolPickerTitle", tableName: "BraveShared", bundle: .module, + value: "Transport Protocol", + comment: "Title for vpn tunnel protocol screen") + + public static let protocolPickerDescription = + NSLocalizedString("vpn.protocolPickerDescription", tableName: "BraveShared", bundle: .module, + value: "Please select your preferred transport protocol. Once switched your existing VPN credentials will be cleared and you will be reconnected if a VPN connection is currently established.", + comment: "Description of vpn tunnel protocol") + public static let regionSwitchSuccessPopupText = NSLocalizedString("vpn.regionSwitchSuccessPopupText", tableName: "BraveShared", bundle: .module, value: "VPN region changed.", comment: "Message that we show after successfully changing vpn region.") + + public static let protocolPickerErrorTitle = + NSLocalizedString("vpn.protocolPickerErrorTitle", tableName: "BraveShared", bundle: .module, + value: "Server Error", + comment: "Title for error when we fail to switch tunnel protocol for the user") + + public static let protocolPickerErrorMessage = + NSLocalizedString("vpn.protocolPickerErrorMessage", tableName: "BraveShared", bundle: .module, + value: "Failed to switch tunnel protocol, please try again later.", + comment: "Message for error when we fail to switch tunnel protocol for the user") + + public static let protocolSwitchSuccessPopupText = + NSLocalizedString("vpn.protocolSwitchSuccessPopupText", tableName: "BraveShared", bundle: .module, + value: "VPN Tunnel Protocol changed.", + comment: "Message that we show after successfully changing tunnel protocol.") public static let settingsFailedToFetchServerList = NSLocalizedString("vpn.settingsFailedToFetchServerList", tableName: "BraveShared", bundle: .module, diff --git a/Sources/BraveVPN/BraveVPN.swift b/Sources/BraveVPN/BraveVPN.swift index 71109f8bc65..9b4f6dd55b5 100644 --- a/Sources/BraveVPN/BraveVPN.swift +++ b/Sources/BraveVPN/BraveVPN.swift @@ -16,6 +16,7 @@ public class BraveVPN { private static let housekeepingApi = GRDHousekeepingAPI() private static let helper = GRDVPNHelper.sharedInstance() private static let serverManager = GRDServerManager() + private static let tunnelManager = GRDTunnelManager() public static let iapObserver = IAPObserver() @@ -278,11 +279,14 @@ public class BraveVPN { completion?(status == .success) } } else { - // Setting preferred protocol in order to determine which tunnel to be activated while region selection + // Setting User preferred Transport Protocol to WireGuard + // In order to easily fetch and change in settings later GRDTransportProtocol.setUserPreferred(.wireGuard) // New user or no credentials and have to remake them. - helper.configureFirstTimeUser(for: .wireGuard, postCredential: nil) { success, error in + helper.configureFirstTimeUser( + for: GRDTransportProtocol.getUserPreferredTransportProtocol(), + postCredential: nil) { success, error in if let error = error { logAndStoreError("configureFirstTimeUserPostCredential \(error)") } else { @@ -308,6 +312,36 @@ public class BraveVPN { completion?(status) } } + + public static func changePreferredTransportProtocol(with transportProtocol: TransportProtocol, completion: ((Bool) -> Void)? = nil) { + helper.forceDisconnectVPNIfNecessary() + GRDVPNHelper.clearVpnConfiguration() + + GRDTransportProtocol.setUserPreferred(transportProtocol) + + // New user or no credentials and have to remake them. + helper.configureFirstTimeUser(for: transportProtocol, postCredential: nil) { success, error in + if let error = error { + logAndStoreError("Change Preferred transport FirstTimeUserPostCredential \(error)") + } else { + removeConfigurationFromPreferences(for: transportProtocol) + } + + reconnectPending = false + completion?(success) + } + + func removeConfigurationFromPreferences(for transportProtocol: TransportProtocol) { + switch transportProtocol { + case .wireGuard: + helper.ikev2VPNManager.removeFromPreferences() + case .ikEv2: + tunnelManager.removeTunnel() + default: + return + } + } + } public static func clearCredentials() { GRDKeychain.removeGuardianKeychainItems() diff --git a/Sources/BraveVPN/BraveVPNContactFormViewController.swift b/Sources/BraveVPN/BraveVPNContactFormViewController.swift index 30b652b92b6..bc899a4e2a0 100644 --- a/Sources/BraveVPN/BraveVPNContactFormViewController.swift +++ b/Sources/BraveVPN/BraveVPNContactFormViewController.swift @@ -13,6 +13,7 @@ import UIKit import BraveUI import os.log import BraveStrings +import GuardianConnect class BraveVPNContactFormViewController: TableViewController { @@ -24,6 +25,7 @@ class BraveVPNContactFormViewController: TableViewController { private struct ContactForm { var hostname: String? + var tunnelProtocol: String? var subscriptionType: String? var receipt: String? var appVersion: String? @@ -75,6 +77,23 @@ class BraveVPNContactFormViewController: TableViewController { } }))) + // MARK: TunnelProtocol + let userPreferredTunnelProtocol = GRDTransportProtocol.getUserPreferredTransportProtocol() + let transportProtocol = GRDTransportProtocol.prettyTransportProtocolString(for: userPreferredTunnelProtocol) + let tunnelProtocolRow = + Row( + text: Strings.VPN.protocolPickerTitle, detailText: transportProtocol, + accessory: .view( + SwitchAccessoryView( + initialValue: false, + valueChange: { [weak self] isOn in + if isOn { + self?.contactForm.tunnelProtocol = transportProtocol + } else { + self?.contactForm.tunnelProtocol = nil + } + })), cellClass: MultilineSubtitleCell.self) + // MARK: SubscriptionType let subscriptionType = BraveVPN.subscriptionName let subscriptionTypeRow = @@ -189,8 +208,8 @@ class BraveVPNContactFormViewController: TableViewController { })), cellClass: MultilineSubtitleCell.self) var section = Section(rows: [ - hostnameRow, subscriptionTypeRow, receiptRow, appVersionRow, - timezoneRow, networkTypeRow, carrierRow, errorLogs, + hostnameRow, tunnelProtocolRow, subscriptionTypeRow, receiptRow, + appVersionRow, timezoneRow, networkTypeRow, carrierRow, errorLogs ]) // MARK: Issue @@ -280,6 +299,11 @@ class BraveVPNContactFormViewController: TableViewController { body.append(Strings.VPN.contactFormHostname) body.append("\n\(hostname)\n\n") } + + if let tunnelProtocol = contactForm.tunnelProtocol { + body.append(Strings.VPN.protocolPickerTitle) + body.append("\n\(tunnelProtocol)\n\n") + } if let subcriptionType = contactForm.subscriptionType { body.append(Strings.VPN.contactFormSubscriptionType) diff --git a/Sources/BraveVPN/BraveVPNPickerViewController.swift b/Sources/BraveVPN/BraveVPNPickerViewController.swift new file mode 100644 index 00000000000..bb33a599359 --- /dev/null +++ b/Sources/BraveVPN/BraveVPNPickerViewController.swift @@ -0,0 +1,116 @@ +// Copyright 2020 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Shared +import BraveUI +import Lottie +import NetworkExtension +import GuardianConnect + +class BraveVPNPickerViewController: UIViewController { + + private var overlayView: UIView? + let tableView: UITableView = .init(frame: .zero, style: .insetGrouped) + + deinit { + NotificationCenter.default.removeObserver(self) + } + + var isLoading: Bool = false { + didSet { + overlayView?.removeFromSuperview() + + navigationItem.hidesBackButton = isLoading + + // Prevent dismissing the modal by swipe when the VPN is being configured + navigationController?.isModalInPresentation = isLoading + + if !isLoading { return } + + let overlay = UIView().then { + $0.backgroundColor = UIColor.black.withAlphaComponent(0.5) + let activityIndicator = UIActivityIndicatorView().then { indicator in + indicator.startAnimating() + indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + $0.addSubview(activityIndicator) + } + + view.addSubview(overlay) + overlay.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + overlayView = overlay + } + } + + init() { + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError() } + + override func viewDidLoad() { + tableView.register(VPNRegionCell.self) + + NotificationCenter.default.addObserver(self, selector: #selector(vpnConfigChanged(notification:)), + name: .NEVPNStatusDidChange, object: nil) + + view.addSubview(tableView) + tableView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tableView.reloadData() + } + + @objc func vpnConfigChanged(notification: NSNotification) { } + + func showSuccessAlert(text: String) { + let animation = AnimationView(name: "vpncheckmark", bundle: .module).then { + $0.bounds = CGRect(x: 0, y: 0, width: 300, height: 200) + $0.contentMode = .scaleAspectFill + $0.play() + } + + let popup = AlertPopupView(imageView: animation, + title: text, message: "", + titleWeight: .semibold, titleSize: 18, + dismissHandler: { true }) + + popup.showWithType(showType: .flyUp, autoDismissTime: 1.5) + } + + func showErrorAlert(title: String, message: String?) { + DispatchQueue.main.async { + let alert = AlertController(title: Strings.VPN.regionPickerErrorTitle, + message: Strings.VPN.regionPickerErrorMessage, + preferredStyle: .alert) + let okAction = UIAlertAction(title: Strings.OKString, style: .default) { _ in + self.dismiss(animated: true) + } + alert.addAction(okAction) + + self.present(alert, animated: true) + } + } +} + +class VPNRegionCell: UITableViewCell, TableViewReusable { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .value1, reuseIdentifier: reuseIdentifier) + } + @available(*, unavailable) + required init(coder: NSCoder) { + fatalError() + } +} diff --git a/Sources/BraveVPN/BraveVPNProtocolPickerViewController.swift b/Sources/BraveVPN/BraveVPNProtocolPickerViewController.swift new file mode 100644 index 00000000000..a0c8872727c --- /dev/null +++ b/Sources/BraveVPN/BraveVPNProtocolPickerViewController.swift @@ -0,0 +1,108 @@ +// Copyright 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Shared +import BraveUI +import Lottie +import NetworkExtension +import GuardianConnect + +class BraveVPNProtocolPickerViewController: BraveVPNPickerViewController { + + private let tunnelProtocolList: [TransportProtocol] + + /// This group monitors vpn connection status. + private var dispatchGroup: DispatchGroup? + private var vpnRegionChangeSuccess = false + + override init() { + self.tunnelProtocolList = [.wireGuard, .ikEv2] + + super.init() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError() } + + override func viewDidLoad() { + title = Strings.VPN.protocolPickerTitle + + tableView.delegate = self + tableView.dataSource = self + + super.viewDidLoad() + } + + override func vpnConfigChanged(notification: NSNotification) { + guard let connection = notification.object as? NEVPNConnection else { return } + + if connection.status == .connected { + dispatchGroup?.leave() + self.vpnRegionChangeSuccess = true + dispatchGroup = nil + } + } +} + +// MARK: - UITableView Data Source & Delegate + +extension BraveVPNProtocolPickerViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + tunnelProtocolList.count + } + + func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return Strings.VPN.protocolPickerDescription + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(for: indexPath) as VPNRegionCell + cell.accessoryType = .none + + guard let tunnelProtocol = tunnelProtocolList[safe: indexPath.row] else { return cell } + cell.textLabel?.text = GRDTransportProtocol.prettyTransportProtocolString(for: tunnelProtocol) + + let activeProtocolOption = GRDTransportProtocol.getUserPreferredTransportProtocol() + + if activeProtocolOption == tunnelProtocol { + cell.accessoryType = .checkmark + } + + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + guard let tunnelProtocol = tunnelProtocolList[safe: indexPath.row] else { return } + + let activeProtocolOption = GRDTransportProtocol.getUserPreferredTransportProtocol() + + // Same option is selected do nothing + if activeProtocolOption == tunnelProtocol { + return + } + + isLoading = true + + BraveVPN.changePreferredTransportProtocol(with: tunnelProtocol) { [weak self] success in + guard let self else { return } + + self.isLoading = false + + if success { + self.dismiss(animated: true) { + self.showSuccessAlert(text: Strings.VPN.protocolSwitchSuccessPopupText) + } + } else { + self.showErrorAlert(title: Strings.VPN.protocolPickerErrorTitle, + message: Strings.VPN.protocolPickerErrorMessage) + } + } + + } +} diff --git a/Sources/BraveVPN/BraveVPNRegionPickerViewController.swift b/Sources/BraveVPN/BraveVPNRegionPickerViewController.swift index 731779cf099..962853111c5 100644 --- a/Sources/BraveVPN/BraveVPNRegionPickerViewController.swift +++ b/Sources/BraveVPN/BraveVPNRegionPickerViewController.swift @@ -10,10 +10,7 @@ import Lottie import NetworkExtension import GuardianConnect -class BraveVPNRegionPickerViewController: UIViewController { - - private var overlayView: UIView? - private let tableView: UITableView = .init(frame: .zero, style: .insetGrouped) +class BraveVPNRegionPickerViewController: BraveVPNPickerViewController { private let regionList: [GRDRegion] private enum Section: Int, CaseIterable { @@ -21,49 +18,15 @@ class BraveVPNRegionPickerViewController: UIViewController { case regionList } - deinit { - NotificationCenter.default.removeObserver(self) - } - /// This group monitors vpn connection status. private var dispatchGroup: DispatchGroup? private var vpnRegionChangeSuccess = false - private var isLoading: Bool = false { - didSet { - overlayView?.removeFromSuperview() - - navigationItem.hidesBackButton = isLoading - - // Prevent dismissing the modal by swipe when the VPN is being configured - navigationController?.isModalInPresentation = isLoading - - if !isLoading { return } - - let overlay = UIView().then { - $0.backgroundColor = UIColor.black.withAlphaComponent(0.5) - let activityIndicator = UIActivityIndicatorView().then { indicator in - indicator.startAnimating() - indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight] - } - - $0.addSubview(activityIndicator) - } - - view.addSubview(overlay) - overlay.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - overlayView = overlay - } - } - - init() { + override init() { self.regionList = BraveVPN.regions .sorted { $0.displayName < $1.displayName } - super.init(nibName: nil, bundle: nil) + super.init() } @available(*, unavailable) @@ -74,18 +37,11 @@ class BraveVPNRegionPickerViewController: UIViewController { tableView.delegate = self tableView.dataSource = self - tableView.register(VPNRegionCell.self) - NotificationCenter.default.addObserver(self, selector: #selector(vpnConfigChanged(notification:)), - name: .NEVPNStatusDidChange, object: nil) - - view.addSubview(tableView) - tableView.snp.makeConstraints { - $0.edges.equalToSuperview() - } + super.viewDidLoad() } - @objc private func vpnConfigChanged(notification: NSNotification) { + override func vpnConfigChanged(notification: NSNotification) { guard let connection = notification.object as? NEVPNConnection else { return } if connection.status == .connected { @@ -94,15 +50,12 @@ class BraveVPNRegionPickerViewController: UIViewController { dispatchGroup = nil } } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - tableView.reloadData() - } } // MARK: - UITableView Data Source & Delegate + extension BraveVPNRegionPickerViewController: UITableViewDelegate, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { Section.allCases.count } @@ -167,22 +120,9 @@ extension BraveVPNRegionPickerViewController: UITableViewDelegate, UITableViewDa BraveVPN.changeVPNRegion(to: newRegion) { [weak self] success in guard let self = self else { return } - func _showError() { - DispatchQueue.main.async { - let alert = AlertController(title: Strings.VPN.regionPickerErrorTitle, - message: Strings.VPN.regionPickerErrorMessage, - preferredStyle: .alert) - let okAction = UIAlertAction(title: Strings.OKString, style: .default) { _ in - self.dismiss(animated: true) - } - alert.addAction(okAction) - - self.present(alert, animated: true) - } - } - if !success { - _showError() + self.showErrorAlert(title: Strings.VPN.regionPickerErrorTitle, + message: Strings.VPN.regionPickerErrorMessage) } // Changing vpn server settings takes lot of time, @@ -202,37 +142,13 @@ extension BraveVPNRegionPickerViewController: UITableViewDelegate, UITableViewDa if self.vpnRegionChangeSuccess { self.dismiss(animated: true) { - self.showSuccessAlert() + self.showSuccessAlert(text: Strings.VPN.regionSwitchSuccessPopupText) } } else { - _showError() + self.showErrorAlert(title: Strings.VPN.regionPickerErrorTitle, + message: Strings.VPN.regionPickerErrorMessage) } } } } - - private func showSuccessAlert() { - let animation = AnimationView(name: "vpncheckmark", bundle: .module).then { - $0.bounds = CGRect(x: 0, y: 0, width: 300, height: 200) - $0.contentMode = .scaleAspectFill - $0.play() - } - - let popup = AlertPopupView(imageView: animation, - title: Strings.VPN.regionSwitchSuccessPopupText, message: "", - titleWeight: .semibold, titleSize: 18, - dismissHandler: { true }) - - popup.showWithType(showType: .flyUp, autoDismissTime: 1.5) - } -} - -private class VPNRegionCell: UITableViewCell, TableViewReusable { - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .value1, reuseIdentifier: reuseIdentifier) - } - @available(*, unavailable) - required init(coder: NSCoder) { - fatalError() - } } diff --git a/Sources/BraveVPN/BraveVPNSettingsViewController.swift b/Sources/BraveVPN/BraveVPNSettingsViewController.swift index a8b5b5b0979..902b00af89d 100644 --- a/Sources/BraveVPN/BraveVPNSettingsViewController.swift +++ b/Sources/BraveVPN/BraveVPNSettingsViewController.swift @@ -10,6 +10,7 @@ import Preferences import BraveUI import os.log import BraveShared +import GuardianConnect public class BraveVPNSettingsViewController: TableViewController { @@ -28,6 +29,7 @@ public class BraveVPNSettingsViewController: TableViewController { private let serverSectionId = "server" private let hostCellId = "host" private let locationCellId = "location" + private let protocolCellId = "protocol" private let resetCellId = "reset" private let vpnStatusSectionCellId = "vpnStatus" @@ -133,33 +135,46 @@ public class BraveVPNSettingsViewController: TableViewController { let location = BraveVPN.serverLocation ?? "-" - let serverSection = - Section(header: .title(Strings.VPN.settingsServerSection), - rows: [Row(text: Strings.VPN.settingsServerHost, detailText: hostname, uuid: hostCellId), - Row(text: Strings.VPN.settingsServerLocation, detailText: location, - uuid: locationCellId), - Row(text: Strings.VPN.settingsChangeLocation, - selection: { [unowned self] in - self.selectServerTapped() - }, - cellClass: ButtonCell.self), - Row(text: Strings.VPN.settingsResetConfiguration, - selection: { [unowned self] in - self.resetConfigurationTapped() - }, - cellClass: ButtonCell.self, uuid: resetCellId)], - uuid: serverSectionId) + let userPreferredTunnelProtocol = GRDTransportProtocol.getUserPreferredTransportProtocol() + let transportProtocol = GRDTransportProtocol.prettyTransportProtocolString(for: userPreferredTunnelProtocol) + + let serverSection = Section( + header: .title(Strings.VPN.settingsServerSection), + rows: [Row(text: Strings.VPN.settingsServerHost, detailText: hostname, uuid: hostCellId), + Row(text: Strings.VPN.settingsServerLocation, + detailText: location, + selection: { [unowned self] in + self.selectServerTapped() + }, + accessory: .disclosureIndicator, + uuid: locationCellId), + Row(text: Strings.VPN.settingsTransportProtocol, + detailText: transportProtocol, + selection: { [unowned self] in + self.selectProtocolTapped() + }, + accessory: .disclosureIndicator, + uuid: protocolCellId), + Row(text: Strings.VPN.settingsResetConfiguration, + selection: { [unowned self] in + self.resetConfigurationTapped() + }, + cellClass: ButtonCell.self, uuid: resetCellId)], + uuid: serverSectionId) - let techSupportSection = Section(rows: - [Row(text: Strings.VPN.settingsContactSupport, selection: { [unowned self] in - self.sendContactSupportEmail() - }, accessory: .disclosureIndicator, cellClass: ButtonCell.self)]) + let techSupportSection = Section( + rows: [Row(text: Strings.VPN.settingsContactSupport, + selection: { [unowned self] in + self.sendContactSupportEmail() + }, + accessory: .disclosureIndicator)]) - let termsSection = Section(rows: - [Row(text: Strings.VPN.settingsFAQ, selection: { [unowned self] in - self.openURL?(.brave.braveVPNFaq) - - }, accessory: .disclosureIndicator, cellClass: ButtonCell.self)]) + let termsSection = Section( + rows: [Row(text: Strings.VPN.settingsFAQ, + selection: { [unowned self] in + self.openURL?(.brave.braveVPNFaq) + }, + cellClass: ButtonCell.self)]) dataSource.sections = [vpnStatusSection, subscriptionSection, @@ -271,6 +286,11 @@ public class BraveVPNSettingsViewController: TableViewController { let vc = BraveVPNRegionPickerViewController() navigationController?.pushViewController(vc, animated: true) } + + private func selectProtocolTapped() { + let vc = BraveVPNProtocolPickerViewController() + navigationController?.pushViewController(vc, animated: true) + } private func showVPNResetErrorAlert() { let alert = UIAlertController(title: Strings.VPN.resetVPNErrorTitle,