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

Update the select location view #6950

Merged
merged 3 commits into from
Oct 18, 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
28 changes: 14 additions & 14 deletions ios/MullvadREST/Relay/MultihopDecisionFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ protocol MultihopDecisionFlow {
func pick(
entryCandidates: [RelayCandidate],
exitCandidates: [RelayCandidate],
automaticDaitaRouting: Bool
daitaAutomaticRouting: Bool
) throws -> SelectedRelays
}

Expand All @@ -30,7 +30,7 @@ struct OneToOne: MultihopDecisionFlow {
func pick(
entryCandidates: [RelayCandidate],
exitCandidates: [RelayCandidate],
automaticDaitaRouting: Bool
daitaAutomaticRouting: Bool
) throws -> SelectedRelays {
guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
guard let next else {
Expand All @@ -39,7 +39,7 @@ struct OneToOne: MultihopDecisionFlow {
return try next.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
automaticDaitaRouting: automaticDaitaRouting
daitaAutomaticRouting: daitaAutomaticRouting
)
}

Expand All @@ -50,7 +50,7 @@ struct OneToOne: MultihopDecisionFlow {
let exitMatch = try relayPicker.findBestMatch(from: exitCandidates)
let entryMatch = try relayPicker.findBestMatch(
from: entryCandidates,
closeTo: automaticDaitaRouting ? exitMatch.location : nil
closeTo: daitaAutomaticRouting ? exitMatch.location : nil
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
Expand All @@ -73,7 +73,7 @@ struct OneToMany: MultihopDecisionFlow {
func pick(
entryCandidates: [RelayCandidate],
exitCandidates: [RelayCandidate],
automaticDaitaRouting: Bool
daitaAutomaticRouting: Bool
) throws -> SelectedRelays {
guard let multihopPicker = relayPicker as? MultihopPicker else {
fatalError("Could not cast picker to MultihopPicker")
Expand All @@ -86,13 +86,13 @@ struct OneToMany: MultihopDecisionFlow {
return try next.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
automaticDaitaRouting: automaticDaitaRouting
daitaAutomaticRouting: daitaAutomaticRouting
)
}

guard !automaticDaitaRouting else {
guard !daitaAutomaticRouting else {
return try ManyToOne(next: next, relayPicker: relayPicker)
.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates, automaticDaitaRouting: true)
.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates, daitaAutomaticRouting: true)
}

let entryMatch = try multihopPicker.findBestMatch(from: entryCandidates)
Expand All @@ -118,7 +118,7 @@ struct ManyToOne: MultihopDecisionFlow {
func pick(
entryCandidates: [RelayCandidate],
exitCandidates: [RelayCandidate],
automaticDaitaRouting: Bool
daitaAutomaticRouting: Bool
) throws -> SelectedRelays {
guard let multihopPicker = relayPicker as? MultihopPicker else {
fatalError("Could not cast picker to MultihopPicker")
Expand All @@ -131,15 +131,15 @@ struct ManyToOne: MultihopDecisionFlow {
return try next.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
automaticDaitaRouting: automaticDaitaRouting
daitaAutomaticRouting: daitaAutomaticRouting
)
}

let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
let entryMatch = try multihopPicker.exclude(
relay: exitMatch,
from: entryCandidates,
closeTo: automaticDaitaRouting ? exitMatch.location : nil
closeTo: daitaAutomaticRouting ? exitMatch.location : nil
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
Expand All @@ -162,7 +162,7 @@ struct ManyToMany: MultihopDecisionFlow {
func pick(
entryCandidates: [RelayCandidate],
exitCandidates: [RelayCandidate],
automaticDaitaRouting: Bool
daitaAutomaticRouting: Bool
) throws -> SelectedRelays {
guard let multihopPicker = relayPicker as? MultihopPicker else {
fatalError("Could not cast picker to MultihopPicker")
Expand All @@ -175,15 +175,15 @@ struct ManyToMany: MultihopDecisionFlow {
return try next.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
automaticDaitaRouting: automaticDaitaRouting
daitaAutomaticRouting: daitaAutomaticRouting
)
}

let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
let entryMatch = try multihopPicker.exclude(
relay: exitMatch,
from: entryCandidates,
closeTo: automaticDaitaRouting ? exitMatch.location : nil
closeTo: daitaAutomaticRouting ? exitMatch.location : nil
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
Expand Down
54 changes: 13 additions & 41 deletions ios/MullvadREST/Relay/RelayPicking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,31 +46,23 @@ struct SinglehopPicker: RelayPicking {

func pick() throws -> SelectedRelays {
do {
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.daitaState.isEnabled
)

let match = try findBestMatch(from: exitCandidates)
return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
// If DAITA is on and Direct only is off, and no supported relays are found, we should try to find the nearest
// available relay that supports DAITA and use it as entry in a multihop selection.
if daitaSettings.shouldDoAutomaticRouting {
var constraints = constraints
constraints.entryLocations = .any

if daitaSettings.isAutomaticRouting {
return try MultihopPicker(
relays: relays,
constraints: constraints,
connectionAttemptCount: connectionAttemptCount,
daitaSettings: daitaSettings,
automaticDaitaRouting: true
daitaSettings: daitaSettings
).pick()
} else {
throw error
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.daitaState.isEnabled
)

let match = try findBestMatch(from: exitCandidates)
return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
}
}
}
Expand All @@ -81,7 +73,6 @@ struct MultihopPicker: RelayPicking {
let constraints: RelayConstraints
let connectionAttemptCount: UInt
let daitaSettings: DAITASettings
let automaticDaitaRouting: Bool

func pick() throws -> SelectedRelays {
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
Expand Down Expand Up @@ -117,7 +108,7 @@ struct MultihopPicker: RelayPicking {

do {
let entryCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.entryLocations,
by: daitaSettings.isAutomaticRouting ? .any : constraints.entryLocations,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.daitaState.isEnabled
Expand All @@ -126,27 +117,8 @@ struct MultihopPicker: RelayPicking {
return try decisionFlow.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
automaticDaitaRouting: automaticDaitaRouting
daitaAutomaticRouting: daitaSettings.isAutomaticRouting
)
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
// If DAITA is on and Direct only is off, and no supported relays are found, we should try to find the nearest
// available relay that supports DAITA and use it as entry in a multihop selection.
if daitaSettings.shouldDoAutomaticRouting {
let entryCandidates = try RelaySelector.WireGuard.findCandidates(
by: .any,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: true
)

return try decisionFlow.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
automaticDaitaRouting: true
)
} else {
throw error
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions ios/MullvadREST/Relay/RelaySelectorWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ public final class RelaySelectorWrapper: RelaySelectorProtocol {
relays: relays,
constraints: tunnelSettings.relayConstraints,
connectionAttemptCount: connectionAttemptCount,
daitaSettings: tunnelSettings.daita,
automaticDaitaRouting: false
daitaSettings: tunnelSettings.daita
).pick()
}
}
Expand Down
6 changes: 5 additions & 1 deletion ios/MullvadSettings/DAITASettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ public struct DAITASettings: Codable, Equatable {
public let daitaState: DAITAState
public let directOnlyState: DirectOnlyState

public var shouldDoAutomaticRouting: Bool {
public var isAutomaticRouting: Bool {
daitaState.isEnabled && !directOnlyState.isEnabled
}

public var isDirectOnly: Bool {
daitaState.isEnabled && directOnlyState.isEnabled
}

public init(daitaState: DAITAState = .off, directOnlyState: DirectOnlyState = .off) {
self.daitaState = daitaState
self.directOnlyState = directOnlyState
Expand Down
7 changes: 5 additions & 2 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@
7A21DACF2A30AA3700A787A9 /* UITextField+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */; };
7A27E3C92CAE85710088BCFF /* SettingsInfoButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */; };
7A27E3CB2CAE861D0088BCFF /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */; };
7A27E3CD2CB814EF0088BCFF /* DAITAInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */; };
7A28826A2BA8336600FD9F20 /* VPNSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2882692BA8336600FD9F20 /* VPNSettingsCoordinator.swift */; };
7A2960F62A963F7500389B82 /* AlertCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2960F52A963F7500389B82 /* AlertCoordinator.swift */; };
7A2960FD2A964BB700389B82 /* AlertPresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2960FC2A964BB700389B82 /* AlertPresentation.swift */; };
Expand Down Expand Up @@ -1801,6 +1802,7 @@
7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Appearance.swift"; sourceTree = "<group>"; };
7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoButtonItem.swift; sourceTree = "<group>"; };
7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITAInfoView.swift; sourceTree = "<group>"; };
7A2882692BA8336600FD9F20 /* VPNSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsCoordinator.swift; sourceTree = "<group>"; };
7A2960F52A963F7500389B82 /* AlertCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCoordinator.swift; sourceTree = "<group>"; };
7A2960FC2A964BB700389B82 /* AlertPresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresentation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2779,6 +2781,7 @@
F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */,
F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */,
F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */,
7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */,
5888AD82227B11080051EB06 /* LocationCell.swift */,
F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */,
583DA21325FA4B5C00318683 /* LocationDataSource.swift */,
Expand All @@ -2799,7 +2802,6 @@
isa = PBXGroup;
children = (
7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */,
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */,
5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */,
582BB1AE229566420055B6EF /* SettingsCell.swift */,
Expand All @@ -2813,7 +2815,7 @@
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */,
58677711290976FB006F721F /* SettingsInteractor.swift */,
5867770F290975E8006F721F /* SettingsInteractorFactory.swift */,
F041BE4E2C983C2B0083EC28 /* SettingsPromptAlertItem.swift */,
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */,
58CCA01122424D11004F3011 /* SettingsViewController.swift */,
7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */,
Expand Down Expand Up @@ -5811,6 +5813,7 @@
7AB4CCBB2B691BBB006037F5 /* IPOverrideInteractor.swift in Sources */,
7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */,
F02F41A22B9723AF00625A4F /* AddLocationsCoordinator.swift in Sources */,
7A27E3CD2CB814EF0088BCFF /* DAITAInfoView.swift in Sources */,
F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */,
7A5869C52B5A899C00640D27 /* MethodSettingsCellConfiguration.swift in Sources */,
58E11188292FA11F009FCA84 /* SettingsMigrationUIHandler.swift in Sources */,
Expand Down
6 changes: 4 additions & 2 deletions ios/MullvadVPN/Classes/AppRoutes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ enum AppRouteGroup: AppRouteGroupProtocol {
switch self {
case .primary:
return 0
case .settings, .account, .selectLocation, .changelog:
case .account, .selectLocation, .changelog:
return 1
case .settings:
return 2
case .alert:
// Alerts should always be topmost.
return .max
Expand Down Expand Up @@ -103,7 +105,7 @@ enum AppRoute: AppRouteProtocol {

var isExclusive: Bool {
switch self {
case .selectLocation, .account, .settings, .changelog, .alert:
case .account, .settings, .changelog, .alert:
return true
default:
return false
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadVPN/Coordinators/AccountCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting {
"RESTORE_PURCHASES_DIALOG_MESSAGE",
tableName: "Account",
value: """
You can use the restore purchases function to check for any in-app payments \
You can use the "restore purchases" function to check for any in-app payments \
made via Apple services. If there is a payment that has not been credited, it will \
add the time to the currently logged in Mullvad account.
""",
Expand Down
32 changes: 31 additions & 1 deletion ios/MullvadVPN/Coordinators/LocationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import UIKit

class LocationCoordinator: Coordinator, Presentable, Presenting {
private let tunnelManager: TunnelManager
private var tunnelObserver: TunnelObserver?
private let relayCacheTracker: RelayCacheTracker
private let customListRepository: CustomListRepositoryProtocol
private var locationRelays: LocationRelays?
Expand Down Expand Up @@ -54,6 +55,8 @@ class LocationCoordinator: Coordinator, Presentable, Presenting {
}

func start() {
// If multihop is enabled, we should check if there's a DAITA related error when opening the location
// view. If there is, help the user by showing the entry instead of the exit view.
var startContext: LocationViewControllerWrapper.MultihopContext = .exit
if tunnelManager.settings.tunnelMultihopState.isEnabled {
startContext = if case .noRelaysSatisfyingDaitaConstraints = tunnelManager.tunnelStatus.observedState
Expand All @@ -71,8 +74,14 @@ class LocationCoordinator: Coordinator, Presentable, Presenting {

locationViewControllerWrapper.didFinish = { [weak self] in
guard let self else { return }

if let tunnelObserver {
tunnelManager.removeObserver(tunnelObserver)
}
didFinish?(self)
}

addTunnelObserver()
relayCacheTracker.addObserver(self)

if let cachedRelays = try? relayCacheTracker.getCachedRelays() {
Expand All @@ -86,6 +95,23 @@ class LocationCoordinator: Coordinator, Presentable, Presenting {
navigationController.pushViewController(locationViewControllerWrapper, animated: false)
}

private func addTunnelObserver() {
let tunnelObserver =
TunnelBlockObserver(
didUpdateTunnelSettings: { [weak self] _, settings in
guard let self, let locationRelays else { return }
locationViewControllerWrapper?.onDaitaSettingsUpdate(
settings.daita,
relaysWithLocation: locationRelays,
filter: relayFilter
)
}
)

tunnelManager.addObserver(tunnelObserver)
self.tunnelObserver = tunnelObserver
}

private func updateRelaysWithLocationFrom(
cachedRelays: CachedRelays,
filter: RelayFilter,
Expand All @@ -99,7 +125,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting {
RelaySelector.relayMatchesFilter(relay, filter: filter)
}

self.locationRelays = relaysWithLocation
locationRelays = relaysWithLocation

controllerWrapper.setRelaysWithLocation(relaysWithLocation, filter: filter)
}
Expand Down Expand Up @@ -203,6 +229,10 @@ extension LocationCoordinator: LocationViewControllerWrapperDelegate {
}
}

func navigateToDaitaSettings() {
applicationRouter?.present(.settings(nil))
}

func didSelectExitRelays(_ relays: UserSelectedRelays) {
var relayConstraints = tunnelManager.settings.relayConstraints
relayConstraints.exitLocations = .only(relays)
Expand Down
Loading
Loading