summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-04-10 10:37:07 +0200
committerJon Petersson <jon.petersson@mullvad.net>2025-04-17 14:57:45 +0200
commit811871db9197fcdf22d89d092849305273aec35c (patch)
treeaf5237c99b4b8b693d6d9e635a46046e2be6a986
parent7244df1fbe2f871885715a19804c40d8346c7902 (diff)
downloadmullvadvpn-811871db9197fcdf22d89d092849305273aec35c.tar.xz
mullvadvpn-811871db9197fcdf22d89d092849305273aec35c.zip
Add filter pill to location view when obfuscation is used
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift5
-rw-r--r--ios/MullvadVPN/View controllers/RelayFilter/ChipViewCell.swift8
-rw-r--r--ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift54
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift11
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift35
-rw-r--r--ios/MullvadVPNUITests/Pages/DAITAPage.swift28
-rw-r--r--ios/MullvadVPNUITests/SelectLocationTests.swift54
8 files changed, 178 insertions, 21 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index a67853a642..672799a067 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -647,6 +647,7 @@
7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */; };
7AB401852DA53D5300522E17 /* NewAccountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB401842DA53D4E00522E17 /* NewAccountData.swift */; };
7AB401872DA53DA300522E17 /* NewAccountDataMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB401862DA53D9B00522E17 /* NewAccountDataMock.swift */; };
+ 7AB4018D2DA790DA00522E17 /* SelectLocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4018C2DA790CE00522E17 /* SelectLocationTests.swift */; };
7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */; };
7AB4CCBB2B691BBB006037F5 /* IPOverrideInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */; };
7AB73F6E2D9AAD0A00DA5E1D /* MullvadAccountProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB73F6D2D9AAD0400DA5E1D /* MullvadAccountProxy.swift */; };
@@ -2175,6 +2176,7 @@
7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewControllerWrapper.swift; sourceTree = "<group>"; };
7AB401842DA53D4E00522E17 /* NewAccountData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewAccountData.swift; sourceTree = "<group>"; };
7AB401862DA53D9B00522E17 /* NewAccountDataMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewAccountDataMock.swift; sourceTree = "<group>"; };
+ 7AB4018C2DA790CE00522E17 /* SelectLocationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationTests.swift; sourceTree = "<group>"; };
7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideTests.swift; sourceTree = "<group>"; };
7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideInteractor.swift; sourceTree = "<group>"; };
7AB73F6D2D9AAD0400DA5E1D /* MullvadAccountProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadAccountProxy.swift; sourceTree = "<group>"; };
@@ -4413,6 +4415,7 @@
A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */,
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */,
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
+ 7AB4018C2DA790CE00522E17 /* SelectLocationTests.swift */,
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */,
@@ -6778,6 +6781,7 @@
85139B2D2B84B4A700734217 /* OutOfTimePage.swift in Sources */,
852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */,
4495ECD52D131A4800A7358B /* ShadowsocksObfuscationSettingsPage.swift in Sources */,
+ 7AB4018D2DA790DA00522E17 /* SelectLocationTests.swift in Sources */,
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */,
856952E22BD6B04C008C1F84 /* XCUIElement+Extensions.swift in Sources */,
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index c94c188e34..2b82be84cf 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -1,5 +1,5 @@
//
-// RelayFilter.swift
+// AccessibilityIdentifier.swift
// MullvadVPN
//
// Created by Jon Petersson on 2023-12-20.
@@ -64,6 +64,7 @@ public enum AccessibilityIdentifier: Equatable {
case openPortSelectorMenuButton
case cancelPurchaseListButton
case acceptLocalNetworkSharingButton
+
// Cells
case deviceCell
case accessMethodDirectCell
@@ -99,6 +100,8 @@ public enum AccessibilityIdentifier: Equatable {
case daitaConfirmAlertEnableButton
case multihopCell
case daitaCell
+ case daitaFilterPill
+ case obfuscationFilterPill
// Labels
case accountPageDeviceNameLabel
diff --git a/ios/MullvadVPN/View controllers/RelayFilter/ChipViewCell.swift b/ios/MullvadVPN/View controllers/RelayFilter/ChipViewCell.swift
index 3e1ddff30f..b5d1ae32c0 100644
--- a/ios/MullvadVPN/View controllers/RelayFilter/ChipViewCell.swift
+++ b/ios/MullvadVPN/View controllers/RelayFilter/ChipViewCell.swift
@@ -93,6 +93,7 @@ class ChipViewCell: UIView, UIContentView {
titleLabel.textColor = chipConfiguration.textColor
titleLabel.font = chipConfiguration.font
closeButton.isHidden = chipConfiguration.didTapButton == nil
+ titleLabel.accessibilityIdentifier = chipConfiguration.accessibilityId?.asString
if chipConfiguration.didTapButton != nil {
closeButton.addAction(closeButtonActionHandler, for: .touchUpInside)
} else {
@@ -109,6 +110,7 @@ struct ChipConfiguration: UIContentConfiguration {
var group: Group
var title: String
+ var accessibilityId: AccessibilityIdentifier? = nil
var textColor: UIColor = .white
var font = UIFont.preferredFont(forTextStyle: .caption1)
var backgroundColor: UIColor = .primaryColor
@@ -122,3 +124,9 @@ struct ChipConfiguration: UIContentConfiguration {
return self
}
}
+
+extension ChipConfiguration: Equatable {
+ static func == (lhs: ChipConfiguration, rhs: ChipConfiguration) -> Bool {
+ lhs.title == rhs.title
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
index 285889eac4..27110e97a2 100644
--- a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
+++ b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
@@ -57,32 +57,60 @@ class RelayFilterView: UIView {
}
func setDaita(_ enabled: Bool) {
- let text = NSLocalizedString(
- "RELAY_FILTER_APPLIED_DAITA",
- tableName: "RelayFilter",
- value: "Setting: DAITA",
- comment: ""
+ let chip = ChipConfiguration(
+ group: .settings,
+ title: NSLocalizedString(
+ "RELAY_FILTER_APPLIED_DAITA",
+ tableName: "RelayFilter",
+ value: "Setting: DAITA",
+ comment: ""
+ ),
+ accessibilityId: .daitaFilterPill,
+ didTapButton: nil
+ )
+
+ setChip(chip, enabled: enabled)
+ }
+
+ func setObfuscation(_ enabled: Bool) {
+ let chip = ChipConfiguration(
+ group: .settings,
+ title: NSLocalizedString(
+ "RELAY_FILTER_APPLIED_OBFUSCATION",
+ tableName: "RelayFilter",
+ value: "Setting: Obfuscation",
+ comment: ""
+ ),
+ accessibilityId: .obfuscationFilterPill,
+ didTapButton: nil
)
- chips.removeAll(where: { $0.title.contains(text) })
+
+ setChip(chip, enabled: enabled)
+ }
+
+ // MARK: - Private
+
+ private func setChip(_ chip: ChipConfiguration, enabled: Bool) {
if enabled {
- chips.insert(ChipConfiguration(group: .settings, title: text, didTapButton: nil), at: 0)
+ if !chips.contains(chip) {
+ chips.insert(chip, at: 0)
+ }
+ } else {
+ chips.removeAll { $0 == chip }
}
+
chipsView.setChips(chips)
- hideIfNeeded()
}
- // MARK: - Private
-
private func setUpViews() {
let dummyView = UIView()
dummyView.layoutMargins = UIMetrics.FilterView.chipViewLayoutMargins
let contentContainer = UIStackView(arrangedSubviews: [dummyView, chipsView])
contentContainer.distribution = .fill
- contentContainer.alignment = .firstBaseline
collectionViewHeightConstraint = chipsView.collectionView.heightAnchor
- .constraint(equalToConstant: 8.0)
+ .constraint(equalToConstant: 8)
collectionViewHeightConstraint.isActive = true
dummyView.addConstrainedSubviews([titleLabel]) {
@@ -90,7 +118,7 @@ class RelayFilterView: UIView {
}
addConstrainedSubviews([contentContainer]) {
- contentContainer.pinEdgesToSuperview(PinnableEdges([.top(8.0), .bottom(8.0), .leading(4), .trailing(4)]))
+ contentContainer.pinEdgesToSuperview(PinnableEdges([.top(8), .bottom(8), .leading(4), .trailing(4)]))
}
// Add KVO for observing collectionView's contentSize changes
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
index d3998ce206..bdae59b546 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
@@ -29,6 +29,7 @@ final class LocationViewController: UIViewController {
private var filter = RelayFilter()
private var selectedRelays: RelaySelection
private var shouldFilterDaita: Bool
+ private var shouldFilterObfuscation: Bool
weak var delegate: LocationViewControllerDelegate?
var customListRepository: CustomListRepositoryProtocol
@@ -39,11 +40,13 @@ final class LocationViewController: UIViewController {
init(
customListRepository: CustomListRepositoryProtocol,
selectedRelays: RelaySelection,
- shouldFilterDaita: Bool
+ shouldFilterDaita: Bool,
+ shouldFilterObfuscation: Bool
) {
self.customListRepository = customListRepository
self.selectedRelays = selectedRelays
self.shouldFilterDaita = shouldFilterDaita
+ self.shouldFilterObfuscation = shouldFilterObfuscation
super.init(nibName: nil, bundle: nil)
}
@@ -91,6 +94,11 @@ final class LocationViewController: UIViewController {
filterView.setDaita(isEnabled)
}
+ func setObfuscationChip(_ isEnabled: Bool) {
+ self.shouldFilterObfuscation = isEnabled
+ filterView.setObfuscation(isEnabled)
+ }
+
func refreshCustomLists() {
dataSource?.refreshCustomLists()
}
@@ -175,6 +183,7 @@ final class LocationViewController: UIViewController {
topContentView.addArrangedSubview(filterView)
topContentView.addArrangedSubview(searchBar)
filterView.setDaita(shouldFilterDaita)
+ filterView.setObfuscation(shouldFilterObfuscation)
filterView.didUpdateFilter = { [weak self] in
self?.delegate?.didUpdateFilter(filter: $0)
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
index 5fa22383bf..974821fe25 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
@@ -78,13 +78,15 @@ final class LocationViewControllerWrapper: UIViewController {
entryLocationViewController = LocationViewController(
customListRepository: customListRepository,
selectedRelays: RelaySelection(),
- shouldFilterDaita: settings.daita.isDirectOnly
+ shouldFilterDaita: false,
+ shouldFilterObfuscation: false
)
exitLocationViewController = LocationViewController(
customListRepository: customListRepository,
selectedRelays: RelaySelection(),
- shouldFilterDaita: settings.daita.isDirectOnly && !settings.daita.isAutomaticRouting
+ shouldFilterDaita: false,
+ shouldFilterObfuscation: false
)
super.init(nibName: nil, bundle: nil)
@@ -124,9 +126,21 @@ final class LocationViewControllerWrapper: UIViewController {
private func setRelaysWithLocation() {
let emptyResult = LocationRelays(relays: [], locations: [:])
let relaysCandidates = try? relaySelectorWrapper.findCandidates(tunnelSettings: settings)
- entryLocationViewController?.setDaitaChip(settings.daita.isDirectOnly)
- exitLocationViewController.setDaitaChip(settings.daita.isDirectOnly && !settings.tunnelMultihopState.isEnabled)
- entryLocationViewController?.toggleDaitaAutomaticRouting(isEnabled: settings.daita.isAutomaticRouting)
+
+ let isMultihop = settings.tunnelMultihopState.isEnabled
+ let isDirectOnly = settings.daita.isDirectOnly
+ let isAutomaticRouting = settings.daita.isAutomaticRouting
+ let isObfuscation = settings.wireGuardObfuscation.state.affectsRelaySelection
+
+ if isMultihop {
+ entryLocationViewController?.setObfuscationChip(isObfuscation && !isAutomaticRouting)
+ entryLocationViewController?.setDaitaChip(isDirectOnly)
+ entryLocationViewController?.toggleDaitaAutomaticRouting(isEnabled: isAutomaticRouting)
+ } else {
+ exitLocationViewController.setObfuscationChip(isObfuscation)
+ exitLocationViewController.setDaitaChip(isDirectOnly)
+ }
+
if let entryRelays = relaysCandidates?.entryRelays {
entryLocationViewController?.setRelaysWithLocation(entryRelays.toLocationRelays(), filter: relayFilter)
} else {
@@ -304,3 +318,14 @@ extension LocationViewControllerWrapper: @preconcurrency LocationViewControllerD
delegate?.didUpdateFilter(filter)
}
}
+
+private extension WireGuardObfuscationState {
+ var affectsRelaySelection: Bool {
+ switch self {
+ case .shadowsocks:
+ true
+ case .on, .off, .automatic, .udpOverTcp:
+ false
+ }
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/DAITAPage.swift b/ios/MullvadVPNUITests/Pages/DAITAPage.swift
index a41cd3afd7..17e1380f30 100644
--- a/ios/MullvadVPNUITests/Pages/DAITAPage.swift
+++ b/ios/MullvadVPNUITests/Pages/DAITAPage.swift
@@ -22,6 +22,14 @@ class DAITAPage: Page {
return self
}
+ @discardableResult func tapEnableDirectOnlyDialogButtonIfPresent() -> Self {
+ let buttonElement = app.buttons[AccessibilityIdentifier.daitaConfirmAlertEnableButton]
+ if buttonElement.exists {
+ buttonElement.tap()
+ }
+ return self
+ }
+
@discardableResult func verifyTwoPages() -> Self {
XCTAssertEqual(app.pageIndicators.firstMatch.value as? String, "page 1 of 2")
return self
@@ -41,6 +49,15 @@ class DAITAPage: Page {
return self
}
+ @discardableResult func tapEnableSwitchIfOff() -> Self {
+ let switchElement = app.switches[AccessibilityIdentifier.daitaSwitch]
+
+ if switchElement.value as? String == "0" {
+ tapEnableSwitch()
+ }
+ return self
+ }
+
@discardableResult func verifyDirectOnlySwitchIsEnabled() -> Self {
XCTAssertTrue(app.switches[AccessibilityIdentifier.daitaDirectOnlySwitch].isEnabled)
return self
@@ -60,7 +77,16 @@ class DAITAPage: Page {
let switchElement = app.switches[AccessibilityIdentifier.daitaDirectOnlySwitch]
if switchElement.value as? String == "1" {
- tapEnableSwitch()
+ tapDirectOnlySwitch()
+ }
+ return self
+ }
+
+ @discardableResult func tapDirectOnlySwitchIfOff() -> Self {
+ let switchElement = app.switches[AccessibilityIdentifier.daitaDirectOnlySwitch]
+
+ if switchElement.value as? String == "0" {
+ tapDirectOnlySwitch()
}
return self
}
diff --git a/ios/MullvadVPNUITests/SelectLocationTests.swift b/ios/MullvadVPNUITests/SelectLocationTests.swift
new file mode 100644
index 0000000000..71fc345f1b
--- /dev/null
+++ b/ios/MullvadVPNUITests/SelectLocationTests.swift
@@ -0,0 +1,54 @@
+//
+// SelectLocationTests.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-04-10.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class SelectLocationTests: LoggedInWithTimeUITestCase {
+ func testEnableDAITA() {
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapDAITACell()
+
+ DAITAPage(app)
+ .tapEnableSwitchIfOff()
+ .tapDirectOnlySwitchIfOff()
+ .tapEnableDirectOnlyDialogButtonIfPresent()
+ .tapBackButton()
+
+ SettingsPage(app)
+ .tapDoneButton()
+
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ XCTAssertTrue(app.staticTexts[AccessibilityIdentifier.daitaFilterPill.asString].exists)
+ }
+
+ func testEnableShadowsocksObfuscation() {
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
+ .tapWireGuardObfuscationExpandButton()
+ .tapWireGuardObfuscationShadowsocksCell()
+ .tapBackButton()
+
+ SettingsPage(app)
+ .tapDoneButton()
+
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ XCTAssertTrue(app.staticTexts[AccessibilityIdentifier.obfuscationFilterPill.asString].exists)
+ }
+}