Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade the UX in Daita scenarios #6745

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ios/MullvadSettings/DAITASettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public enum DAITAState: Codable {
}
}

/// Selected relay is incompatible with Daita, either through singlehop or multihop.
public enum DAITASettingsCompatibilityError {
case singlehop, multihop
}

public struct DAITASettings: Codable, Equatable {
public let state: DAITAState

Expand Down
20 changes: 10 additions & 10 deletions ios/MullvadVPN/TunnelManager/TunnelManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,15 @@ final class TunnelManager: StorePaymentObserver {
try relayCacheTracker.refreshCachedRelays()
}

func selectRelays(tunnelSettings: LatestTunnelSettings) throws -> SelectedRelays {
let retryAttempts = tunnelStatus.observedState.connectionState?.connectionAttemptCount ?? 0

return try relaySelector.selectRelays(
tunnelSettings: tunnelSettings,
connectionAttemptCount: retryAttempts
)
}

// MARK: - Tunnel observeration

/// Add tunnel observer.
Expand Down Expand Up @@ -782,15 +791,6 @@ final class TunnelManager: StorePaymentObserver {
updateTunnelStatus(tunnel?.status ?? .disconnected)
}

fileprivate func selectRelays() throws -> SelectedRelays {
let retryAttempts = tunnelStatus.observedState.connectionState?.connectionAttemptCount ?? 0

return try relaySelector.selectRelays(
tunnelSettings: settings,
connectionAttemptCount: retryAttempts
)
}

fileprivate func prepareForVPNConfigurationDeletion() {
nslock.lock()
defer { nslock.unlock() }
Expand Down Expand Up @@ -1265,7 +1265,7 @@ private struct TunnelInteractorProxy: TunnelInteractor {
}

func selectRelays() throws -> SelectedRelays {
try tunnelManager.selectRelays()
try tunnelManager.selectRelays(tunnelSettings: tunnelManager.settings)
}

func handleRestError(_ error: Error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<

deselectAllRowsInSectionExceptRowAt(indexPath)

let obfuscationSettingsUpdate = TunnelSettingsUpdate.obfuscation(WireGuardObfuscationSettings(
state: viewModel.obfuscationState,
port: viewModel.obfuscationPort
))
let quantumResistanceUpdate = TunnelSettingsUpdate.quantumResistance(viewModel.quantumResistance)

switch item {
case .dnsSettings:
tableView.deselectRow(at: indexPath, animated: false)
Expand All @@ -285,26 +291,25 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<

case .wireGuardObfuscationAutomatic:
selectObfuscationState(.automatic)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(obfuscationSettingsUpdate)
case .wireGuardObfuscationOn:
selectObfuscationState(.on)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(obfuscationSettingsUpdate)
case .wireGuardObfuscationOff:
selectObfuscationState(.off)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(obfuscationSettingsUpdate)
case let .wireGuardObfuscationPort(port):
selectObfuscationPort(port)
delegate?.didChangeViewModel(viewModel)

delegate?.didUpdateTunnelSettings(obfuscationSettingsUpdate)
case .quantumResistanceAutomatic:
selectQuantumResistance(.automatic)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(quantumResistanceUpdate)
case .quantumResistanceOn:
selectQuantumResistance(.on)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(quantumResistanceUpdate)
case .quantumResistanceOff:
selectQuantumResistance(.off)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(quantumResistanceUpdate)
default:
break
}
Expand Down Expand Up @@ -642,22 +647,32 @@ extension VPNSettingsDataSource: VPNSettingsCellEventHandler {

func switchMultihop(_ state: MultihopState) {
viewModel.setMultihop(state)
delegate?.didChangeViewModel(viewModel)
delegate?.didUpdateTunnelSettings(.multihop(viewModel.multihopState))
}

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()
let updateSettings = { [weak self] in
self?.viewModel.setDAITASettings(settings)
self?.delegate?.didUpdateTunnelSettings(.daita(settings))
}

if let error = delegate?.didAttemptToChangeDaitaSettings(settings) {
switch error {
case .singlehop:
delegate?.showPrompt(for: .daitaSettingIncompatibleWithSinglehop) {
updateSettings()
} onDiscard: { [weak self] in
self?.tableView?.reloadData()
}
case .multihop:
delegate?.showPrompt(for: .daitaSettingIncompatibleWithMultihop) {
updateSettings()
} onDiscard: { [weak self] in
self?.tableView?.reloadData()
}
}
} else {
viewModel.setDAITASettings(settings)
delegate?.didChangeViewModel(viewModel)
updateSettings()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ protocol DNSSettingsDataSourceDelegate: AnyObject {
}

protocol VPNSettingsDataSourceDelegate: AnyObject {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel)
func didUpdateTunnelSettings(_ update: TunnelSettingsUpdate)
func didAttemptToChangeDaitaSettings(_ settings: DAITASettings) -> DAITASettingsCompatibilityError?
func showInfo(for: VPNSettingsInfoButtonItem)
func showDNSSettings()
func showIPOverrides()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ enum VPNSettingsInfoButtonItem {
}

enum VPNSettingsPromptAlertItem {
case daita
case daitaSettingIncompatibleWithSinglehop
case daitaSettingIncompatibleWithMultihop
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ final class VPNSettingsInteractor {

tunnelManager.updateSettings([.relayConstraints(relayConstraints)], completionHandler: completion)
}

func evaluateDaitaSettingsCompatibility(_ settings: DAITASettings) -> DAITASettingsCompatibilityError? {
guard settings.state.isEnabled else { return nil }

var tunnelSettings = tunnelSettings
tunnelSettings.daita = settings

let selectedRelays = try? tunnelManager.selectRelays(tunnelSettings: tunnelSettings)
let multihopEnabled = tunnelSettings.tunnelMultihopState.isEnabled

return if multihopEnabled {
selectedRelays?.entry == nil ? .multihop : nil
} else {
selectedRelays?.exit == nil ? .singlehop : nil
}
}
}

extension VPNSettingsInteractor: RelayCacheTrackerObserver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,8 @@ class VPNSettingsViewController: UITableViewController {
}

extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel) {
interactor.updateSettings(
[
.obfuscation(WireGuardObfuscationSettings(
state: viewModel.obfuscationState,
port: viewModel.obfuscationPort
)),
.quantumResistance(viewModel.quantumResistance),
.multihop(viewModel.multihopState),
.daita(viewModel.daitaSettings),
]
)
func didUpdateTunnelSettings(_ update: TunnelSettingsUpdate) {
interactor.updateSettings([update])
}

// swiftlint:disable:next function_body_length
Expand Down Expand Up @@ -217,58 +207,63 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
interactor.setPort(port)
}

func didAttemptToChangeDaitaSettings(_ settings: DAITASettings) -> DAITASettingsCompatibilityError? {
interactor.evaluateDaitaSettingsCompatibility(settings)
}

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()
}
let messageString = switch item {
case .daitaSettingIncompatibleWithSinglehop:
"""
DAITA isn’t available on the current server. After enabling, please go to \
the Switch location view and select a location that supports DAITA.
"""
case .daitaSettingIncompatibleWithMultihop:
"""
DAITA isn’t available on the current entry server. After enabling, please go to \
the Switch location view and select an entry location that supports DAITA.
"""
}

let presentation = AlertPresentation(
id: "vpn-settings-content-blockers-alert",
accessibilityIdentifier: .daitaPromptAlert,
icon: .info,
message: NSLocalizedString(
"DAITA_INFORMATION_TEXT",
tableName: "DAITA",
value: messageString,
comment: ""
),
buttons: [
AlertAction(
title: NSLocalizedString(
"VPN_SETTINGS_VPN_DAITA_OK_ACTION",
tableName: "DAITA",
value: "Enable anyway",
comment: ""
),
AlertAction(
title: NSLocalizedString(
"VPN_SETTINGS_VPN_DAITA_CANCEL_ACTION",
tableName: "DAITA",
value: "Back",
comment: ""
),
style: .default, handler: {
onDiscard()
}
style: .default,
accessibilityId: .daitaConfirmAlertEnableButton,
handler: { onSave() }
),
AlertAction(
title: NSLocalizedString(
"VPN_SETTINGS_VPN_DAITA_CANCEL_ACTION",
tableName: "DAITA",
value: "Back",
comment: ""
),
]
)
alertPresenter.showAlert(presentation: presentation, animated: true)
}
style: .default,
handler: { onDiscard() }
),
]
)

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