Skip to content

Commit

Permalink
Add multi-hop toggle to settings view
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii committed May 15, 2024
1 parent b09ebf9 commit 004a5bf
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 20 deletions.
6 changes: 6 additions & 0 deletions ios/MullvadSettings/QuantumResistanceSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
//

import Foundation
// FIXME: Remove MultihopState when upgrading is merged
public enum MultihopState: Codable {
case automatic
case on
case off
}

public enum TunnelQuantumResistance: Codable {
case automatic
Expand Down
5 changes: 5 additions & 0 deletions ios/MullvadSettings/TunnelSettingsUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum TunnelSettingsUpdate {
case obfuscation(WireGuardObfuscationSettings)
case relayConstraints(RelayConstraints)
case quantumResistance(TunnelQuantumResistance)
case multihop(MultihopState)
}

extension TunnelSettingsUpdate {
Expand All @@ -27,6 +28,9 @@ extension TunnelSettingsUpdate {
settings.relayConstraints = newRelayConstraints
case let .quantumResistance(newQuantumResistance):
settings.tunnelQuantumResistance = newQuantumResistance
case let .multihop(newMultihopState):
// TODO: update setting with the new state
return
}
}

Expand All @@ -36,6 +40,7 @@ extension TunnelSettingsUpdate {
case .obfuscation: "obfuscation settings"
case .relayConstraints: "relay constraints"
case .quantumResistance: "quantum resistance"
case .multihop: "multihop"
}
}
}
6 changes: 6 additions & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public enum AccessibilityIdentifier: String {
case cityLocationCell
case relayLocationCell
case customListLocationCell
case multihopPromptAlertBackButton
case multihopPromptAlertEnableButton

// Labels
case accountPageDeviceNameLabel
Expand Down Expand Up @@ -183,6 +185,10 @@ public enum AccessibilityIdentifier: String {
case quantumResistanceOff
case quantumResistanceOn

// Multihop
case multihopEnableSwitch
case multihopPromptAlert

// Error
case unknown
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
private let cellFactory: CustomDNSCellFactory
private weak var tableView: UITableView?

weak var delegate: VPNSettingsDataSourceDelegate?
weak var delegate: DnsSettingsDataSourceDelegate?

init(tableView: UITableView) {
self.tableView = tableView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import MullvadSettings
import UIKit

class CustomDNSViewController: UITableViewController, VPNSettingsDataSourceDelegate {
class CustomDNSViewController: UITableViewController {
private let interactor: VPNSettingsInteractor
private var dataSource: CustomDNSDataSource?
private let alertPresenter: AlertPresenter
Expand Down Expand Up @@ -93,9 +93,9 @@ class CustomDNSViewController: UITableViewController, VPNSettingsDataSourceDeleg

alertPresenter.showAlert(presentation: presentation, animated: true)
}
}

// MARK: - VPNSettingsDataSourceDelegate

extension CustomDNSViewController: DnsSettingsDataSourceDelegate {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel) {
interactor.updateSettings([.dnsSettings(viewModel.asDNSSettings())])
}
Expand Down Expand Up @@ -133,16 +133,4 @@ class CustomDNSViewController: UITableViewController, VPNSettingsDataSourceDeleg

showInfo(with: message)
}

func showDNSSettings() {
// No op.
}

func showIPOverrides() {
// No op.
}

func didSelectWireGuardPort(_ port: UInt16?) {
// No op.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protocol VPNSettingsCellEventHandler {
func addCustomPort(_ port: UInt16)
func selectCustomPortEntry(_ port: UInt16) -> Bool
func selectObfuscationState(_ state: WireGuardObfuscationState)
func switchMultihop(_ state: MultihopState, _ discardHandler: @escaping () -> Void)
}

final class VPNSettingsCellFactory: CellFactoryProtocol {
Expand Down Expand Up @@ -206,6 +207,24 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()
#endif

case .multihop:
guard let cell = cell as? SettingsSwitchCell else { return }

cell.titleLabel.text = NSLocalizedString(
"MULTIHOP_LABEL",
tableName: "VPNSettings",
value: "Enable multihop",
comment: ""
)
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.setOn(viewModel.multihopState == .on, animated: false)

cell.action = { [weak self] value in
self?.delegate?.switchMultihop(value ? .on : .off) {
cell.setOn(false, animated: true)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscation
case wireGuardObfuscationPort
case quantumResistance
case multihop
var reusableViewClass: AnyClass {
switch self {
case .dnsSettings:
Expand All @@ -39,6 +40,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return SelectableSettingsCell.self
case .quantumResistance:
return SelectableSettingsCell.self
case .multihop:
return SettingsSwitchCell.self
}
}
}
Expand All @@ -60,6 +63,10 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
#if DEBUG
case quantumResistance
#endif

#if DEBUG
case multiHop
#endif
}

enum Item: Hashable {
Expand All @@ -76,6 +83,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case quantumResistanceOn
case quantumResistanceOff
#endif
case multihop

static var wireGuardPorts: [Item] {
let defaultPorts = VPNSettingsViewModel.defaultWireGuardPorts.map {
Expand Down Expand Up @@ -124,6 +132,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .quantumResistanceOff:
return .quantumResistanceOff
#endif
case .multihop:
return .multihopEnableSwitch
}
}

Expand All @@ -145,6 +155,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff:
return .quantumResistance
#endif
case .multihop:
return .multihop
}
}
}
Expand Down Expand Up @@ -417,6 +429,10 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
snapshot.appendItems([.dnsSettings], toSection: .dnsSettings)
snapshot.appendItems([.ipOverrides], toSection: .ipOverrides)

#if DEBUG
snapshot.appendItems([.multihop], toSection: .multiHop)
#endif

applySnapshot(snapshot, animated: animated, completion: completion)
}

Expand Down Expand Up @@ -620,6 +636,21 @@ extension VPNSettingsDataSource: VPNSettingsCellEventHandler {
func selectQuantumResistance(_ state: TunnelQuantumResistance) {
viewModel.setQuantumResistance(state)
}

func switchMultihop(_ state: MultihopState, _ discardHandler: @escaping () -> Void) {
if viewModel.multihopState == .off {
delegate?.showMultihopConfirmation({ [weak self] in
guard let self else { return }
viewModel.setMultihop(.on)
delegate?.didChangeViewModel(viewModel)
}, {
discardHandler()
})
} else {
viewModel.setMultihop(.off)
delegate?.didChangeViewModel(viewModel)
}
}
}

// swiftlint:disable:this file_length
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@
//

import Foundation
import MullvadSettings

protocol DnsSettingsDataSourceDelegate: AnyObject {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel)
func showInfo(for: VPNSettingsInfoButtonItem)
}

protocol VPNSettingsDataSourceDelegate: AnyObject {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel)
func showInfo(for: VPNSettingsInfoButtonItem)
func showDNSSettings()
func showIPOverrides()
func didSelectWireGuardPort(_ port: UInt16?)
func showMultihopConfirmation(_ onSave: @escaping () -> Void, _ onDiscard: @escaping () -> Void)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ protocol VPNSettingsViewControllerDelegate: AnyObject {
func showIPOverrides()
}

class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDelegate {
class VPNSettingsViewController: UITableViewController {
private let interactor: VPNSettingsInteractor
private var dataSource: VPNSettingsDataSource?
private let alertPresenter: AlertPresenter
Expand Down Expand Up @@ -104,9 +104,9 @@ class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDel
}
.joined(separator: ", ")
}
}

// MARK: - VPNSettingsDataSourceDelegate

extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel) {
interactor.updateSettings(
[
Expand All @@ -115,6 +115,7 @@ class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDel
port: viewModel.obfuscationPort
)),
.quantumResistance(viewModel.quantumResistance),
.multihop(viewModel.multihopState),
]
)
}
Expand Down Expand Up @@ -175,7 +176,6 @@ class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDel
""",
comment: ""
)

default:
assertionFailure("No matching InfoButtonItem")
}
Expand All @@ -195,4 +195,48 @@ class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDel
func didSelectWireGuardPort(_ port: UInt16?) {
interactor.setPort(port)
}

func showMultihopConfirmation(_ onSave: @escaping () -> Void, _ onDiscard: @escaping () -> Void) {
let presentation = AlertPresentation(
id: "multihop-prompt-alert",
accessibilityIdentifier: .multihopPromptAlert,
icon: .info,
message: NSLocalizedString(
"MULTIHOP_PROMPT_ALERT_TEXT",
tableName: "Multihop",
value: "This setting increases latency. Use only if needed.",
comment: ""
),
buttons: [
AlertAction(
title: NSLocalizedString(
"MULTIHOP_PROMPT_ALERT_ENABLE_BUTTON",
tableName: "Multihop",
value: "Enable anyway",
comment: ""
),
style: .destructive,
accessibilityId: .multihopPromptAlertEnableButton,
handler: {
onSave()
}
),
AlertAction(
title: NSLocalizedString(
"MULTIHOP_PROMPT_ALERT_BACK_BUTTON",
tableName: "Multihop",
value: "Back",
comment: ""
),
style: .default,
accessibilityId: .multihopPromptAlertBackButton,
handler: {
onDiscard()
}
),
]
)

alertPresenter.showAlert(presentation: presentation, animated: true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ struct VPNSettingsViewModel: Equatable {
private(set) var obfuscationPort: WireGuardObfuscationPort

private(set) var quantumResistance: TunnelQuantumResistance
private(set) var multihopState: MultihopState

static let defaultWireGuardPorts: [UInt16] = [51820, 53]

Expand Down Expand Up @@ -154,6 +155,10 @@ struct VPNSettingsViewModel: Equatable {
quantumResistance = newState
}

mutating func setMultihop(_ newState: MultihopState) {
multihopState = newState
}

/// Precondition for enabling Custom DNS.
var customDNSPrecondition: CustomDNSPrecondition {
if blockAdvertising || blockTracking || blockMalware ||
Expand Down Expand Up @@ -201,6 +206,7 @@ struct VPNSettingsViewModel: Equatable {
obfuscationPort = tunnelSettings.wireGuardObfuscation.port

quantumResistance = tunnelSettings.tunnelQuantumResistance
multihopState = tunnelSettings.tunnelMultihopState
}

/// Produce merged view model keeping entry `identifier` for matching DNS entries.
Expand Down

0 comments on commit 004a5bf

Please sign in to comment.