Skip to content

Commit

Permalink
Put custom list name or (unfmtd) location name into connect button
Browse files Browse the repository at this point in the history
  • Loading branch information
acb-mv authored and buggmagnet committed Jan 24, 2025
1 parent cfc132e commit 2a17914
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 17 deletions.
5 changes: 4 additions & 1 deletion ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ extension RelaySelector {
filter: RelayConstraint<RelayFilter>,
in relaysResponse: REST.ServerRelaysResponse
) -> REST.BridgeRelay? {
let mappedBridges = mapRelays(relays: relaysResponse.bridge.relays, locations: relaysResponse.locations)
let mappedBridges = RelayWithLocation.locateRelays(
relays: relaysResponse.bridge.relays,
locations: relaysResponse.locations
)
let filteredRelays = (try? applyConstraints(
location,
filterConstraint: filter,
Expand Down
5 changes: 4 additions & 1 deletion ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ extension RelaySelector {
filterConstraint: RelayConstraint<RelayFilter>,
daitaEnabled: Bool
) throws -> [RelayWithLocation<REST.ServerRelay>] {
let mappedRelays = mapRelays(relays: relays.wireguard.relays, locations: relays.locations)
let mappedRelays = RelayWithLocation.locateRelays(
relays: relays.wireguard.relays,
locations: relays.locations
)

return try applyConstraints(
relayConstraint,
Expand Down
10 changes: 0 additions & 10 deletions ios/MullvadREST/Relay/RelaySelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,6 @@ public enum RelaySelector {
return randomRelay
}

static func mapRelays<T: AnyRelay>(
relays: [T],
locations: [String: REST.ServerLocation]
) -> [RelayWithLocation<T>] {
relays.compactMap { relay in
guard let serverLocation = locations[relay.location] else { return nil }
return makeRelayWithLocationFrom(serverLocation, relay: relay)
}
}

/// Produce a list of `RelayWithLocation` items satisfying the given constraints
static func applyConstraints<T: AnyRelay>(
_ relayConstraint: RelayConstraint<UserSelectedRelays>,
Expand Down
34 changes: 33 additions & 1 deletion ios/MullvadREST/Relay/RelayWithLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public struct RelayWithLocation<T: AnyRelay> {
let relay: T
public let serverLocation: Location

func matches(location: RelayLocation) -> Bool {
public func matches(location: RelayLocation) -> Bool {
return switch location {
case let .country(countryCode):
serverLocation.countryCode == countryCode
Expand All @@ -28,6 +28,38 @@ public struct RelayWithLocation<T: AnyRelay> {
relay.hostname == hostname
}
}

init(relay: T, serverLocation: Location) {
self.relay = relay
self.serverLocation = serverLocation
}

init?(_ relay: T, locations: [String: REST.ServerLocation]) {
let locationComponents = relay.location.split(separator: "-")
guard
locationComponents.count > 1,
let serverLocation = locations[relay.location]
else { return nil }

self.relay = relay
self.serverLocation = Location(
country: serverLocation.country,
countryCode: String(locationComponents[0]),
city: serverLocation.city,
cityCode: String(locationComponents[1]),
latitude: serverLocation.latitude,
longitude: serverLocation.longitude
)
}

/// given a list of `AnyRelay` values and a name to location mapping, produce a list of
/// `RelayWithLocation`values for those whose locations have successfully been found.
public static func locateRelays(
relays: [T],
locations: [String: REST.ServerLocation]
) -> [RelayWithLocation<T>] {
relays.compactMap { RelayWithLocation($0, locations: locations) }
}
}

extension RelayWithLocation: Equatable {
Expand Down
10 changes: 10 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; };
44DD7D2D2B74E44A0005F67F /* QuantumResistanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */; };
44DF8AC42BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */; };
44E1F7582D3EA83A003A60FF /* DestinationDescriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44E1F7572D3EA82C003A60FF /* DestinationDescriber.swift */; };
44E1F75A2D3FDCCA003A60FF /* DestinationDescriberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44E1F7592D3FDCBA003A60FF /* DestinationDescriberTests.swift */; };
44E1F75B2D3FEC81003A60FF /* DestinationDescriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44E1F7572D3EA82C003A60FF /* DestinationDescriber.swift */; };
5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; };
5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; };
5807E2C02432038B00F5FF30 /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Helpers.swift */; };
Expand Down Expand Up @@ -1483,6 +1486,8 @@
44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; };
44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantumResistanceSettings.swift; sourceTree = "<group>"; };
44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+PostQuantum.swift"; sourceTree = "<group>"; };
44E1F7572D3EA82C003A60FF /* DestinationDescriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationDescriber.swift; sourceTree = "<group>"; };
44E1F7592D3FDCBA003A60FF /* DestinationDescriberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationDescriberTests.swift; sourceTree = "<group>"; };
5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; };
5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = "<group>"; };
5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2664,6 +2669,7 @@
440E9EFD2BDA982A00B1FD11 /* Tunnel */ = {
isa = PBXGroup;
children = (
44E1F7592D3FDCBA003A60FF /* DestinationDescriberTests.swift */,
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */,
);
path = Tunnel;
Expand Down Expand Up @@ -2712,6 +2718,7 @@
4419AA862D28264D001B13C9 /* ConnectionView */ = {
isa = PBXGroup;
children = (
44E1F7572D3EA82C003A60FF /* DestinationDescriber.swift */,
F0ADF1CF2D01B50B00299F09 /* ChipView */,
7AA130972CFF364F00640DF9 /* FeatureIndicators */,
449E9A6E2D283C7400F8574A /* ButtonPanel.swift */,
Expand Down Expand Up @@ -5623,6 +5630,7 @@
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */,
A9A5F9F12ACB05160083449F /* String+Helpers.swift in Sources */,
A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */,
44E1F75B2D3FEC81003A60FF /* DestinationDescriber.swift in Sources */,
A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */,
A9A5F9F52ACB05160083449F /* NewDeviceNotificationProvider.swift in Sources */,
F09D04B72AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift in Sources */,
Expand Down Expand Up @@ -5730,6 +5738,7 @@
F0ACE3362BE517D6006D5333 /* ServerRelaysResponse+Stubs.swift in Sources */,
7ADCB2DA2B6A730400C88F89 /* IPOverrideRepositoryStub.swift in Sources */,
A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */,
44E1F75A2D3FDCCA003A60FF /* DestinationDescriberTests.swift in Sources */,
A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */,
A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */,
A9BD4D552CA58C3700C8A0E6 /* RESTTransportStub.swift in Sources */,
Expand Down Expand Up @@ -6229,6 +6238,7 @@
58FB865526E8BF3100F188BC /* StorePaymentManagerError.swift in Sources */,
F09D04B32AE919AC003D4F89 /* OutgoingConnectionProxy.swift in Sources */,
7A5869BF2B57D0A100640D27 /* IPOverrideStatus.swift in Sources */,
44E1F7582D3EA83A003A60FF /* DestinationDescriber.swift in Sources */,
58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */,
7AF10EB22ADE859200C090B9 /* AlertViewController.swift in Sources */,
587D9676288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import MullvadMockData
import MullvadREST
import MullvadSettings
import MullvadTypes
import PacketTunnelCore
Expand Down Expand Up @@ -37,7 +38,10 @@ struct ConnectionViewComponentPreview<Content: View>: View {
isDaitaEnabled: true
)),
state: .connected(SelectedRelaysStub.selectedRelays, isPostQuantum: true, isDaita: true)
)
),
relayConstraints: RelayConstraints(),
relayCache: RelayCache(cacheDirectory: ApplicationConfiguration.containerURL),
customListRepository: CustomListRepository()
)

var content: (FeatureIndicatorsViewModel, ConnectionViewViewModel, Binding<Bool>) -> Content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//

import Combine
import MullvadREST
import MullvadSettings
import MullvadTypes
import SwiftUI

class ConnectionViewViewModel: ObservableObject {
Expand All @@ -26,6 +29,17 @@ class ConnectionViewViewModel: ObservableObject {

@Published private(set) var tunnelStatus: TunnelStatus
@Published var outgoingConnectionInfo: OutgoingConnectionInfo?
@Published var showsActivityIndicator = false

@Published var relayConstraints: RelayConstraints
let destinationDescriber: DestinationDescribing

var combinedState: Publishers.CombineLatest<
Published<TunnelStatus>.Publisher,
Published<Bool>.Publisher
> {
$tunnelStatus.combineLatest($showsActivityIndicator)
}

var tunnelIsConnected: Bool {
if case .connected = tunnelStatus.state {
Expand All @@ -35,8 +49,25 @@ class ConnectionViewViewModel: ObservableObject {
}
}

init(tunnelStatus: TunnelStatus) {
var connectionName: String? {
if case let .only(loc) = relayConstraints.exitLocations {
return destinationDescriber.describe(loc)
}
return nil
}

init(
tunnelStatus: TunnelStatus,
relayConstraints: RelayConstraints,
relayCache: RelayCacheProtocol,
customListRepository: CustomListRepositoryProtocol
) {
self.tunnelStatus = tunnelStatus
self.relayConstraints = relayConstraints
self.destinationDescriber = DestinationDescriber(
relayCache: relayCache,
customListRepository: customListRepository
)
}

func update(tunnelStatus: TunnelStatus) {
Expand Down Expand Up @@ -131,7 +162,7 @@ extension ConnectionViewViewModel {
var localizedTitleForSelectLocationButton: LocalizedStringKey {
switch tunnelStatus.state {
case .disconnecting, .pendingReconnect, .disconnected, .waitingForConnectivity(.noNetwork):
LocalizedStringKey("Select location")
LocalizedStringKey(connectionName ?? "Select location")
case .connecting, .connected, .reconnecting, .waitingForConnectivity(.noConnection),
.negotiatingEphemeralPeer, .error:
LocalizedStringKey("Switch location")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// DestinationDescriber.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2025-01-20.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
// A source of truth for converting an exit relay destination (i.e., a relay or list) into a name

import MullvadREST
import MullvadSettings
import MullvadTypes

protocol DestinationDescribing {
func describe(_ destination: UserSelectedRelays) -> String?
}

struct DestinationDescriber: DestinationDescribing {
let relayCache: RelayCacheProtocol
let customListRepository: CustomListRepositoryProtocol

public init(
relayCache: RelayCacheProtocol,
customListRepository: CustomListRepositoryProtocol
) {
self.relayCache = relayCache
self.customListRepository = customListRepository
}

private func customListDescription(_ destination: UserSelectedRelays) -> String? {
// We only return a description for the list if the user has selected the
// entire list. If they have only selected relays/locations from it,
// we show those as if they selected them from elsewhere.
guard
let customListSelection = destination.customListSelection,
customListSelection.isList,
let customList = customListRepository.fetch(by: customListSelection.listId)
else { return nil }
return customList.name
}

private func describeRelayLocation(
_ locationSpec: RelayLocation,
usingRelayWithLocation serverLocation: Location
) -> String {
switch locationSpec {
case .country: serverLocation.country
case .city: serverLocation.city
case let .hostname(_, _, hostname):
"\(serverLocation.city) (\(hostname))"
}
}

private func relayDescription(_ destination: UserSelectedRelays) -> String? {
guard
let location = destination.locations.first,
let cachedRelays = try? relayCache.read().relays
else { return nil }
let locatedRelays = RelayWithLocation.locateRelays(
relays: cachedRelays.wireguard.relays,
locations: cachedRelays.locations
)

guard let matchingRelay = (locatedRelays.first { $0.matches(location: location)
}) else { return nil }

return describeRelayLocation(location, usingRelayWithLocation: matchingRelay.serverLocation)
}

func describe(_ destination: UserSelectedRelays) -> String? {
customListDescription(destination) ?? relayDescription(destination)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Combine
import MapKit
import MullvadLogging
import MullvadREST
import MullvadSettings
import MullvadTypes
import SwiftUI
Expand Down Expand Up @@ -60,7 +61,12 @@ class TunnelViewController: UIViewController, RootContainment {
self.interactor = interactor

tunnelState = interactor.tunnelStatus.state
connectionViewViewModel = ConnectionViewViewModel(tunnelStatus: interactor.tunnelStatus)
connectionViewViewModel = ConnectionViewViewModel(
tunnelStatus: interactor.tunnelStatus,
relayConstraints: interactor.tunnelSettings.relayConstraints,
relayCache: RelayCache(cacheDirectory: ApplicationConfiguration.containerURL),
customListRepository: CustomListRepository()
)
indicatorsViewViewModel = FeatureIndicatorsViewModel(
tunnelSettings: interactor.tunnelSettings,
ipOverrides: interactor.ipOverrides
Expand Down Expand Up @@ -97,6 +103,7 @@ class TunnelViewController: UIViewController, RootContainment {

interactor.didUpdateTunnelSettings = { [weak self] tunnelSettings in
self?.indicatorsViewViewModel.tunnelSettings = tunnelSettings
self?.connectionViewViewModel.relayConstraints = tunnelSettings.relayConstraints
}

interactor.didUpdateIpOverrides = { [weak self] overrides in
Expand Down
Loading

0 comments on commit 2a17914

Please sign in to comment.