Skip to content

Commit

Permalink
Add the multihop page to the root of the settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
rablador committed Nov 28, 2024
1 parent c4f7af2 commit f5ee0e7
Show file tree
Hide file tree
Showing 61 changed files with 955 additions and 59 deletions.
18 changes: 14 additions & 4 deletions ios/MullvadSettings/DAITASettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ public enum DAITAState: Codable {
case off

public var isEnabled: Bool {
self == .on
get {
self == .on
}
set {
self = newValue ? .on : .off
}
}
}

Expand All @@ -24,7 +29,12 @@ public enum DirectOnlyState: Codable {
case off

public var isEnabled: Bool {
self == .on
get {
self == .on
}
set {
self = newValue ? .on : .off
}
}
}

Expand All @@ -37,8 +47,8 @@ public struct DAITASettings: Codable, Equatable {
@available(*, deprecated, renamed: "daitaState")
public let state: DAITAState = .off

public let daitaState: DAITAState
public let directOnlyState: DirectOnlyState
public var daitaState: DAITAState
public var directOnlyState: DirectOnlyState

public var isAutomaticRouting: Bool {
daitaState.isEnabled && !directOnlyState.isEnabled
Expand Down
9 changes: 7 additions & 2 deletions ios/MullvadSettings/MultihopSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
import Foundation
import MullvadTypes

/// Whether Multi-hop is enabled
/// Whether Multihop is enabled
public enum MultihopState: Codable {
case on
case off

public var isEnabled: Bool {
self == .on
get {
self == .on
}
set {
self = newValue ? .on : .off
}
}
}
84 changes: 84 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public enum AccessibilityIdentifier: String {
case customListLocationCell
case daitaConfirmAlertBackButton
case daitaConfirmAlertEnableButton
case multihopCell
case daitaCell

// Labels
case accountPageDeviceNameLabel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// DAITATunnelSettingsObserver.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-21.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings

class DAITATunnelSettingsObserver: ObservableObject {
private let tunnelManager: TunnelManager
private var tunnelObserver: TunnelObserver?

var value: DAITASettings {
willSet(newValue) {
guard newValue != self.value else { return }

objectWillChange.send()
tunnelManager.updateSettings([.daita(newValue)])
}
}

init(tunnelManager: TunnelManager) {
self.tunnelManager = tunnelManager
value = tunnelManager.settings.daita

tunnelObserver = TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in
self?.value = newSettings.daita
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// DAITATunnelSettingsObservable.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-21.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings

protocol DAITATunnelSettingsObservable: ObservableObject {
var value: DAITASettings { get set }
}

class MockDAITATunnelSettingsViewModel: DAITATunnelSettingsObservable {
@Published var value: DAITASettings

init(daitaSettings: DAITASettings = DAITASettings()) {
value = daitaSettings
}
}

class DAITATunnelSettingsViewModel: DAITATunnelSettingsObserver, DAITATunnelSettingsObservable {}
106 changes: 106 additions & 0 deletions ios/MullvadVPN/Coordinators/Settings/DAITA/SettingsDAITAView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// SettingsDAITAView.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-14.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import SwiftUI

struct SettingsDAITAView<VM>: View where VM: DAITATunnelSettingsObservable {
@StateObject var tunnelViewModel: VM

private let dataViewModel = SettingsInfoViewModel(
pages: [
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_DAITA_PAGE_1",
tableName: "Settings",
value: """
DAITA (Defense against AI-guided Traffic Analysis) hides patterns in \
your encrypted VPN traffic.
By using sophisticated AI it’s possible to analyze the traffic of data \
packets going in and out of your device (even if the traffic is encrypted).
If an observer monitors these data packets, DAITA makes it significantly \
harder for them to identify which websites you are visiting or with whom \
you are communicating.
""",
comment: ""
),
image: .daitaOffIllustration
),
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_DAITA_PAGE_2",
tableName: "Settings",
value: """
DAITA does this by carefully adding network noise and making all network \
packets the same size.
Not all our servers are DAITA-enabled. Therefore, we use multihop \
automatically to enable DAITA with any server.
Attention: Be cautious if you have a limited data plan as this feature \
will increase your network traffic.
""",
comment: ""
),
image: .daitaOnIllustration
)
]
)

var body: some View {
SettingsInfoContainerView {
VStack(alignment: .leading, spacing: 8) {
SettingsInfoView(viewModel: dataViewModel)

VStack {
GroupedRowView {
SwitchRowView(
enabled: $tunnelViewModel.value.daitaState.isEnabled,
text: NSLocalizedString(
"SETTINGS_SWITCH_DAITA_ENABLE",
tableName: "Settings",
value: "Enable",
comment: ""
)
)
RowSeparator()
SwitchRowView(
enabled: $tunnelViewModel.value.directOnlyState.isEnabled,
text: NSLocalizedString(
"SETTINGS_SWITCH_DAITA_DIRECT_ONLY",
tableName: "Settings",
value: "Direct only",
comment: ""
)
)
}

SettingsRowViewFooter(
text: NSLocalizedString(
"SETTINGS_SWITCH_DAITA_ENABLE",
tableName: "Settings",
value: """
By enabling "Direct only" you will have to manually select a server that \
is DAITA-enabled. Multihop won't automatically be used to enable DAITA with \
any server.
""",
comment: ""
)
)
}
.padding(.leading, UIMetrics.contentInsets.left)
.padding(.trailing, UIMetrics.contentInsets.right)
}
}
}
}

#Preview {
SettingsDAITAView(tunnelViewModel: MockDAITATunnelSettingsViewModel())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// MultihopTunnelSettingsObserver.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-21.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings

class MultihopTunnelSettingsObserver: ObservableObject {
private let tunnelManager: TunnelManager
private var tunnelObserver: TunnelObserver?

var value: MultihopState {
willSet(newValue) {
guard newValue != value else { return }

objectWillChange.send()
tunnelManager.updateSettings([.multihop(newValue)])
}
}

init(tunnelManager: TunnelManager) {
self.tunnelManager = tunnelManager
value = tunnelManager.settings.tunnelMultihopState

tunnelObserver = TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in
self?.value = newSettings.tunnelMultihopState
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// MultihopTunnelSettingsViewModel.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-21.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings

protocol MultihopTunnelSettingsObservable: ObservableObject {
var value: MultihopState { get set }
}

class MockMultihopTunnelSettingsViewModel: MultihopTunnelSettingsObservable {
@Published var value: MultihopState

init(multihopState: MultihopState = .off) {
value = multihopState
}
}

class MultihopTunnelSettingsViewModel: MultihopTunnelSettingsObserver, MultihopTunnelSettingsObservable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// SettingsMultihopView.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-14.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Combine
import SwiftUI

struct SettingsMultihopView<VM>: View where VM: MultihopTunnelSettingsObservable {
@StateObject var tunnelViewModel: VM

private let viewModel = SettingsInfoViewModel(
pages: [
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_MULTIHOP",
tableName: "Settings",
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: ""
),
image: .multihopIllustration
)
]
)

var body: some View {
SettingsInfoContainerView {
VStack(alignment: .leading, spacing: 8) {
SettingsInfoView(viewModel: viewModel)

SwitchRowView(
enabled: $tunnelViewModel.value.isEnabled,
text: NSLocalizedString(
"SETTINGS_SWITCH_MULTIHOP",
tableName: "Settings",
value: "Enable",
comment: ""
)
)
.padding(.leading, UIMetrics.contentInsets.left)
.padding(.trailing, UIMetrics.contentInsets.right)
}
}
}
}

#Preview {
SettingsMultihopView(tunnelViewModel: MockMultihopTunnelSettingsViewModel())
}
Loading

0 comments on commit f5ee0e7

Please sign in to comment.