diff --git a/ios/MullvadSettings/WireGuardObfuscationSettings.swift b/ios/MullvadSettings/WireGuardObfuscationSettings.swift index 98fab6ca2ba2..bde6ea6a8fb4 100644 --- a/ios/MullvadSettings/WireGuardObfuscationSettings.swift +++ b/ios/MullvadSettings/WireGuardObfuscationSettings.swift @@ -124,9 +124,9 @@ public struct WireGuardObfuscationSettings: Codable, Equatable { @available(*, deprecated, message: "Use `udpOverTcpPort` instead") private var port: WireGuardObfuscationPort = .automatic - public let state: WireGuardObfuscationState - public let udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort - public let shadowsocksPort: WireGuardObfuscationShadowsockPort + public var state: WireGuardObfuscationState + public var udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort + public var shadowsocksPort: WireGuardObfuscationShadowsockPort public init( state: WireGuardObfuscationState = .automatic, diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 79dbbdb46944..4a25d2ed9050 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -40,6 +40,11 @@ 06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114128F8413A0037AF9A /* AddressCache.swift */; }; 0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; }; 06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; + 44075DFB2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */; }; + 440E5AB02CDBD67D00B09614 /* StatefulPreviewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */; }; + 440E5AB42CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */; }; + 4422C0712CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */; }; + 4424CDD32CDBD4A6009D8C9F /* SingleChoiceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */; }; 449275422C3570CA000526DE /* ICMP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275412C3570CA000526DE /* ICMP.swift */; }; 449872E12B7BBC5400094DDC /* TunnelSettingsUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */; }; 449872E42B7CB96300094DDC /* TunnelSettingsUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E32B7CB96300094DDC /* TunnelSettingsUpdateTests.swift */; }; @@ -1385,6 +1390,11 @@ 06FAE67A28F83CA50033DD93 /* RESTDevicesProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTDevicesProxy.swift; sourceTree = ""; }; 06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = ""; }; 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = ""; }; + 44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPTCPObfuscationSettingsViewModel.swift; sourceTree = ""; }; + 440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatefulPreviewWrapper.swift; sourceTree = ""; }; + 440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObfuscationSettingsWatchingObservableObject.swift; sourceTree = ""; }; + 4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPTCPObfuscationSettingsView.swift; sourceTree = ""; }; + 4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleChoiceList.swift; sourceTree = ""; }; 449275412C3570CA000526DE /* ICMP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICMP.swift; sourceTree = ""; }; 449275432C3C3029000526DE /* TunnelPinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelPinger.swift; sourceTree = ""; }; 449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdate.swift; sourceTree = ""; }; @@ -2581,6 +2591,25 @@ path = Protocols; sourceTree = ""; }; + 4422C06F2CCFF6520001A385 /* Obfuscation */ = { + isa = PBXGroup; + children = ( + 4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */, + 44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */, + 440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */, + ); + path = Obfuscation; + sourceTree = ""; + }; + 4424CDD12CDBD457009D8C9F /* SwiftUI components */ = { + isa = PBXGroup; + children = ( + 440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */, + 4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */, + ); + path = "SwiftUI components"; + sourceTree = ""; + }; 449872E22B7CB91B00094DDC /* MullvadSettings */ = { isa = PBXGroup; children = ( @@ -2809,6 +2838,8 @@ 583FE01829C19709006E85F9 /* Settings */ = { isa = PBXGroup; children = ( + 4424CDD12CDBD457009D8C9F /* SwiftUI components */, + 4422C06F2CCFF6520001A385 /* Obfuscation */, 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */, F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */, 7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */, @@ -5607,7 +5638,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 44075DFB2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift in Sources */, 7A6389DC2B7E3BD6008E77E1 /* CustomListViewModel.swift in Sources */, + 4422C0712CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift in Sources */, 7A9CCCC42A96302800DD6A34 /* TunnelCoordinator.swift in Sources */, 5827B0A42B0F38FD00CCBBA1 /* EditAccessMethodInteractorProtocol.swift in Sources */, 586C0D852B03D31E00E7CDD7 /* SocksSectionHandler.swift in Sources */, @@ -5679,6 +5712,7 @@ 58DFF7D22B0256A300F864E0 /* MarkdownStylingOptions.swift in Sources */, 5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */, F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */, + 4424CDD32CDBD4A6009D8C9F /* SingleChoiceList.swift in Sources */, 5827B0BD2B14AC9200CCBBA1 /* AccessViewModel+TestingStatus.swift in Sources */, 58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */, 58EFC7752AFB4CEF00E9F4CB /* AboutViewController.swift in Sources */, @@ -5722,6 +5756,7 @@ 7A5869C72B5A8E4C00640D27 /* MethodSettingsDataSourceConfiguration.swift in Sources */, 58F2E14C276A61C000A79513 /* RotateKeyOperation.swift in Sources */, 5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */, + 440E5AB02CDBD67D00B09614 /* StatefulPreviewWrapper.swift in Sources */, F0E8E4C52A60499100ED26A3 /* AccountDeletionViewController.swift in Sources */, 7A9CCCC12A96302800DD6A34 /* AccountCoordinator.swift in Sources */, 58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */, @@ -5852,6 +5887,7 @@ 58CEB2FD2AFD19D300E6E088 /* UITableView+ReuseIdentifier.swift in Sources */, F0FADDEA2BE90AAA000D0B02 /* LaunchArguments.swift in Sources */, 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */, + 440E5AB42CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift in Sources */, 588D7EDE2AF3A585005DF40A /* ListAccessMethodItem.swift in Sources */, 5827B0B02B0F4CCD00CCBBA1 /* ListAccessMethodViewControllerDelegate.swift in Sources */, 588D7EE02AF3A595005DF40A /* ListAccessMethodInteractor.swift in Sources */, diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index c706fd9298cb..0972f965cd1d 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -177,6 +177,7 @@ public enum AccessibilityIdentifier: String { case wireGuardObfuscationUdpOverTcp case wireGuardObfuscationShadowsocks case wireGuardPort + case udpTcpObfuscationSettings // Custom DNS case blockAll diff --git a/ios/MullvadVPN/View controllers/Settings/Obfuscation/TunnelObfuscationSettingsWatchingObservableObject.swift b/ios/MullvadVPN/View controllers/Settings/Obfuscation/TunnelObfuscationSettingsWatchingObservableObject.swift new file mode 100644 index 000000000000..729b10070918 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/Obfuscation/TunnelObfuscationSettingsWatchingObservableObject.swift @@ -0,0 +1,44 @@ +// +// TunnelObfuscationSettingsWatchingObservableObject.swift +// MullvadVPN +// +// Created by Andrew Bulhak on 2024-11-07. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadSettings + +/// a generic ObservableObject that binds to obfuscation settings in TunnelManager. +/// Used as the basis for ViewModels for SwiftUI interfaces for these settings. + +class TunnelObfuscationSettingsWatchingObservableObject: ObservableObject { + let tunnelManager: TunnelManager + let keyPath: WritableKeyPath + private var tunnelObserver: TunnelObserver? + + // this is essentially @Published from scratch + var value: T { + willSet(newValue) { + guard newValue != self.value else { return } + objectWillChange.send() + var obfuscationSettings = tunnelManager.settings.wireGuardObfuscation + obfuscationSettings[keyPath: keyPath] = newValue + } + } + + init(tunnelManager: TunnelManager, keyPath: WritableKeyPath, _ initialValue: T) { + self.tunnelManager = tunnelManager + self.keyPath = keyPath + self.value = initialValue + tunnelObserver = + TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in + guard let self else { return } + updateValueFromSettings(newSettings.wireGuardObfuscation) + }) + } + + private func updateValueFromSettings(_ settings: WireGuardObfuscationSettings) { + let newValue = settings + } +} diff --git a/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsView.swift b/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsView.swift new file mode 100644 index 000000000000..faa738cf5278 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsView.swift @@ -0,0 +1,27 @@ +// +// UDPTCPObfuscationSettingsView.swift +// MullvadVPN +// +// Created by Andrew Bulhak on 2024-10-28. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import MullvadSettings +import SwiftUI + +struct UDPTCPObfuscationSettingsView: View where VM: UDPTCPObfuscationSettingsViewModel { + @StateObject var viewModel: VM + + var body: some View { + SingleChoiceList( + title: "Port", + options: [WireGuardObfuscationUdpOverTcpPort.automatic, .port80, .port5001], + value: $viewModel.value + ) + } +} + +#Preview { + var model = MockUDPTCPObfuscationSettingsViewModel(udpTcpPort: .port5001) + return UDPTCPObfuscationSettingsView(viewModel: model) +} diff --git a/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsViewModel.swift b/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsViewModel.swift new file mode 100644 index 000000000000..df5ea26cbab2 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsViewModel.swift @@ -0,0 +1,37 @@ +// +// UDPTCPObfuscationSettingsViewModel.swift +// MullvadVPN +// +// Created by Andrew Bulhak on 2024-11-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadSettings + +protocol UDPTCPObfuscationSettingsViewModel: ObservableObject { + var value: WireGuardObfuscationUdpOverTcpPort { get set } +} + +/** A simple mock view model for use in Previews and similar */ +class MockUDPTCPObfuscationSettingsViewModel: UDPTCPObfuscationSettingsViewModel { + @Published var value: WireGuardObfuscationUdpOverTcpPort + + init(udpTcpPort: WireGuardObfuscationUdpOverTcpPort = .automatic) { + self.value = udpTcpPort + } +} + +/** The live view model which interfaces with the TunnelManager */ +class TunnelUDPTCPObfuscationSettingsViewModel: TunnelObfuscationSettingsWatchingObservableObject< + WireGuardObfuscationUdpOverTcpPort +>, + UDPTCPObfuscationSettingsViewModel { + init(tunnelManager: TunnelManager) { + super.init( + tunnelManager: tunnelManager, + keyPath: \.udpOverTcpPort, + .automatic + ) + } +} diff --git a/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift new file mode 100644 index 000000000000..3374bf37e363 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift @@ -0,0 +1,55 @@ +// +// SingleChoiceList.swift +// MullvadVPN +// +// Created by Andrew Bulhak on 2024-11-06. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +/** + A component presenting a vertical list in the Mullvad style for selecting a single item from a list. + The items can be any Hashable type. + */ + +struct SingleChoiceList: View where T: Hashable { + let title: String + let options: [T] + var value: Binding + + func row(_ v: T) -> some View { + let isSelected = value.wrappedValue == v + return HStack { + Image("IconTick").opacity(isSelected ? 1.0 : 0.0) + Text(verbatim: "\(v)") + Spacer() + } + .padding(16) + .background(isSelected ? Color(UIColor.Cell.Background.selected) : Color(UIColor.Cell.Background.normal)) + .foregroundColor(Color(UIColor.Cell.titleTextColor)) + .onTapGesture { + value.wrappedValue = v + } + } + + var body: some View { + VStack(spacing: 0) { + HStack { + Text(title) + Spacer() + } + .padding(16) + ForEach(options, id: \.self) { opt in + row(opt) + } + Spacer() + } + .background(Color(.secondaryColor)) + .foregroundColor(Color(.primaryTextColor)) + } +} + +#Preview { + StatefulPreviewWrapper(1) { SingleChoiceList(title: "Test", options: [1, 2, 3], value: $0) } +} diff --git a/ios/MullvadVPN/View controllers/Settings/SwiftUI components/StatefulPreviewWrapper.swift b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/StatefulPreviewWrapper.swift new file mode 100644 index 000000000000..c90e8e3f777b --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/StatefulPreviewWrapper.swift @@ -0,0 +1,35 @@ +// +// StatefulPreviewWrapper.swift +// MullvadVPN +// +// Created by Andrew Bulhak on 2024-11-06. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +// This should probably live somewhere more central than `View controllers/Settings/SwiftUI components`. Where exactly is to be determined. + +import SwiftUI + +/** A wrapper for providing a state binding for SwiftUI Views in #Preview. This takes as arguments an initial value for the binding and a block which accepts the binding and returns a View to be previewed + The usage looks like: + + ``` + #Preview { + StatefulPreviewWrapper(initvalue) { ComponentToBePreviewed(binding: $0) } + } + ``` + */ + +struct StatefulPreviewWrapper: View { + @State var value: Value + var content: (Binding) -> Content + + var body: some View { + content($value) + } + + init(_ value: Value, content: @escaping (Binding) -> Content) { + self._value = State(wrappedValue: value) + self.content = content + } +} diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift index 526fb1c16c8c..eee4103311bb 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift @@ -66,6 +66,19 @@ final class VPNSettingsCellFactory: CellFactoryProtocol { cell.disclosureType = .chevron cell.accessibilityIdentifier = item.accessibilityIdentifier + case .udpTcpObfuscationSettings: + guard let cell = cell as? SettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "UDP_TCP_OBFUSCATION_CELL_LABEL", + tableName: "VPNSettings", + value: "UDP/TCP Obfuscation", + comment: "" + ) + + cell.disclosureType = .chevron + cell.accessibilityIdentifier = item.accessibilityIdentifier + case let .wireGuardPort(port): guard let cell = cell as? SelectableSettingsCell else { return } diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift index 4adf52ce2654..cce43501ce29 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift @@ -22,6 +22,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case wireGuardCustomPort case wireGuardObfuscation case wireGuardObfuscationOption + case udpTcpObfuscationSettings case wireGuardObfuscationPort case quantumResistance case multihop @@ -40,6 +41,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< return SelectableSettingsDetailsCell.self case .wireGuardObfuscation: return SelectableSettingsCell.self + case .udpTcpObfuscationSettings: + return SettingsCell.self case .wireGuardObfuscationPort: return SelectableSettingsCell.self case .quantumResistance: @@ -65,7 +68,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case ipOverrides case wireGuardPorts case wireGuardObfuscation - case wireGuardObfuscationPort + case udpTcpObfuscationSettings case quantumResistance case privacyAndSecurity } @@ -75,6 +78,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case ipOverrides case wireGuardPort(_ port: UInt16?) case wireGuardCustomPort + case udpTcpObfuscationSettings case wireGuardObfuscationAutomatic case wireGuardObfuscationUdpOverTcp case wireGuardObfuscationShadowsocks @@ -128,6 +132,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< return .wireGuardPort case .wireGuardCustomPort: return .wireGuardCustomPort + case .udpTcpObfuscationSettings: + return .udpTcpObfuscationSettings case .wireGuardObfuscationAutomatic: return .wireGuardObfuscationAutomatic case .wireGuardObfuscationUdpOverTcp: @@ -159,6 +165,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< return .wireGuardPort case .wireGuardCustomPort: return .wireGuardCustomPort + case .udpTcpObfuscationSettings: + return .udpTcpObfuscationSettings case .wireGuardObfuscationAutomatic, .wireGuardObfuscationOff: return .wireGuardObfuscation case .wireGuardObfuscationUdpOverTcp, .wireGuardObfuscationShadowsocks: @@ -310,6 +318,10 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case .wireGuardCustomPort: getCustomPortCell()?.textField.becomeFirstResponder() + case .udpTcpObfuscationSettings: + tableView.deselectRow(at: indexPath, animated: false) + delegate?.showUDPTCPObfuscationSettings() + case .wireGuardObfuscationAutomatic: selectObfuscationState(.automatic) delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings)) @@ -369,9 +381,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case .wireGuardObfuscation: configureObfuscationHeader(view) return view - case .wireGuardObfuscationPort: - configureObfuscationPortHeader(view) - return view case .quantumResistance: configureQuantumResistanceHeader(view) return view @@ -449,6 +458,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< snapshot.appendSections(Section.allCases) snapshot.appendItems([.dnsSettings], toSection: .dnsSettings) snapshot.appendItems([.ipOverrides], toSection: .ipOverrides) + snapshot.appendItems([.udpTcpObfuscationSettings], toSection: .udpTcpObfuscationSettings) snapshot.appendItems([.multihopSwitch], toSection: .privacyAndSecurity) @@ -547,36 +557,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< } } - private func configureObfuscationPortHeader(_ header: SettingsHeaderView) { - let title = NSLocalizedString( - "OBFUSCATION_PORT_HEADER_LABEL", - tableName: "VPNSettings", - value: "UDP-over-TCP Port", - comment: "" - ) - - header.accessibilityIdentifier = .udpOverTCPPortCell - header.titleLabel.text = title - header.accessibilityCustomActionName = title - header.isExpanded = isExpanded(.wireGuardObfuscationPort) - 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?.showInfo(for: .wireGuardObfuscationPort) } - } - } - private func configureQuantumResistanceHeader(_ header: SettingsHeaderView) { let title = NSLocalizedString( "QUANTUM_RESISTANCE_HEADER_LABEL", diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift index 98695d1f7fed..f86dbbeebb11 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift @@ -20,6 +20,7 @@ protocol VPNSettingsDataSourceDelegate: AnyObject { func showDetails(for: VPNSettingsDetailsButtonItem) func showDNSSettings() func showIPOverrides() + func showUDPTCPObfuscationSettings() func didSelectWireGuardPort(_ port: UInt16?) func humanReadablePortRepresentation() -> String } diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift index 10a519a737af..cb977651ce59 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift @@ -7,6 +7,7 @@ // import MullvadSettings +import SwiftUI import UIKit protocol VPNSettingsViewControllerDelegate: AnyObject { @@ -125,6 +126,14 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate { delegate?.showIPOverrides() } + func showUDPTCPObfuscationSettings() { + let viewModel = TunnelUDPTCPObfuscationSettingsViewModel(tunnelManager: interactor.tunnelManager) + let view = UDPTCPObfuscationSettingsView(viewModel: viewModel) + let vc = UIHostingController(rootView: view) + vc.title = "UDP TCP Obfuscation" + navigationController?.pushViewController(vc, animated: true) + } + func didSelectWireGuardPort(_ port: UInt16?) { interactor.setPort(port) }