diff options
11 files changed, 315 insertions, 49 deletions
diff --git a/ios/MullvadSettings/WireGuardObfuscationSettings.swift b/ios/MullvadSettings/WireGuardObfuscationSettings.swift index 2828c4da9b..e8679fb0c4 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 1b1c87beaf..6ce2fca25e 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 */; }; @@ -1391,6 +1396,11 @@ 06FAE67A28F83CA50033DD93 /* RESTDevicesProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTDevicesProxy.swift; sourceTree = "<group>"; }; 06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = "<group>"; }; 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = "<group>"; }; + 44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPTCPObfuscationSettingsViewModel.swift; sourceTree = "<group>"; }; + 440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatefulPreviewWrapper.swift; sourceTree = "<group>"; }; + 440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObfuscationSettingsWatchingObservableObject.swift; sourceTree = "<group>"; }; + 4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPTCPObfuscationSettingsView.swift; sourceTree = "<group>"; }; + 4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleChoiceList.swift; sourceTree = "<group>"; }; 449275412C3570CA000526DE /* ICMP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICMP.swift; sourceTree = "<group>"; }; 449275432C3C3029000526DE /* TunnelPinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelPinger.swift; sourceTree = "<group>"; }; 449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdate.swift; sourceTree = "<group>"; }; @@ -2597,6 +2607,25 @@ path = Protocols; sourceTree = "<group>"; }; + 4422C06F2CCFF6520001A385 /* Obfuscation */ = { + isa = PBXGroup; + children = ( + 440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */, + 4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */, + 44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */, + ); + path = Obfuscation; + sourceTree = "<group>"; + }; + 4424CDD12CDBD457009D8C9F /* SwiftUI components */ = { + isa = PBXGroup; + children = ( + 440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */, + 4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */, + ); + path = "SwiftUI components"; + sourceTree = "<group>"; + }; 449872E22B7CB91B00094DDC /* MullvadSettings */ = { isa = PBXGroup; children = ( @@ -2825,6 +2854,8 @@ 583FE01829C19709006E85F9 /* Settings */ = { isa = PBXGroup; children = ( + 4424CDD12CDBD457009D8C9F /* SwiftUI components */, + 4422C06F2CCFF6520001A385 /* Obfuscation */, 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */, F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */, 7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */, @@ -5647,7 +5678,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 */, @@ -5719,6 +5752,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 */, @@ -5762,6 +5796,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 */, @@ -5892,6 +5927,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 c706fd9298..f916e7ae50 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 udpOverTcpObfuscationSettings // 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 0000000000..58e8144ad3 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/Obfuscation/TunnelObfuscationSettingsWatchingObservableObject.swift @@ -0,0 +1,48 @@ +// +// 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<T: Equatable>: ObservableObject { + let tunnelManager: TunnelManager + let keyPath: WritableKeyPath<WireGuardObfuscationSettings, T> + 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 + tunnelManager.updateSettings([.obfuscation(obfuscationSettings)]) + } + } + + init(tunnelManager: TunnelManager, keyPath: WritableKeyPath<WireGuardObfuscationSettings, T>, _ 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[keyPath: keyPath] + if value != newValue { + value = newValue + } + } +} 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 0000000000..70769d71ee --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/Obfuscation/UDPTCPObfuscationSettingsView.swift @@ -0,0 +1,39 @@ +// +// 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<VM>: View where VM: UDPTCPObfuscationSettingsViewModel { + @StateObject var viewModel: VM + + var body: some View { + let portString = NSLocalizedString( + "UDP_TCP_PORT_LABEL", + tableName: "UdpToTcp", + value: "Port", + comment: "" + ) + SingleChoiceList( + title: portString, + options: [WireGuardObfuscationUdpOverTcpPort.automatic, .port80, .port5001], + value: $viewModel.value, + itemDescription: { item in NSLocalizedString( + "UDP_TCP_PORT_VALUE_\(item)", + tableName: "UdpToTcp", + value: "\(item)", + comment: "" + ) } + ) + } +} + +#Preview { + let 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 0000000000..f712f0e644 --- /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, + tunnelManager.settings.wireGuardObfuscation.udpOverTcpPort + ) + } +} 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 0000000000..6077539b5d --- /dev/null +++ b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift @@ -0,0 +1,70 @@ +// +// 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<Item>: View where Item: Hashable { + let title: String + let options: [Item] + var value: Binding<Item> + let itemDescription: (Item) -> String + + init(title: String, options: [Item], value: Binding<Item>, itemDescription: ((Item) -> String)? = nil) { + self.title = title + self.options = options + self.value = value + self.itemDescription = itemDescription ?? { "\($0)" } + } + + func row(_ item: Item) -> some View { + let isSelected = value.wrappedValue == item + return HStack { + Image(uiImage: UIImage(resource: .iconTick)).opacity(isSelected ? 1.0 : 0.0) + Spacer().frame(width: UIMetrics.SettingsCell.selectableSettingsCellLeftViewSpacing) + Text(verbatim: itemDescription(item)) + Spacer() + } + .padding(EdgeInsets(UIMetrics.SettingsCell.layoutMargins)) + .background( + isSelected + ? Color(UIColor.Cell.Background.selected) + : Color(UIColor.Cell.Background.indentationLevelOne) + ) + .foregroundColor(Color(UIColor.Cell.titleTextColor)) + .onTapGesture { + value.wrappedValue = item + } + } + + var body: some View { + VStack(spacing: UIMetrics.TableView.separatorHeight) { + HStack { + Text(title).fontWeight(.semibold) + Spacer() + } + .padding(EdgeInsets(UIMetrics.SettingsCell.layoutMargins)) + .background(Color(UIColor.Cell.Background.normal)) + ForEach(options, id: \.self) { opt in + row(opt) + } + Spacer() + } + .padding(EdgeInsets(top: 24, leading: 0, bottom: 0, trailing: 0)) + .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 0000000000..c90e8e3f77 --- /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<Value, Content: View>: View { + @State var value: Value + var content: (Binding<Value>) -> Content + + var body: some View { + content($value) + } + + init(_ value: Value, content: @escaping (Binding<Value>) -> Content) { + self._value = State(wrappedValue: value) + self.content = content + } +} diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift index 4adf52ce26..f74717f1bc 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift @@ -65,7 +65,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case ipOverrides case wireGuardPorts case wireGuardObfuscation - case wireGuardObfuscationPort case quantumResistance case privacyAndSecurity } @@ -265,14 +264,16 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< } func update(from tunnelSettings: LatestTunnelSettings) { - let newViewModel = VPNSettingsViewModel(from: tunnelSettings) - let mergedViewModel = viewModel.merged(newViewModel) + updateViewModel(from: tunnelSettings) + updateSnapshot() + } - if viewModel != mergedViewModel { - viewModel = mergedViewModel - } + func reload(from tunnelSettings: LatestTunnelSettings) { + updateViewModel(from: tunnelSettings) - updateSnapshot() + var snapshot = snapshot() + snapshot.reconfigureItems(snapshot.itemIdentifiers) + applySnapshot(snapshot, animated: false) } // MARK: - UITableViewDelegate @@ -369,9 +370,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case .wireGuardObfuscation: configureObfuscationHeader(view) return view - case .wireGuardObfuscationPort: - configureObfuscationPortHeader(view) - return view case .quantumResistance: configureQuantumResistanceHeader(view) return view @@ -420,6 +418,15 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< // MARK: - Private + func updateViewModel(from tunnelSettings: LatestTunnelSettings) { + let newViewModel = VPNSettingsViewModel(from: tunnelSettings) + let mergedViewModel = viewModel.merged(newViewModel) + + if viewModel != mergedViewModel { + viewModel = mergedViewModel + } + } + private func registerClasses() { CellReuseIdentifiers.allCases.forEach { enumCase in tableView?.register( @@ -449,7 +456,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< snapshot.appendSections(Section.allCases) snapshot.appendItems([.dnsSettings], toSection: .dnsSettings) snapshot.appendItems([.ipOverrides], toSection: .ipOverrides) - snapshot.appendItems([.multihopSwitch], toSection: .privacyAndSecurity) applySnapshot(snapshot, animated: animated, completion: completion) @@ -547,36 +553,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/VPNSettingsInteractor.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift index e304a8e4b6..88fddf83b2 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift @@ -30,10 +30,12 @@ final class VPNSettingsInteractor { self.tunnelManager = tunnelManager self.relayCacheTracker = relayCacheTracker - tunnelObserver = + let tunnelObserver = TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in self?.tunnelSettingsDidChange?(newSettings) }) + self.tunnelObserver = tunnelObserver + tunnelManager.addObserver(tunnelObserver) } func updateSettings(_ changes: [TunnelSettingsUpdate], completion: (() -> Void)? = nil) { diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift index 10a519a737..f6628feaa9 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 { @@ -56,7 +57,7 @@ class VPNSettingsViewController: UITableViewController { ) interactor.tunnelSettingsDidChange = { [weak self] newSettings in - self?.dataSource?.update(from: newSettings) + self?.dataSource?.reload(from: newSettings) } dataSource?.update(from: interactor.tunnelSettings) @@ -111,9 +112,13 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate { alertPresenter.showAlert(presentation: presentation, animated: true) } - func showDetails(for: VPNSettingsDetailsButtonItem) { - // TODO: When ready, add navigation to detail views for selecting obfuscation options for - // UDP-over-TCP and shadowsocks. + func showDetails(for item: VPNSettingsDetailsButtonItem) { + switch item { + case .udpOverTcp: + showUDPOverTCPObfuscationSettings() + case .wireguardOverShadowsocks: + showShadowsocksObfuscationSettings() + } } func showDNSSettings() { @@ -125,6 +130,23 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate { delegate?.showIPOverrides() } + private func showUDPOverTCPObfuscationSettings() { + let viewModel = TunnelUDPTCPObfuscationSettingsViewModel(tunnelManager: interactor.tunnelManager) + let view = UDPTCPObfuscationSettingsView(viewModel: viewModel) + let vc = UIHostingController(rootView: view) + vc.title = NSLocalizedString( + "UDP_OVER_TCP_TITLE", + tableName: "VPNSettings", + value: "UDP-over-TCP", + comment: "" + ) + navigationController?.pushViewController(vc, animated: true) + } + + private func showShadowsocksObfuscationSettings() { + // TODO: + } + func didSelectWireGuardPort(_ port: UInt16?) { interactor.setPort(port) } |
