Skip to content

Commit

Permalink
Add the DAITA page to the root of the settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
rablador committed Dec 2, 2024
1 parent f5ee0e7 commit 5dc221e
Show file tree
Hide file tree
Showing 31 changed files with 729 additions and 497 deletions.
2 changes: 1 addition & 1 deletion ios/MullvadSettings/MultihopSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation
import MullvadTypes

/// Whether Multihop is enabled
/// Whether multihop is enabled.
public enum MultihopState: Codable {
case on
case off
Expand Down
42 changes: 25 additions & 17 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -567,11 +567,13 @@
7A8A190E2CEB77C1000BCB5B /* SettingsRowViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190D2CEB77B7000BCB5B /* SettingsRowViewFooter.swift */; };
7A8A19102CEE391B000BCB5B /* RowSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */; };
7A8A19122CEF1E68000BCB5B /* SettingsInfoContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19112CEF1E58000BCB5B /* SettingsInfoContainerView.swift */; };
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsObserver.swift */; };
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsObserver.swift */; };
7A8A19182CEF27AB000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19172CEF279C000BCB5B /* DAITATunnelSettingsViewModel.swift */; };
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */; };
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */; };
7A8A191A2CEF41AF000BCB5B /* GroupedRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19192CEF41AC000BCB5B /* GroupedRowView.swift */; };
7A8A191C2CEF55E3000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A191B2CEF55DA000BCB5B /* MultihopTunnelSettingsViewModel.swift */; };
7A8A191E2CEF5CF2000BCB5B /* TunnelSettingsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A191D2CEF5CDF000BCB5B /* TunnelSettingsObservable.swift */; };
7A8A19242CF4C9BF000BCB5B /* MultihopPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */; };
7A8A19262CF4D37B000BCB5B /* DAITAPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19252CF4D373000BCB5B /* DAITAPage.swift */; };
7A8A19282CF603EB000BCB5B /* SettingsViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19272CF603E3000BCB5B /* SettingsViewControllerFactory.swift */; };
7A9BE5A22B8F88C500E2A7D0 /* LocationNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */; };
7A9BE5A32B8F89B900E2A7D0 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389F72B864CDF008E77E1 /* LocationNode.swift */; };
7A9BE5A52B90760C00E2A7D0 /* CustomListsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */; };
Expand Down Expand Up @@ -1921,11 +1923,13 @@
7A8A190D2CEB77B7000BCB5B /* SettingsRowViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowViewFooter.swift; sourceTree = "<group>"; };
7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowSeparator.swift; sourceTree = "<group>"; };
7A8A19112CEF1E58000BCB5B /* SettingsInfoContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoContainerView.swift; sourceTree = "<group>"; };
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITATunnelSettingsObserver.swift; sourceTree = "<group>"; };
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopTunnelSettingsObserver.swift; sourceTree = "<group>"; };
7A8A19172CEF279C000BCB5B /* DAITATunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITATunnelSettingsViewModel.swift; sourceTree = "<group>"; };
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITATunnelSettingsViewModel.swift; sourceTree = "<group>"; };
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopTunnelSettingsViewModel.swift; sourceTree = "<group>"; };
7A8A19192CEF41AC000BCB5B /* GroupedRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedRowView.swift; sourceTree = "<group>"; };
7A8A191B2CEF55DA000BCB5B /* MultihopTunnelSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopTunnelSettingsViewModel.swift; sourceTree = "<group>"; };
7A8A191D2CEF5CDF000BCB5B /* TunnelSettingsObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsObservable.swift; sourceTree = "<group>"; };
7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPage.swift; sourceTree = "<group>"; };
7A8A19252CF4D373000BCB5B /* DAITAPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITAPage.swift; sourceTree = "<group>"; };
7A8A19272CF603E3000BCB5B /* SettingsViewControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewControllerFactory.swift; sourceTree = "<group>"; };
7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationNodeTests.swift; sourceTree = "<group>"; };
7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSourceTests.swift; sourceTree = "<group>"; };
7A9BE5A82B90806800E2A7D0 /* CustomListsRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsRepositoryStub.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2885,7 +2889,6 @@
4424CDD12CDBD457009D8C9F /* SwiftUI components */,
4422C06F2CCFF6520001A385 /* Obfuscation */,
7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */,
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */,
7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */,
5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */,
Expand Down Expand Up @@ -3823,6 +3826,8 @@
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
7A6389EC2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift */,
7A6389EA2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift */,
7A8A19272CF603E3000BCB5B /* SettingsViewControllerFactory.swift */,
7A8A191D2CEF5CDF000BCB5B /* TunnelSettingsObservable.swift */,
);
path = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -3972,8 +3977,7 @@
7A8A18F72CE34E8F000BCB5B /* Multihop */ = {
isa = PBXGroup;
children = (
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsObserver.swift */,
7A8A191B2CEF55DA000BCB5B /* MultihopTunnelSettingsViewModel.swift */,
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */,
7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */,
);
path = Multihop;
Expand All @@ -3994,8 +3998,8 @@
7A8A19082CE5FFD7000BCB5B /* DAITA */ = {
isa = PBXGroup;
children = (
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsObserver.swift */,
7A8A19172CEF279C000BCB5B /* DAITATunnelSettingsViewModel.swift */,
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */,
7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */,
);
path = DAITA;
Expand Down Expand Up @@ -4103,6 +4107,7 @@
85021CAD2BDBC4290098B400 /* AppLogsPage.swift */,
8587A05C2B84D43100152938 /* ChangeLogAlert.swift */,
A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */,
7A8A19252CF4D373000BCB5B /* DAITAPage.swift */,
F09084672C6E88ED001CD36E /* DaitaPromptAlert.swift */,
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */,
852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */,
Expand All @@ -4111,6 +4116,7 @@
85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */,
A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */,
852969342B4E9270007EAD4C /* LoginPage.swift */,
7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */,
85139B2C2B84B4A700734217 /* OutOfTimePage.swift */,
852969322B4E9232007EAD4C /* Page.swift */,
855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */,
Expand Down Expand Up @@ -5771,7 +5777,7 @@
7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */,
7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */,
587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */,
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsObserver.swift in Sources */,
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */,
7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */,
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */,
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */,
Expand Down Expand Up @@ -5802,6 +5808,7 @@
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */,
F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */,
7A6389EB2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift in Sources */,
7A8A19282CF603EB000BCB5B /* SettingsViewControllerFactory.swift in Sources */,
58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */,
7A8A19072CE4E9D3000BCB5B /* SettingsInfoView.swift in Sources */,
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
Expand Down Expand Up @@ -5923,7 +5930,6 @@
F0EF50D52A949F8E0031E8DF /* ChangeLogViewModel.swift in Sources */,
F0E8E4BB2A56C9F100ED26A3 /* WelcomeInteractor.swift in Sources */,
7A6000F62B60092F001CF0D9 /* AccessMethodViewModelEditing.swift in Sources */,
7A8A191C2CEF55E3000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */,
5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */,
58EF87572B16330B00C098B2 /* ProxyConfigurationTester.swift in Sources */,
5827B0A62B0F39E900CCBBA1 /* EditAccessMethodInteractor.swift in Sources */,
Expand Down Expand Up @@ -5951,6 +5957,7 @@
7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */,
582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */,
7AF9BE8E2A331C7B00DBFEDB /* RelayFilterViewModel.swift in Sources */,
7A8A191E2CEF5CF2000BCB5B /* TunnelSettingsObservable.swift in Sources */,
58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */,
5864AF0829C78849005B0CD9 /* CellFactoryProtocol.swift in Sources */,
7A6389E22B7E3BD6008E77E1 /* CustomListInteractor.swift in Sources */,
Expand All @@ -5959,7 +5966,6 @@
58CEB2F32AFD0BA100E6E088 /* TextCellContentView.swift in Sources */,
7A6389E72B7E42BE008E77E1 /* CustomListViewController.swift in Sources */,
586C0D7C2B03BDD100E7CDD7 /* AccessMethodViewModel.swift in Sources */,
7A8A19182CEF27AB000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */,
587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */,
7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */,
5819C2172729595500D6EC38 /* SettingsAddDNSEntryCell.swift in Sources */,
Expand Down Expand Up @@ -5994,7 +6000,7 @@
5892A45E265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift in Sources */,
580909D32876D09A0078138D /* RevokedDeviceViewController.swift in Sources */,
58FF9FF02B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift in Sources */,
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsObserver.swift in Sources */,
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */,
58CEB2FD2AFD19D300E6E088 /* UITableView+ReuseIdentifier.swift in Sources */,
F0FADDEA2BE90AAA000D0B02 /* LaunchArguments.swift in Sources */,
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */,
Expand Down Expand Up @@ -6309,6 +6315,7 @@
85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */,
852969352B4E9270007EAD4C /* LoginPage.swift in Sources */,
A998DA832BD2B055001D61A2 /* EditCustomListLocationsPage.swift in Sources */,
7A8A19242CF4C9BF000BCB5B /* MultihopPage.swift in Sources */,
7ACD79392C0DAADD00DBEE14 /* AddCustomListLocationsPage.swift in Sources */,
8556EB562B9B0AC500D26DD4 /* RevokedDevicePage.swift in Sources */,
A9BFAFFF2BD004ED00F2BCA1 /* CustomListsTests.swift in Sources */,
Expand All @@ -6318,6 +6325,7 @@
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */,
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */,
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */,
7A8A19262CF4D37B000BCB5B /* DAITAPage.swift in Sources */,
856952DC2BD2922A008C1F84 /* PartnerAPIClient.swift in Sources */,
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ public enum AccessibilityIdentifier: String {
case editCustomListEditLocationsTableView
case relayFilterChipView
case dnsSettingsTableView
case multihopView
case daitaView

// Other UI elements
case accessMethodEnableSwitch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ enum DAITASettingsPromptItem: CustomStringConvertible {
switch self {
case .daitaSettingIncompatibleWithSinglehop:
"""
Not all our servers are DAITA-enabled. In order to use the internet, you might have to \
select a new location after enabling.
DAITA isn’t available at the currently selected location. After enabling, please go to \
the “Select location” view and select a location that supports DAITA.
"""
case .daitaSettingIncompatibleWithMultihop:
"""
Not all our servers are DAITA-enabled. In order to use the internet, you might have to \
select a new entry location after enabling.
DAITA isn’t available on the current entry server. After enabling, please go to the \
“Select location” view and select an entry location that supports DAITA.
"""
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,24 +1,107 @@
//
// DAITATunnelSettingsObservable.swift
// DAITATunnelSettingsViewModel.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-21.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadREST
import MullvadSettings

protocol DAITATunnelSettingsObservable: ObservableObject {
var value: DAITASettings { get set }
class DAITATunnelSettingsViewModel: TunnelSettingsObserver, ObservableObject {
typealias TunnelSetting = DAITASettings

let tunnelManager: TunnelManager
var tunnelObserver: TunnelObserver?

var didFailDAITAValidation: (((item: DAITASettingsPromptItem, setting: DAITASettings)) -> Void)?

var value: DAITASettings {
willSet {
guard newValue != value else { return }

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

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

tunnelObserver = TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in
self?.value = newSettings.daita
})
}

func evaluate(setting: DAITASettings) {
if let error = evaluateDaitaSettingsCompatibility(setting) {
let promptItem = promptItem(from: error, setting: setting)

didFailDAITAValidation?((item: promptItem, setting: setting))
return
}

value = setting
}
}

extension DAITATunnelSettingsViewModel {
private func promptItem(
from error: DAITASettingsCompatibilityError,
setting: DAITASettings
) -> DAITASettingsPromptItem {
let promptItemSetting: DAITASettingsPromptItem.Setting = if setting.daitaState != value.daitaState {
.daita
} else {
.directOnly
}

var promptItem: DAITASettingsPromptItem

switch error {
case .singlehop:
promptItem = .daitaSettingIncompatibleWithSinglehop(promptItemSetting)
case .multihop:
promptItem = .daitaSettingIncompatibleWithMultihop(promptItemSetting)
}

return promptItem
}

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

var tunnelSettings = tunnelManager.settings
tunnelSettings.daita = settings

var compatibilityError: DAITASettingsCompatibilityError?

do {
_ = try tunnelManager.selectRelays(tunnelSettings: tunnelSettings)
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
// Return error if no relays could be selected due to DAITA constraints.
compatibilityError = tunnelSettings.tunnelMultihopState.isEnabled ? .multihop : .singlehop
} catch _ as NoRelaysSatisfyingConstraintsError {
// Even if the constraints error is not DAITA specific, if both DAITA and Direct only are enabled,
// we should return a DAITA related error since the current settings would have resulted in the
// relay selector not being able to select a DAITA relay anyway.
if settings.isDirectOnly {
compatibilityError = tunnelSettings.tunnelMultihopState.isEnabled ? .multihop : .singlehop
}
} catch {}

return compatibilityError
}
}

class MockDAITATunnelSettingsViewModel: DAITATunnelSettingsObservable {
class MockDAITATunnelSettingsViewModel: TunnelSettingsObservable {
@Published var value: DAITASettings

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

class DAITATunnelSettingsViewModel: DAITATunnelSettingsObserver, DAITATunnelSettingsObservable {}
func evaluate(setting: MullvadSettings.DAITASettings) {}
}
Loading

0 comments on commit 5dc221e

Please sign in to comment.