diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2025-04-10 10:37:07 +0200 |
|---|---|---|
| committer | Jon Petersson <jon.petersson@mullvad.net> | 2025-04-17 14:57:45 +0200 |
| commit | 811871db9197fcdf22d89d092849305273aec35c (patch) | |
| tree | af5237c99b4b8b693d6d9e635a46046e2be6a986 | |
| parent | 7244df1fbe2f871885715a19804c40d8346c7902 (diff) | |
| download | mullvadvpn-811871db9197fcdf22d89d092849305273aec35c.tar.xz mullvadvpn-811871db9197fcdf22d89d092849305273aec35c.zip | |
Add filter pill to location view when obfuscation is used
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) + } +} |
