Skip to content

Commit 996efad

Browse files
committed
Add the DAITA page to the root of the settings page
1 parent 31648b9 commit 996efad

27 files changed

+547
-399
lines changed

ios/MullvadSettings/MultihopSettings.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import Foundation
1010
import MullvadTypes
1111

12-
/// Whether Multihop is enabled
12+
/// Whether multihop is enabled.
1313
public enum MultihopState: Codable {
1414
case on
1515
case off

ios/MullvadVPN.xcodeproj/project.pbxproj

+21-17
Original file line numberDiff line numberDiff line change
@@ -565,11 +565,12 @@
565565
7A8A190E2CEB77C1000BCB5B /* SettingsRowViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190D2CEB77B7000BCB5B /* SettingsRowViewFooter.swift */; };
566566
7A8A19102CEE391B000BCB5B /* RowSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */; };
567567
7A8A19122CEF1E68000BCB5B /* SettingsInfoContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19112CEF1E58000BCB5B /* SettingsInfoContainerView.swift */; };
568-
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsObserver.swift */; };
569-
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsObserver.swift */; };
570-
7A8A19182CEF27AB000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19172CEF279C000BCB5B /* DAITATunnelSettingsViewModel.swift */; };
568+
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */; };
569+
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */; };
571570
7A8A191A2CEF41AF000BCB5B /* GroupedRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19192CEF41AC000BCB5B /* GroupedRowView.swift */; };
572-
7A8A191C2CEF55E3000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A191B2CEF55DA000BCB5B /* MultihopTunnelSettingsViewModel.swift */; };
571+
7A8A191E2CEF5CF2000BCB5B /* TunnelSettingsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A191D2CEF5CDF000BCB5B /* TunnelSettingsObservable.swift */; };
572+
7A8A19242CF4C9BF000BCB5B /* MultihopPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */; };
573+
7A8A19262CF4D37B000BCB5B /* DAITAPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19252CF4D373000BCB5B /* DAITAPage.swift */; };
573574
7A9BE5A22B8F88C500E2A7D0 /* LocationNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */; };
574575
7A9BE5A32B8F89B900E2A7D0 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389F72B864CDF008E77E1 /* LocationNode.swift */; };
575576
7A9BE5A52B90760C00E2A7D0 /* CustomListsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */; };
@@ -1921,11 +1922,12 @@
19211922
7A8A190D2CEB77B7000BCB5B /* SettingsRowViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowViewFooter.swift; sourceTree = "<group>"; };
19221923
7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowSeparator.swift; sourceTree = "<group>"; };
19231924
7A8A19112CEF1E58000BCB5B /* SettingsInfoContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoContainerView.swift; sourceTree = "<group>"; };
1924-
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITATunnelSettingsObserver.swift; sourceTree = "<group>"; };
1925-
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopTunnelSettingsObserver.swift; sourceTree = "<group>"; };
1926-
7A8A19172CEF279C000BCB5B /* DAITATunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITATunnelSettingsViewModel.swift; sourceTree = "<group>"; };
1925+
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITATunnelSettingsViewModel.swift; sourceTree = "<group>"; };
1926+
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopTunnelSettingsViewModel.swift; sourceTree = "<group>"; };
19271927
7A8A19192CEF41AC000BCB5B /* GroupedRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedRowView.swift; sourceTree = "<group>"; };
1928-
7A8A191B2CEF55DA000BCB5B /* MultihopTunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopTunnelSettingsViewModel.swift; sourceTree = "<group>"; };
1928+
7A8A191D2CEF5CDF000BCB5B /* TunnelSettingsObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsObservable.swift; sourceTree = "<group>"; };
1929+
7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPage.swift; sourceTree = "<group>"; };
1930+
7A8A19252CF4D373000BCB5B /* DAITAPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITAPage.swift; sourceTree = "<group>"; };
19291931
7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationNodeTests.swift; sourceTree = "<group>"; };
19301932
7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSourceTests.swift; sourceTree = "<group>"; };
19311933
7A9BE5A82B90806800E2A7D0 /* CustomListsRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsRepositoryStub.swift; sourceTree = "<group>"; };
@@ -2887,7 +2889,6 @@
28872889
4424CDD12CDBD457009D8C9F /* SwiftUI components */,
28882890
4422C06F2CCFF6520001A385 /* Obfuscation */,
28892891
7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */,
2890-
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
28912892
7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */,
28922893
7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */,
28932894
5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */,
@@ -3825,6 +3826,7 @@
38253826
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
38263827
7A6389EC2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift */,
38273828
7A6389EA2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift */,
3829+
7A8A191D2CEF5CDF000BCB5B /* TunnelSettingsObservable.swift */,
38283830
);
38293831
path = Settings;
38303832
sourceTree = "<group>";
@@ -3978,8 +3980,7 @@
39783980
7A8A18F72CE34E8F000BCB5B /* Multihop */ = {
39793981
isa = PBXGroup;
39803982
children = (
3981-
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsObserver.swift */,
3982-
7A8A191B2CEF55DA000BCB5B /* MultihopTunnelSettingsViewModel.swift */,
3983+
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */,
39833984
7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */,
39843985
);
39853986
path = Multihop;
@@ -4000,8 +4001,8 @@
40004001
7A8A19082CE5FFD7000BCB5B /* DAITA */ = {
40014002
isa = PBXGroup;
40024003
children = (
4003-
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsObserver.swift */,
4004-
7A8A19172CEF279C000BCB5B /* DAITATunnelSettingsViewModel.swift */,
4004+
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
4005+
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */,
40054006
7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */,
40064007
);
40074008
path = DAITA;
@@ -4109,6 +4110,7 @@
41094110
85021CAD2BDBC4290098B400 /* AppLogsPage.swift */,
41104111
8587A05C2B84D43100152938 /* ChangeLogAlert.swift */,
41114112
A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */,
4113+
7A8A19252CF4D373000BCB5B /* DAITAPage.swift */,
41124114
F09084672C6E88ED001CD36E /* DaitaPromptAlert.swift */,
41134115
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */,
41144116
852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */,
@@ -4117,6 +4119,7 @@
41174119
85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */,
41184120
A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */,
41194121
852969342B4E9270007EAD4C /* LoginPage.swift */,
4122+
7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */,
41204123
85139B2C2B84B4A700734217 /* OutOfTimePage.swift */,
41214124
852969322B4E9232007EAD4C /* Page.swift */,
41224125
855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */,
@@ -5781,7 +5784,7 @@
57815784
7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */,
57825785
7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */,
57835786
587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */,
5784-
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsObserver.swift in Sources */,
5787+
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */,
57855788
7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */,
57865789
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */,
57875790
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */,
@@ -5932,7 +5935,6 @@
59325935
F0EF50D52A949F8E0031E8DF /* ChangeLogViewModel.swift in Sources */,
59335936
F0E8E4BB2A56C9F100ED26A3 /* WelcomeInteractor.swift in Sources */,
59345937
7A6000F62B60092F001CF0D9 /* AccessMethodViewModelEditing.swift in Sources */,
5935-
7A8A191C2CEF55E3000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */,
59365938
5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */,
59375939
58EF87572B16330B00C098B2 /* ProxyConfigurationTester.swift in Sources */,
59385940
5827B0A62B0F39E900CCBBA1 /* EditAccessMethodInteractor.swift in Sources */,
@@ -5960,6 +5962,7 @@
59605962
7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */,
59615963
582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */,
59625964
7AF9BE8E2A331C7B00DBFEDB /* RelayFilterViewModel.swift in Sources */,
5965+
7A8A191E2CEF5CF2000BCB5B /* TunnelSettingsObservable.swift in Sources */,
59635966
58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */,
59645967
5864AF0829C78849005B0CD9 /* CellFactoryProtocol.swift in Sources */,
59655968
7A6389E22B7E3BD6008E77E1 /* CustomListInteractor.swift in Sources */,
@@ -5968,7 +5971,6 @@
59685971
58CEB2F32AFD0BA100E6E088 /* TextCellContentView.swift in Sources */,
59695972
7A6389E72B7E42BE008E77E1 /* CustomListViewController.swift in Sources */,
59705973
586C0D7C2B03BDD100E7CDD7 /* AccessMethodViewModel.swift in Sources */,
5971-
7A8A19182CEF27AB000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */,
59725974
587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */,
59735975
7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */,
59745976
5819C2172729595500D6EC38 /* SettingsAddDNSEntryCell.swift in Sources */,
@@ -6002,7 +6004,7 @@
60026004
5892A45E265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift in Sources */,
60036005
580909D32876D09A0078138D /* RevokedDeviceViewController.swift in Sources */,
60046006
58FF9FF02B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift in Sources */,
6005-
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsObserver.swift in Sources */,
6007+
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */,
60066008
58CEB2FD2AFD19D300E6E088 /* UITableView+ReuseIdentifier.swift in Sources */,
60076009
F0FADDEA2BE90AAA000D0B02 /* LaunchArguments.swift in Sources */,
60086010
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */,
@@ -6317,6 +6319,7 @@
63176319
85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */,
63186320
852969352B4E9270007EAD4C /* LoginPage.swift in Sources */,
63196321
A998DA832BD2B055001D61A2 /* EditCustomListLocationsPage.swift in Sources */,
6322+
7A8A19242CF4C9BF000BCB5B /* MultihopPage.swift in Sources */,
63206323
7ACD79392C0DAADD00DBEE14 /* AddCustomListLocationsPage.swift in Sources */,
63216324
8556EB562B9B0AC500D26DD4 /* RevokedDevicePage.swift in Sources */,
63226325
A9BFAFFF2BD004ED00F2BCA1 /* CustomListsTests.swift in Sources */,
@@ -6326,6 +6329,7 @@
63266329
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */,
63276330
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */,
63286331
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */,
6332+
7A8A19262CF4D37B000BCB5B /* DAITAPage.swift in Sources */,
63296333
856952DC2BD2922A008C1F84 /* PartnerAPIClient.swift in Sources */,
63306334
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
63316335
855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,

ios/MullvadVPN/Classes/AccessbilityIdentifier.swift

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ public enum AccessibilityIdentifier: String {
146146
case editCustomListEditLocationsTableView
147147
case relayFilterChipView
148148
case dnsSettingsTableView
149+
case multihopView
150+
case daitaView
149151

150152
// Other UI elements
151153
case accessMethodEnableSwitch

ios/MullvadVPN/Coordinators/Settings/DAITA/DAITATunnelSettingsObserver.swift

-33
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,107 @@
11
//
2-
// DAITATunnelSettingsObservable.swift
2+
// DAITATunnelSettingsViewModel.swift
33
// MullvadVPN
44
//
55
// Created by Jon Petersson on 2024-11-21.
66
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
77
//
88

9-
import Foundation
9+
import MullvadREST
1010
import MullvadSettings
1111

12-
protocol DAITATunnelSettingsObservable: ObservableObject {
13-
var value: DAITASettings { get set }
12+
class DAITATunnelSettingsViewModel: TunnelSettingsObserver, ObservableObject {
13+
typealias TunnelSetting = DAITASettings
14+
15+
let tunnelManager: TunnelManager
16+
var tunnelObserver: TunnelObserver?
17+
18+
var didFailDAITAValidation: (((item: DAITASettingsPromptItem, setting: DAITASettings)) -> Void)?
19+
20+
var value: DAITASettings {
21+
willSet {
22+
guard newValue != value else { return }
23+
24+
objectWillChange.send()
25+
tunnelManager.updateSettings([.daita(newValue)])
26+
}
27+
}
28+
29+
required init(tunnelManager: TunnelManager) {
30+
self.tunnelManager = tunnelManager
31+
value = tunnelManager.settings.daita
32+
33+
tunnelObserver = TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in
34+
self?.value = newSettings.daita
35+
})
36+
}
37+
38+
func evaluate(setting: DAITASettings) {
39+
if let error = evaluateDaitaSettingsCompatibility(setting) {
40+
let promptItem = promptItem(from: error, setting: setting)
41+
42+
didFailDAITAValidation?((item: promptItem, setting: setting))
43+
return
44+
}
45+
46+
value = setting
47+
}
48+
}
49+
50+
extension DAITATunnelSettingsViewModel {
51+
private func promptItem(
52+
from error: DAITASettingsCompatibilityError,
53+
setting: DAITASettings
54+
) -> DAITASettingsPromptItem {
55+
let promptItemSetting: DAITASettingsPromptItem.Setting = if setting.daitaState != value.daitaState {
56+
.daita
57+
} else {
58+
.directOnly
59+
}
60+
61+
var promptItem: DAITASettingsPromptItem
62+
63+
switch error {
64+
case .singlehop:
65+
promptItem = .daitaSettingIncompatibleWithSinglehop(promptItemSetting)
66+
case .multihop:
67+
promptItem = .daitaSettingIncompatibleWithMultihop(promptItemSetting)
68+
}
69+
70+
return promptItem
71+
}
72+
73+
private func evaluateDaitaSettingsCompatibility(_ settings: DAITASettings) -> DAITASettingsCompatibilityError? {
74+
guard settings.daitaState.isEnabled else { return nil }
75+
76+
var tunnelSettings = tunnelManager.settings
77+
tunnelSettings.daita = settings
78+
79+
var compatibilityError: DAITASettingsCompatibilityError?
80+
81+
do {
82+
_ = try tunnelManager.selectRelays(tunnelSettings: tunnelSettings)
83+
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
84+
// Return error if no relays could be selected due to DAITA constraints.
85+
compatibilityError = tunnelSettings.tunnelMultihopState.isEnabled ? .multihop : .singlehop
86+
} catch _ as NoRelaysSatisfyingConstraintsError {
87+
// Even if the constraints error is not DAITA specific, if both DAITA and Direct only are enabled,
88+
// we should return a DAITA related error since the current settings would have resulted in the
89+
// relay selector not being able to select a DAITA relay anyway.
90+
if settings.isDirectOnly {
91+
compatibilityError = tunnelSettings.tunnelMultihopState.isEnabled ? .multihop : .singlehop
92+
}
93+
} catch {}
94+
95+
return compatibilityError
96+
}
1497
}
1598

16-
class MockDAITATunnelSettingsViewModel: DAITATunnelSettingsObservable {
99+
class MockDAITATunnelSettingsViewModel: TunnelSettingsObservable {
17100
@Published var value: DAITASettings
18101

19102
init(daitaSettings: DAITASettings = DAITASettings()) {
20103
value = daitaSettings
21104
}
22-
}
23105

24-
class DAITATunnelSettingsViewModel: DAITATunnelSettingsObserver, DAITATunnelSettingsObservable {}
106+
func evaluate(setting: MullvadSettings.DAITASettings) {}
107+
}

0 commit comments

Comments
 (0)