summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authormojganii <mojgan.jelodar@codic.se>2024-08-28 11:56:03 +0200
committerBug Magnet <marco.nikic@mullvad.net>2024-08-28 14:28:09 +0200
commitcbce176ba180f9ce42a89460946311e43cf0ffbb (patch)
tree9c46ebd43e72a79234bd644f3033cee0f4a78540
parent4f6e15103f2b0226a2bdae69b8fcd3f51aa99765 (diff)
downloadmullvadvpn-cbce176ba180f9ce42a89460946311e43cf0ffbb.tar.xz
mullvadvpn-cbce176ba180f9ce42a89460946311e43cf0ffbb.zip
Add UI for DAITA in VPN settings
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift9
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginContentView.swift3
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginViewController.swift11
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift27
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift51
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift1
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift5
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift193
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift6
-rw-r--r--ios/MullvadVPNUITests/AccountTests.swift4
-rw-r--r--ios/MullvadVPNUITests/Pages/DaitaPromptAlert.swift28
-rw-r--r--ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift36
-rw-r--r--ios/MullvadVPNUITests/SettingsMigrationTests.swift56
14 files changed, 314 insertions, 120 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 6419df3898..242db12863 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -909,6 +909,7 @@
F08B6B7D2C528C6300D0A121 /* PostQuantumKeyExchangingPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919762C453FAF00C301F3 /* PostQuantumKeyExchangingPipeline.swift */; };
F08B6B7E2C528C6300D0A121 /* MultiHopPostQuantumKeyExchanging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F059197C2C454C9200C301F3 /* MultiHopPostQuantumKeyExchanging.swift */; };
F08B6B822C52931600D0A121 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = F08B6B812C52931600D0A121 /* WireGuardKitTypes */; };
+ F09084682C6E88ED001CD36E /* DaitaPromptAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09084672C6E88ED001CD36E /* DaitaPromptAlert.swift */; };
F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */; };
F09A297C2A9F8A9B00EA3B6F /* VoucherTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */; };
F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */; };
@@ -2106,6 +2107,7 @@
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextFormatterTests.swift; sourceTree = "<group>"; };
F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherOperation.swift; sourceTree = "<group>"; };
F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredDeviceInAppNotificationProvider.swift; sourceTree = "<group>"; };
+ F09084672C6E88ED001CD36E /* DaitaPromptAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaitaPromptAlert.swift; sourceTree = "<group>"; };
F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogoutDialogueView.swift; sourceTree = "<group>"; };
F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoucherTextField.swift; sourceTree = "<group>"; };
F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherContentView.swift; sourceTree = "<group>"; };
@@ -3932,6 +3934,7 @@
85021CAD2BDBC4290098B400 /* AppLogsPage.swift */,
8587A05C2B84D43100152938 /* ChangeLogAlert.swift */,
A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */,
+ F09084672C6E88ED001CD36E /* DaitaPromptAlert.swift */,
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */,
852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */,
8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */,
@@ -6067,6 +6070,7 @@
85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */,
A9DF789D2B7D1E8B0094E4AD /* LoggedInWithTimeUITestCase.swift in Sources */,
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */,
+ F09084682C6E88ED001CD36E /* DaitaPromptAlert.swift in Sources */,
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */,
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index d434d39b71..c673fdfe57 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -89,8 +89,8 @@ public enum AccessibilityIdentifier: String {
case cityLocationCell
case relayLocationCell
case customListLocationCell
- case multihopConfirmAlertBackButton
- case multihopConfirmAlertEnableButton
+ case daitaConfirmAlertBackButton
+ case daitaConfirmAlertEnableButton
// Labels
case accountPageDeviceNameLabel
@@ -190,6 +190,10 @@ public enum AccessibilityIdentifier: String {
case dnsServer
case dnsServerInfo
+ // DAITA
+ case daitaSwitch
+ case daitaPromptAlert
+
// Quantum resistance
case quantumResistanceAutomatic
case quantumResistanceOff
@@ -197,7 +201,6 @@ public enum AccessibilityIdentifier: String {
// Multihop
case multihopSwitch
- case multihopPromptAlert
// Error
case unknown
diff --git a/ios/MullvadVPN/View controllers/Login/LoginContentView.swift b/ios/MullvadVPN/View controllers/Login/LoginContentView.swift
index 5e1bfec865..209b465528 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginContentView.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginContentView.swift
@@ -43,6 +43,7 @@ class LoginContentView: UIView {
let statusActivityView: StatusActivityView = {
let statusActivityView = StatusActivityView(state: .hidden)
statusActivityView.translatesAutoresizingMaskIntoConstraints = false
+ statusActivityView.clipsToBounds = true
return statusActivityView
}()
@@ -151,6 +152,8 @@ class LoginContentView: UIView {
createAccountButton.pinEdges(.all().excluding(.top), to: footerContainer.layoutMarginsGuide)
statusActivityView.centerXAnchor.constraint(equalTo: contentContainer.centerXAnchor)
+ statusActivityView.widthAnchor.constraint(equalToConstant: 60.0)
+ statusActivityView.heightAnchor.constraint(equalTo: statusActivityView.widthAnchor, multiplier: 1.0)
formContainer.topAnchor.constraint(equalTo: statusActivityView.bottomAnchor, constant: 30)
formContainer.centerYAnchor.constraint(equalTo: contentContainer.centerYAnchor, constant: -20)
diff --git a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
index bf6c2d1401..af9698f89c 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
@@ -256,17 +256,6 @@ class LoginViewController: UIViewController, RootContainment {
private func updateStatusIcon() {
contentView.statusActivityView.state = loginState.statusActivityState
-
- switch loginState {
- case .authenticating:
- contentView.statusActivityView.accessibilityIdentifier = .loginStatusIconAuthenticating
- case .failure:
- contentView.statusActivityView.accessibilityIdentifier = .loginStatusIconFailure
- case .success:
- contentView.statusActivityView.accessibilityIdentifier = .loginStatusIconSuccess
- default:
- break
- }
}
private func beginLogin(_ action: LoginAction) {
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
index 0817c8ad06..42f1c29afc 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
@@ -15,6 +15,7 @@ protocol VPNSettingsCellEventHandler {
func selectCustomPortEntry(_ port: UInt16) -> Bool
func selectObfuscationState(_ state: WireGuardObfuscationState)
func switchMultihop(_ state: MultihopState)
+ func switchDaitaState(_ settings: DAITASettings)
}
final class VPNSettingsCellFactory: CellFactoryProtocol {
@@ -170,7 +171,6 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
)
cell.accessibilityIdentifier = "\(item.accessibilityIdentifier.rawValue)\(portString)"
cell.applySubCellStyling()
-
case .quantumResistanceAutomatic:
guard let cell = cell as? SelectableSettingsCell else { return }
@@ -206,13 +206,34 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()
- case .multihop:
+ case .daitaSwitch:
+ guard let cell = cell as? SettingsSwitchCell else { return }
+
+ cell.titleLabel.text = NSLocalizedString(
+ "DAITA_LABEL",
+ tableName: "VPNSettings",
+ value: "DAITA",
+ comment: ""
+ )
+ cell.accessibilityIdentifier = item.accessibilityIdentifier
+ cell.setOn(viewModel.daitaSettings.state.isEnabled, animated: false)
+
+ cell.infoButtonHandler = { [weak self] in
+ self?.delegate?.showInfo(for: .daita)
+ }
+
+ cell.action = { [weak self] isEnabled in
+ let state: DAITAState = isEnabled ? .on : .off
+ self?.delegate?.switchDaitaState(DAITASettings(state: state))
+ }
+
+ case .multihopSwitch:
guard let cell = cell as? SettingsSwitchCell else { return }
cell.titleLabel.text = NSLocalizedString(
"MULTIHOP_LABEL",
tableName: "VPNSettings",
- value: "Enable multihop",
+ value: "Multihop",
comment: ""
)
cell.accessibilityIdentifier = item.accessibilityIdentifier
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index 9a44019ddd..228ff6bfcb 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -24,6 +24,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscationPort
case quantumResistance
case multihop
+ case daita
var reusableViewClass: AnyClass {
switch self {
case .dnsSettings:
@@ -42,12 +43,14 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return SelectableSettingsCell.self
case .multihop:
return SettingsSwitchCell.self
+ case .daita:
+ return SettingsSwitchCell.self
}
}
}
private enum HeaderFooterReuseIdentifiers: String, CaseIterable {
- case wireGuardPortHeader
+ case settingsHeaderView
var reusableViewClass: AnyClass {
return SettingsHeaderView.self
@@ -61,7 +64,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscation
case wireGuardObfuscationPort
case quantumResistance
- case multiHop
+ case privacyAndSecurity
}
enum Item: Hashable {
@@ -76,7 +79,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case quantumResistanceAutomatic
case quantumResistanceOn
case quantumResistanceOff
- case multihop
+ case multihopSwitch
+ case daitaSwitch
static var wireGuardPorts: [Item] {
let defaultPorts = VPNSettingsViewModel.defaultWireGuardPorts.map {
@@ -121,7 +125,9 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return .quantumResistanceOn
case .quantumResistanceOff:
return .quantumResistanceOff
- case .multihop:
+ case .daitaSwitch:
+ return .daitaSwitch
+ case .multihopSwitch:
return .multihopSwitch
}
}
@@ -142,8 +148,10 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return .wireGuardObfuscationPort
case .quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff:
return .quantumResistance
- case .multihop:
+ case .multihopSwitch:
return .multihop
+ case .daitaSwitch:
+ return .daita
}
}
}
@@ -319,8 +327,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
guard let view = tableView
.dequeueReusableHeaderFooterView(
- withIdentifier: HeaderFooterReuseIdentifiers.wireGuardPortHeader
- .rawValue
+ withIdentifier: HeaderFooterReuseIdentifiers.settingsHeaderView.rawValue
) as? SettingsHeaderView else { return nil }
switch sectionIdentifier {
@@ -353,8 +360,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
let sectionIdentifier = snapshot().sectionIdentifiers[section]
switch sectionIdentifier {
- case .dnsSettings, .ipOverrides, .multiHop:
- return 0
+ case .dnsSettings, .ipOverrides, .privacyAndSecurity:
+ return .leastNonzeroMagnitude
default:
return tableView.estimatedRowHeight
}
@@ -375,7 +382,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
let sectionIdentifier = snapshot().sectionIdentifiers[indexPath.section]
return switch sectionIdentifier {
- case .multiHop: false
+ case .privacyAndSecurity: false
default: true
}
}
@@ -392,7 +399,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
tableView?.register(
SettingsHeaderView.self,
- forHeaderFooterViewReuseIdentifier: HeaderFooterReuseIdentifiers.wireGuardPortHeader.rawValue
+ forHeaderFooterViewReuseIdentifier: HeaderFooterReuseIdentifiers.settingsHeaderView.rawValue
)
}
@@ -412,7 +419,11 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
snapshot.appendItems([.dnsSettings], toSection: .dnsSettings)
snapshot.appendItems([.ipOverrides], toSection: .ipOverrides)
- snapshot.appendItems([.multihop], toSection: .multiHop)
+ #if DEBUG
+ snapshot.appendItems([.daitaSwitch, .multihopSwitch], toSection: .privacyAndSecurity)
+ #else
+ snapshot.appendItems([.multihopSwitch], toSection: .privacyAndSecurity)
+ #endif
applySnapshot(snapshot, animated: animated, completion: completion)
}
@@ -620,6 +631,22 @@ extension VPNSettingsDataSource: VPNSettingsCellEventHandler {
viewModel.setMultihop(state)
delegate?.didChangeViewModel(viewModel)
}
+
+ func switchDaitaState(_ settings: DAITASettings) {
+ if settings.state.isEnabled {
+ delegate?.showPrompt(for: .daita) { [weak self] in
+ guard let self else { return }
+ viewModel.setDAITASettings(settings)
+ delegate?.didChangeViewModel(viewModel)
+ } onDiscard: { [weak self] in
+ guard let self else { return }
+ tableView?.reloadData()
+ }
+ } else {
+ viewModel.setDAITASettings(settings)
+ delegate?.didChangeViewModel(viewModel)
+ }
+ }
}
// swiftlint:disable:this file_length
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift
index 48e952c7a6..91e2108fc2 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift
@@ -20,4 +20,5 @@ protocol VPNSettingsDataSourceDelegate: AnyObject {
func showDNSSettings()
func showIPOverrides()
func didSelectWireGuardPort(_ port: UInt16?)
+ func showPrompt(for: VPNSettingsPromptAlertItem, onSave: @escaping () -> Void, onDiscard: @escaping () -> Void)
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
index a6fb585b33..978ccdce86 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
@@ -14,4 +14,9 @@ enum VPNSettingsInfoButtonItem {
case wireGuardObfuscationPort
case quantumResistance
case multihop
+ case daita
+}
+
+enum VPNSettingsPromptAlertItem {
+ case daita
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
index a0833d7c43..8558924c1b 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
@@ -115,83 +115,93 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
)),
.quantumResistance(viewModel.quantumResistance),
.multihop(viewModel.multihopState),
+ .daita(viewModel.daitaSettings),
]
)
}
// swiftlint:disable:next function_body_length
- func showInfo(for item: VPNSettingsInfoButtonItem) {
- var message = ""
-
- switch item {
- case .wireGuardPorts:
- let portsString = humanReadablePortRepresentation(
- interactor.cachedRelays?.relays.wireguard.portRanges ?? []
- )
-
- message = String(
- format: NSLocalizedString(
- "VPN_SETTINGS_WIRE_GUARD_PORTS_GENERAL",
- tableName: "WireGuardPorts",
- value: """
- The automatic setting will randomly choose from the valid port ranges shown below.
- The custom port can be any value inside the valid ranges:
- %@
- """,
- comment: ""
- ),
- portsString
- )
+ func showInfo(for item: VPNSettingsInfoButtonItem) { switch item {
+ case .wireGuardPorts:
+ let portsString = humanReadablePortRepresentation(
+ interactor.cachedRelays?.relays.wireguard.portRanges ?? []
+ )
- case .wireGuardObfuscation:
- message = NSLocalizedString(
- "VPN_SETTINGS_WIRE_GUARD_OBFUSCATION_GENERAL",
- tableName: "WireGuardObfuscation",
+ showInfo(with: String(
+ format: NSLocalizedString(
+ "VPN_SETTINGS_WIRE_GUARD_PORTS_GENERAL",
+ tableName: "WireGuardPorts",
value: """
- Obfuscation hides the WireGuard traffic inside another protocol. \
- It can be used to help circumvent censorship and other types of filtering, \
- where a plain WireGuard connect would be blocked.
+ The automatic setting will randomly choose from the valid port ranges shown below.
+ The custom port can be any value inside the valid ranges:
+ %@
""",
comment: ""
- )
-
- case .wireGuardObfuscationPort:
- message = NSLocalizedString(
- "VPN_SETTINGS_WIRE_GUARD_OBFUSCATION_PORT_GENERAL",
- tableName: "WireGuardObfuscation",
- value: "Which TCP port the UDP-over-TCP obfuscation protocol should connect to on the VPN server.",
- comment: ""
- )
+ ),
+ portsString
+ ))
- case .quantumResistance:
- message = NSLocalizedString(
- "VPN_SETTINGS_QUANTUM_RESISTANCE_GENERAL",
- tableName: "QuantumResistance",
- value: """
- This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.
- It does this by performing an extra key exchange using a quantum safe algorithm and mixing \
- the result into WireGuard’s regular encryption.
- This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.
- """,
- comment: ""
- )
+ case .wireGuardObfuscation:
+ showInfo(with: NSLocalizedString(
+ "VPN_SETTINGS_WIRE_GUARD_OBFUSCATION_GENERAL",
+ tableName: "WireGuardObfuscation",
+ value: """
+ Obfuscation hides the WireGuard traffic inside another protocol. \
+ It can be used to help circumvent censorship and other types of filtering, \
+ where a plain WireGuard connect would be blocked.
+ """,
+ comment: ""
+ ))
- case .multihop:
- message = NSLocalizedString(
- "MULTIHOP_INFORMATION_TEXT",
- tableName: "Multihop",
- value: """
- Multihop routes your traffic into one WireGuard server and out another, making it harder to trace.
- This results in increased latency but increases anonymity online.
- """,
+ case .wireGuardObfuscationPort:
+ showInfo(with: NSLocalizedString(
+ "VPN_SETTINGS_WIRE_GUARD_OBFUSCATION_PORT_GENERAL",
+ tableName: "WireGuardObfuscation",
+ value: "Which TCP port the UDP-over-TCP obfuscation protocol should connect to on the VPN server.",
+ comment: ""
+ ))
- comment: ""
- )
- default:
- assertionFailure("No matching InfoButtonItem")
- }
+ case .quantumResistance:
+ showInfo(with: NSLocalizedString(
+ "VPN_SETTINGS_QUANTUM_RESISTANCE_GENERAL",
+ tableName: "QuantumResistance",
+ value: """
+ This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.
+ It does this by performing an extra key exchange using a quantum safe algorithm and mixing \
+ the result into WireGuard’s regular encryption.
+ This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.
+ """,
+ comment: ""
+ ))
- showInfo(with: message)
+ case .multihop:
+ showInfo(with: NSLocalizedString(
+ "MULTIHOP_INFORMATION_TEXT",
+ tableName: "Multihop",
+ value: """
+ Multihop routes your traffic into one WireGuard server and out another, making it harder to trace.
+ This results in increased latency but increases anonymity online.
+ """,
+ comment: ""
+ ))
+ case .daita:
+ showInfo(with: NSLocalizedString(
+ "DAITA_INFORMATION_TEXT",
+ tableName: "DAITA",
+ value: """
+ DAITA (Defence against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic. \
+ If anyone is monitoring your connection, this makes it significantly harder for them to identify\
+ what websites you are visiting.
+ It does this by carefully adding network noise and making all network packets the same size.
+ Attention: Since this increases your total network traffic,\
+ be cautious if you have a limited data plan.\
+ It can also negatively impact your network speed.
+ """,
+ comment: ""
+ ))
+ default:
+ assertionFailure("No matching InfoButtonItem")
+ }
}
func showDNSSettings() {
@@ -206,4 +216,59 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
func didSelectWireGuardPort(_ port: UInt16?) {
interactor.setPort(port)
}
+
+ func showPrompt(
+ for item: VPNSettingsPromptAlertItem,
+ onSave: @escaping () -> Void,
+ onDiscard: @escaping () -> Void
+ ) {
+ switch item {
+ case .daita:
+ let presentation = AlertPresentation(
+ id: "vpn-settings-content-blockers-alert",
+ accessibilityIdentifier: .daitaPromptAlert,
+ icon: .info,
+ message: NSLocalizedString(
+ "DAITA_INFORMATION_TEXT",
+ tableName: "DAITA",
+ value: """
+ This feature isn't available on all servers. \
+ You might need to change location after enabling.
+ Attention: Since this increases your total network traffic,\
+ be cautious if you have a limited data plan. It can also \
+ negatively impact your network speed. Please consider \
+ this if you want to enable DAITA.
+ """,
+ comment: ""
+ ),
+ buttons: [
+ AlertAction(
+ title: NSLocalizedString(
+ "VPN_SETTINGS_VPN_DAITA_OK_ACTION",
+ tableName: "DAITA",
+ value: "Enable anyway",
+ comment: ""
+ ),
+ style: .default,
+ accessibilityId: .daitaConfirmAlertEnableButton,
+ handler: {
+ onSave()
+ }
+ ),
+ AlertAction(
+ title: NSLocalizedString(
+ "VPN_SETTINGS_VPN_DAITA_CANCEL_ACTION",
+ tableName: "DAITA",
+ value: "Back",
+ comment: ""
+ ),
+ style: .default, handler: {
+ onDiscard()
+ }
+ ),
+ ]
+ )
+ alertPresenter.showAlert(presentation: presentation, animated: true)
+ }
+ }
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift
index 85993c3dee..94fde408b6 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift
@@ -100,6 +100,7 @@ struct VPNSettingsViewModel: Equatable {
private(set) var quantumResistance: TunnelQuantumResistance
private(set) var multihopState: MultihopState
+ private(set) var daitaSettings: DAITASettings
static let defaultWireGuardPorts: [UInt16] = [51820, 53]
@@ -159,6 +160,10 @@ struct VPNSettingsViewModel: Equatable {
multihopState = newState
}
+ mutating func setDAITASettings(_ newSettings: DAITASettings) {
+ daitaSettings = newSettings
+ }
+
/// Precondition for enabling Custom DNS.
var customDNSPrecondition: CustomDNSPrecondition {
if blockAdvertising || blockTracking || blockMalware ||
@@ -207,6 +212,7 @@ struct VPNSettingsViewModel: Equatable {
quantumResistance = tunnelSettings.tunnelQuantumResistance
multihopState = tunnelSettings.tunnelMultihopState
+ daitaSettings = tunnelSettings.daita
}
/// Produce merged view model keeping entry `identifier` for matching DNS entries.
diff --git a/ios/MullvadVPNUITests/AccountTests.swift b/ios/MullvadVPNUITests/AccountTests.swift
index 617b2b3ce1..8b6814b4d0 100644
--- a/ios/MullvadVPNUITests/AccountTests.swift
+++ b/ios/MullvadVPNUITests/AccountTests.swift
@@ -72,12 +72,12 @@ class AccountTests: LoggedOutUITestCase {
var retryCount = 0
let maxRetryCount = 3
- LoginPage(app)
+ let loginPage = LoginPage(app)
.tapAccountNumberTextField()
.enterText(hasTimeAccountNumber)
repeat {
- successIconShown = LoginPage(app)
+ successIconShown = loginPage
.tapAccountNumberSubmitButton()
.getSuccessIconShown()
diff --git a/ios/MullvadVPNUITests/Pages/DaitaPromptAlert.swift b/ios/MullvadVPNUITests/Pages/DaitaPromptAlert.swift
new file mode 100644
index 0000000000..beea7f6b02
--- /dev/null
+++ b/ios/MullvadVPNUITests/Pages/DaitaPromptAlert.swift
@@ -0,0 +1,28 @@
+//
+// DaitaPromptAlert.swift
+// MullvadVPNUITests
+//
+// Created by Mojgan on 2024-08-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+import Foundation
+import XCTest
+
+class DaitaPromptAlert: Page {
+ @discardableResult override init(_ app: XCUIApplication) {
+ super.init(app)
+
+ self.pageElement = app.otherElements[.daitaPromptAlert]
+ waitForPageToBeShown()
+ }
+
+ @discardableResult func tapEnableAnyway() -> Self {
+ app.buttons[AccessibilityIdentifier.daitaConfirmAlertEnableButton].tap()
+ return self
+ }
+
+ @discardableResult func tapBack() -> Self {
+ app.buttons[AccessibilityIdentifier.daitaConfirmAlertBackButton].tap()
+ return self
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift b/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
index 5746ce7ce0..cc4d6e74d7 100644
--- a/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
+++ b/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
@@ -126,6 +126,21 @@ class VPNSettingsPage: Page {
return self
}
+ @discardableResult func tapDaitaSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.daitaSwitch]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+ let promptIsShown = app
+ .otherElements[AccessibilityIdentifier.daitaPromptAlert.rawValue]
+ .waitForExistence(timeout: 1.0)
+
+ if promptIsShown {
+ DaitaPromptAlert(app)
+ .tapEnableAnyway()
+ }
+ return self
+ }
+
@discardableResult func verifyCustomWireGuardPortSelected(portNumber: String) -> Self {
let cell = app.cells[AccessibilityIdentifier.wireGuardCustomPort]
XCTAssertTrue(cell.isSelected)
@@ -158,8 +173,27 @@ class VPNSettingsPage: Page {
return self
}
+ @discardableResult func verifyQuantumResistantTunnelOnSelected() -> Self {
+ let cell = app.cells[AccessibilityIdentifier.quantumResistanceOn]
+ XCTAssertTrue(cell.isSelected)
+ return self
+ }
+
@discardableResult func verifyMultihopSwitchOn() -> Self {
- let switchElement = app.cells[AccessibilityIdentifier.multihopSwitch]
+ let switchElement = app.cells[.multihopSwitch]
+ .switches[AccessibilityIdentifier.customSwitch]
+
+ guard let switchValue = switchElement.value as? String else {
+ XCTFail("Failed to read switch state")
+ return self
+ }
+
+ XCTAssertEqual(switchValue, "1")
+ return self
+ }
+
+ @discardableResult func verifyDaitaSwitchOn() -> Self {
+ let switchElement = app.cells[.daitaSwitch]
.switches[AccessibilityIdentifier.customSwitch]
guard let switchValue = switchElement.value as? String else {
diff --git a/ios/MullvadVPNUITests/SettingsMigrationTests.swift b/ios/MullvadVPNUITests/SettingsMigrationTests.swift
index f9898f703b..85ab71bafa 100644
--- a/ios/MullvadVPNUITests/SettingsMigrationTests.swift
+++ b/ios/MullvadVPNUITests/SettingsMigrationTests.swift
@@ -9,6 +9,15 @@
import Foundation
import XCTest
+/*
+ Settings migration is an exception, it uses four different test plans and a separate workflow
+ `ios-end-to-end-tests-settings-migration.yml` which executes the test plans in order,
+ do not reinstall the app in between runs but upgrades the app after changing settings:
+ * `MullvadVPNUITestsChangeDNSSettings` - Change settings for using custom DNS
+ * `MullvadVPNUITestsVerifyDNSSettingsChanged` - Verify custom DNS settings still changed
+ * `MullvadVPNUITestsChangeSettings` - Change all settings except custom DNS setting
+ * `MullvadVPNUITestsVerifySettingsChanged` - Verify all settings except custom DNS setting still changed
+ */
class SettingsMigrationTests: BaseUITestCase {
let customDNSServerIPAddress = "123.123.123.123"
let wireGuardPort = "4001"
@@ -57,7 +66,22 @@ class SettingsMigrationTests: BaseUITestCase {
.tapDoneButton()
}
- func testChangeSettings() {
+ func testVerifyCustomDNSSettingsStillChanged() {
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
+ .tapDNSSettingsCell()
+
+ DNSSettingsPage(app)
+ .verifyUseCustomDNSSwitchOn()
+ .verifyCustomDNSIPAddress(customDNSServerIPAddress)
+ }
+
+ func testChangeVPNSettings() {
let hasTimeAccountNumber = getAccountWithTime()
addTeardownBlock {
@@ -81,8 +105,8 @@ class SettingsMigrationTests: BaseUITestCase {
.tapBlockAdsSwitch()
.tapBlockTrackerSwitch()
.tapBlockMalwareSwitch()
- .tapBlockAdultContentSwitch()
.tapBlockGamblingSwitch()
+ .tapBlockAdultContentSwitch()
.tapBlockSocialMediaSwitch()
.tapBackButton()
@@ -91,31 +115,16 @@ class SettingsMigrationTests: BaseUITestCase {
.tapCustomWireGuardPortTextField()
.enterText(wireGuardPort)
.dismissKeyboard()
- .tapWireGuardPortsExpandButton()
.tapWireGuardObfuscationExpandButton()
.tapWireGuardObfuscationOnCell()
- .tapWireGuardObfuscationExpandButton()
.tapUDPOverTCPPortExpandButton()
.tapUDPOverTCPPort80Cell()
- .tapUDPOverTCPPortExpandButton()
+ .tapQuantumResistantTunnelExpandButton()
+ .tapQuantumResistantTunnelOnCell()
+ .tapDaitaSwitch()
.tapMultihopSwitch()
}
- func testVerifyCustomDNSSettingsStillChanged() {
- HeaderBar(app)
- .tapSettingsButton()
-
- SettingsPage(app)
- .tapVPNSettingsCell()
-
- VPNSettingsPage(app)
- .tapDNSSettingsCell()
-
- DNSSettingsPage(app)
- .verifyUseCustomDNSSwitchOn()
- .verifyCustomDNSIPAddress(customDNSServerIPAddress)
- }
-
func testVerifySettingsStillChanged() {
HeaderBar(app)
.tapSettingsButton()
@@ -139,14 +148,13 @@ class SettingsMigrationTests: BaseUITestCase {
VPNSettingsPage(app)
.tapWireGuardPortsExpandButton()
.verifyCustomWireGuardPortSelected(portNumber: wireGuardPort)
- .tapWireGuardPortsExpandButton()
.tapWireGuardObfuscationExpandButton()
- .tapWireGuardObfuscationOnCell()
.verifyWireGuardObfuscationOnSelected()
- .tapWireGuardObfuscationExpandButton()
.tapUDPOverTCPPortExpandButton()
.verifyUDPOverTCPPort80Selected()
+ .tapQuantumResistantTunnelExpandButton()
+ .verifyQuantumResistantTunnelOnSelected()
+ .verifyDaitaSwitchOn()
.verifyMultihopSwitchOn()
- .tapBackButton()
}
}