diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2023-07-26 09:07:15 +0100 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2023-08-09 09:26:39 +0200 |
| commit | a9e7d9240e19e651cde51e5ab2b28ffe334181b3 (patch) | |
| tree | e6104df8e4c2a1bac519ba3006ba04d5c902ec54 | |
| parent | 5d00739e09600c4e525c3b5687ddb4f7ec3931fe (diff) | |
| download | mullvadvpn-a9e7d9240e19e651cde51e5ab2b28ffe334181b3.tar.xz mullvadvpn-a9e7d9240e19e651cde51e5ab2b28ffe334181b3.zip | |
Add a UI for wireguard obfuscation preferences
5 files changed, 191 insertions, 14 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 464240306d..d71fb082c5 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -1618,6 +1618,7 @@ 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */, 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */, 58CCA01122424D11004F3011 /* SettingsViewController.swift */, + 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */, ); path = Settings; sourceTree = "<group>"; diff --git a/ios/MullvadVPN/View controllers/Preferences/PreferencesCellFactory.swift b/ios/MullvadVPN/View controllers/Preferences/PreferencesCellFactory.swift index 7223808867..6ccefff8ac 100644 --- a/ios/MullvadVPN/View controllers/Preferences/PreferencesCellFactory.swift +++ b/ios/MullvadVPN/View controllers/Preferences/PreferencesCellFactory.swift @@ -245,6 +245,53 @@ final class PreferencesCellFactory: CellFactoryProtocol { isEditing: isEditing, preferredFont: .systemFont(ofSize: 14) ) + case .wireGuardObfuscationAutomatic: + guard let cell = cell as? SelectableSettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "WIRE_GUARD_OBFUSCATION_AUTOMATIC_LABEL", + tableName: "Preferences", + value: "Automatic", + comment: "" + ) + cell.accessibilityHint = nil + cell.applySubCellStyling() + case .wireGuardObfuscationOn: + guard let cell = cell as? SelectableSettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "WIRE_GUARD_OBFUSCATION_ON_LABEL", + tableName: "Preferences", + value: "On (UDP-over-TCP)", + comment: "" + ) + cell.accessibilityHint = nil + cell.applySubCellStyling() + case .wireGuardObfuscationOff: + guard let cell = cell as? SelectableSettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "WIRE_GUARD_OBFUSCATION_OFF_LABEL", + tableName: "Preferences", + value: "Off", + comment: "" + ) + cell.accessibilityHint = nil + cell.applySubCellStyling() + + case let .wireGuardObfuscationPort(port): + guard let cell = cell as? SelectableSettingsCell else { return } + + let portValue = port == 0 ? "Automatic" : "\(port)" + + cell.titleLabel.text = NSLocalizedString( + "WIRE_GUARD_OBFUSCATION_PORT_LABEL", + tableName: "Preferences", + value: portValue, + comment: "" + ) + cell.accessibilityHint = nil + cell.applySubCellStyling() } } diff --git a/ios/MullvadVPN/View controllers/Preferences/PreferencesDataSource.swift b/ios/MullvadVPN/View controllers/Preferences/PreferencesDataSource.swift index 8927d2107c..73f051597c 100644 --- a/ios/MullvadVPN/View controllers/Preferences/PreferencesDataSource.swift +++ b/ios/MullvadVPN/View controllers/Preferences/PreferencesDataSource.swift @@ -22,6 +22,8 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< case addDNSServer case wireGuardPort case wireGuardCustomPort + case wireGuardObfuscation + case wireGuardObfuscationPort var reusableViewClass: AnyClass { switch self { @@ -39,6 +41,10 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< return SelectableSettingsCell.self case .wireGuardCustomPort: return SettingsInputCell.self + case .wireGuardObfuscation: + return SelectableSettingsCell.self + case .wireGuardObfuscationPort: + return SelectableSettingsCell.self } } } @@ -62,12 +68,16 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< case contentBlockers case blockMalware case wireGuardPorts + case wireGuardObfuscation + case wireGuardObfuscationPort } enum Section: String, Hashable, CaseIterable { case contentBlockers case customDNS case wireGuardPorts + case wireGuardObfuscation + case wireGuardObfuscationPort } enum Item: Hashable { @@ -82,6 +92,10 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< case addDNSServer case dnsServer(_ uniqueID: UUID) case dnsServerInfo + case wireGuardObfuscationAutomatic + case wireGuardObfuscationOn + case wireGuardObfuscationOff + case wireGuardObfuscationPort(_ port: UInt16) static var contentBlockers: [Item] { [.blockAdvertising, .blockTracking, .blockMalware, .blockAdultContent, .blockGambling] @@ -94,6 +108,14 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< return [.wireGuardPort(nil)] + defaultPorts + [.wireGuardCustomPort] } + static var wireGuardObfuscation: [Item] { + [.wireGuardObfuscationAutomatic, .wireGuardObfuscationOn, wireGuardObfuscationOff] + } + + static var wireGuardObfuscationPort: [Item] { + [.wireGuardObfuscationPort(0), wireGuardObfuscationPort(80), wireGuardObfuscationPort(5001)] + } + var accessibilityIdentifier: String { switch self { case .blockAdvertising: @@ -122,6 +144,17 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< return "dnsServer(\(uuid.uuidString))" case .dnsServerInfo: return "dnsServerInfo" + case .wireGuardObfuscationAutomatic: + return "Automatic" + case .wireGuardObfuscationOn: + return "On (UDP-over-TCP)" + case .wireGuardObfuscationOff: + return "Off" + case let .wireGuardObfuscationPort(port): + if port == 0 { + return "Automatic" + } + return "\(port)" } } @@ -145,6 +178,10 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< return .wireGuardPort case .wireGuardCustomPort: return .wireGuardCustomPort + case .wireGuardObfuscationAutomatic, .wireGuardObfuscationOn, .wireGuardObfuscationOff: + return .wireGuardObfuscation + case .wireGuardObfuscationPort: + return .wireGuardObfuscationPort default: return .settingSwitch } @@ -331,7 +368,8 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { switch itemIdentifier(for: indexPath) { - case .wireGuardPort, .wireGuardCustomPort: + case .wireGuardPort, .wireGuardCustomPort, .wireGuardObfuscationAutomatic, .wireGuardObfuscationOn, + .wireGuardObfuscationOff, .wireGuardObfuscationPort: return true default: return false @@ -341,6 +379,8 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = itemIdentifier(for: indexPath) + deselectAllRowsInSectionExceptRowAt(indexPath) + switch item { case let .wireGuardPort(port): viewModel.setWireGuardPort(port) @@ -355,6 +395,15 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< case .wireGuardCustomPort: getCustomPortCell()?.textField.becomeFirstResponder() + case .wireGuardObfuscationAutomatic: + print("UDP over TCP Set to automatic") + case .wireGuardObfuscationOn: + print("turning on UDP over TCP") + case .wireGuardObfuscationOff: + print("Turning off UDP over TCP") + case let .wireGuardObfuscationPort(port): + print("Setting port to \(port)") + default: break } @@ -363,23 +412,27 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let sectionIdentifier = snapshot().sectionIdentifiers[section] + guard let view = tableView + .dequeueReusableHeaderFooterView( + withIdentifier: HeaderFooterReuseIdentifiers.contentBlockerHeader + .rawValue + ) as? SettingsHeaderView else { return nil } + switch sectionIdentifier { case .contentBlockers: - guard let view = tableView - .dequeueReusableHeaderFooterView( - withIdentifier: HeaderFooterReuseIdentifiers.contentBlockerHeader.rawValue - ) as? SettingsHeaderView else { return nil } configureContentBlockersHeader(view) return view case .wireGuardPorts: - guard let view = tableView - .dequeueReusableHeaderFooterView( - withIdentifier: HeaderFooterReuseIdentifiers.contentBlockerHeader.rawValue - ) as? SettingsHeaderView else { return nil } configureWireguardPortsHeader(view) return view + case .wireGuardObfuscation: + configureObfuscationHeader(view) + return view + case .wireGuardObfuscationPort: + configureObfuscationPortHeader(view) + return view default: return nil } @@ -389,14 +442,10 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< let sectionIdentifier = snapshot().sectionIdentifiers[section] switch sectionIdentifier { - case .contentBlockers: - return nil - case .wireGuardPorts: return tableView.dequeueReusableHeaderFooterView( withIdentifier: HeaderFooterReuseIdentifiers.spacer.rawValue ) - default: return nil } @@ -418,7 +467,7 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< let sectionIdentifier = snapshot().sectionIdentifiers[section] switch sectionIdentifier { - case .wireGuardPorts: + case .wireGuardObfuscationPort: return UIMetrics.sectionSpacing default: @@ -492,6 +541,15 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< } } + private func deselectAllRowsInSectionExceptRowAt(_ indexPath: IndexPath) { + guard let indexPaths = tableView?.indexPathsForSelectedRows else { return } + let rowsToDeselect = indexPaths.filter { $0.section == indexPath.section && $0.row != indexPath.row } + + rowsToDeselect.forEach { + tableView?.deselectRow(at: $0, animated: false) + } + } + private func updateSnapshot(animated: Bool = false, completion: (() -> Void)? = nil) { var newSnapshot = NSDiffableDataSourceSnapshot<Section, Item>() let oldSnapshot = snapshot() @@ -783,6 +841,58 @@ final class PreferencesDataSource: UITableViewDiffableDataSource< } } + private func configureObfuscationHeader(_ header: SettingsHeaderView) { + header.titleLabel.text = NSLocalizedString( + "OBFUSCATION_HEADER_LABEL", + tableName: "Preferences", + value: "WireGuard Obfuscation", + comment: "" + ) + + header.didCollapseHandler = { [weak self] header in + guard let self else { return } + + var snapshot = snapshot() + if header.isExpanded { + snapshot.deleteItems(Item.wireGuardObfuscation) + } else { + snapshot.appendItems(Item.wireGuardObfuscation, toSection: .wireGuardObfuscation) + } + header.isExpanded.toggle() + applySnapshot(snapshot, animated: true) + } + + header.infoButtonHandler = { [weak self] in + self.map { $0.delegate?.preferencesDataSource($0, showInfo: .wireGuardObfuscation) } + } + } + + private func configureObfuscationPortHeader(_ header: SettingsHeaderView) { + header.titleLabel.text = NSLocalizedString( + "OBFUSCATION_PORT_HEADER_LABEL", + tableName: "Preferences", + value: "UDP-over-TCP Port", + comment: "" + ) + + header.didCollapseHandler = { [weak self] header in + guard let self else { return } + + var snapshot = snapshot() + if header.isExpanded { + snapshot.deleteItems(Item.wireGuardObfuscationPort) + } else { + snapshot.appendItems(Item.wireGuardObfuscationPort, toSection: .wireGuardObfuscationPort) + } + header.isExpanded.toggle() + applySnapshot(snapshot, animated: true) + } + + header.infoButtonHandler = { [weak self] in + self.map { $0.delegate?.preferencesDataSource($0, showInfo: .wireGuardObfuscationPort) } + } + } + private func selectRow(at indexPath: IndexPath?, animated: Bool = false) { tableView?.selectRow(at: indexPath, animated: false, scrollPosition: .none) } diff --git a/ios/MullvadVPN/View controllers/Preferences/PreferencesViewController.swift b/ios/MullvadVPN/View controllers/Preferences/PreferencesViewController.swift index c6c3bfc218..4410c47d1d 100644 --- a/ios/MullvadVPN/View controllers/Preferences/PreferencesViewController.swift +++ b/ios/MullvadVPN/View controllers/Preferences/PreferencesViewController.swift @@ -35,6 +35,7 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel tableView.estimatedRowHeight = 60 tableView.estimatedSectionHeaderHeight = tableView.estimatedRowHeight tableView.allowsSelectionDuringEditing = true + tableView.allowsMultipleSelection = true dataSource = PreferencesDataSource(tableView: tableView) dataSource?.delegate = self @@ -161,6 +162,21 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel portsString ) + case .wireGuardObfuscation: + message = NSLocalizedString( + "PREFERENCES_WIRE_GUARD_OBFUSCATION_GENERAL", + tableName: "WireGuardObfuscation", + value: "Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connect would be blocked.", + comment: "" + ) + + case .wireGuardObfuscationPort: + message = NSLocalizedString( + "PREFERENCES_WIRE_GUARD_OBFUSCATION_PORT_GENERAL", + tableName: "WireGuardObfuscation", + value: "Which TCP port the UDP-over-TCP obfuscation protocol should connect to on the VPN server.", + comment: "" + ) default: assertionFailure("No matching InfoButtonItem") } diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift b/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift index 51bb8e2742..4a536528a5 100644 --- a/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift +++ b/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift @@ -119,6 +119,9 @@ class SettingsHeaderView: UITableViewHeaderFooterView { } private func updateAccessibilityCustomActions() { + #warning( + "SettingsHeaderView is reused for more than content blockers now, this code doesn't seem correct anymore." + ) let actionName = isExpanded ? NSLocalizedString( "CONTENT_BLOCKERS_COLLAPSE_ACCESSIBILITY_ACTION", |
