summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2025-06-10 16:14:52 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-06-10 16:14:52 +0200
commitddfcd72094c3bc3fedce22ce5003b98a8bff2504 (patch)
treeb69e2a885d7f110386f369a81456a4385d6950f2
parent6d6cf338b2babd95590ee0f507f7fd9f7e048859 (diff)
parent5a86d15003f012f4ef2b98d66b9930fe342c9f10 (diff)
downloadmullvadvpn-ddfcd72094c3bc3fedce22ce5003b98a8bff2504.tar.xz
mullvadvpn-ddfcd72094c3bc3fedce22ce5003b98a8bff2504.zip
Merge branch 'implement-quick-access-design-ios-1175'
-rw-r--r--ios/CHANGELOG.md22
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj17
-rw-r--r--ios/MullvadVPN/Classes/AppRoutes.swift22
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift106
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift25
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/Multihop/MultihopSettingsCoordinator.swift69
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift3
-rw-r--r--ios/MullvadVPN/Coordinators/TunnelCoordinator.swift18
-rw-r--r--ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift42
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift8
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift54
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift18
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift43
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift17
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift10
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCoordinator.swift42
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift17
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift99
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift19
21 files changed, 539 insertions, 118 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 1dfd86c9db..396012005b 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -23,17 +23,29 @@ Line wrap the file at 100 chars. Th
## Unreleased
### Added
-- Make account number copyable on welcome screen.
+- Make feature indicators clickable shortcuts to their corresponding settings.
### Changed
-- Improve the filter view to display the number of available servers based on selected criteria.
+- Improve location view to filter out servers not compatible with custom obfuscation port.
- Make the app feel more responsive when reconnecting.
- Replace Classic McEliece with HQC as one of the post-quantum safe key exchange
mechanisms used for the quantum-resistant tunnels. The main benefits here are that HQC
uses a lot less CPU to compute the keypair, and the public key sent to the server
is drastically smaller.
-## 2025.2 - 2025-02-08
+## [2025.4 - 2025-05-20]
+### Added
+- Make account number copyable on welcome screen.
+- Add animations for connection view.
+
+### Changed
+- Improve the filter view to display the number of available servers based on selected criteria.
+
+## [2025.3 - 2025-03-06]
+### Fixed
+- Fix DAITA for multihop.
+
+## [2025.2 - 2025-02-08]
### Added
- Add different themes for app icons
@@ -46,10 +58,6 @@ Line wrap the file at 100 chars. Th
### Removed
- Remove Google's resolvers from encrypted DNS proxy.
-## [2025.3 - 2025-03-06]
-### Fixed
-- Fix DAITA for multihop.
-
## [2025.1 - 2025-01-14]
### Added
- Update to DAITA v2 - now machines are provided by relays dynamically instead
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 2251f071e2..eb12f8006a 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -1112,13 +1112,16 @@
F910A43A2D4A283D002FF3BB /* InAppPurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */; };
F910A8572D523812002FF3BB /* TunnelSettingsV7.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */; };
F91B94A72DC9EB5E00132C28 /* MullvadInfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F91B94A62DC9EB5E00132C28 /* MullvadInfoHeaderView.swift */; };
+ F924C4532D70692E001F4660 /* MullvadApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C4522D706929001F4660 /* MullvadApiTests.swift */; };
+ F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C5A32DA65F28001F4660 /* Storekit2.swift */; };
+ F924C65F2DAE4554001F4660 /* ServerRelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C65E2DAE4554001F4660 /* ServerRelayTests.swift */; };
F9276C622DBA2103006FE43D /* Font+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9276C612DBA20FC006FE43D /* Font+Mullvad.swift */; };
F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; };
F9394EF02DC0B58D009595EA /* MullvadListNavigationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEF2DC0B58D009595EA /* MullvadListNavigationItemView.swift */; };
F9394EF32DC21D8C009595EA /* MullvadList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EF22DC21D8C009595EA /* MullvadList.swift */; };
- F924C4532D70692E001F4660 /* MullvadApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C4522D706929001F4660 /* MullvadApiTests.swift */; };
- F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C5A32DA65F28001F4660 /* Storekit2.swift */; };
- F924C65F2DAE4554001F4660 /* ServerRelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C65E2DAE4554001F4660 /* ServerRelayTests.swift */; };
+ F97C38C82DE48AAE006DCB08 /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; };
+ F97C38CA2DE49869006DCB08 /* MultihopSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C38C92DE49869006DCB08 /* MultihopSettingsCoordinator.swift */; };
+ F97C38D92DE5930F006DCB08 /* CustomDNSCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C38D82DE59307006DCB08 /* CustomDNSCoordinator.swift */; };
F998EFF82D359C4600D88D01 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; };
F998EFFA2D3656BA00D88D01 /* SKProduct+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = F998EFF92D3656B100D88D01 /* SKProduct+Sorting.swift */; };
F9E3BCF72DD35B78009986C3 /* ListAccessViewModelBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E3BCF62DD35B78009986C3 /* ListAccessViewModelBridge.swift */; };
@@ -2541,6 +2544,8 @@
F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Mullvad.swift"; sourceTree = "<group>"; };
F9394EEF2DC0B58D009595EA /* MullvadListNavigationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadListNavigationItemView.swift; sourceTree = "<group>"; };
F9394EF22DC21D8C009595EA /* MullvadList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadList.swift; sourceTree = "<group>"; };
+ F97C38C92DE49869006DCB08 /* MultihopSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopSettingsCoordinator.swift; sourceTree = "<group>"; };
+ F97C38D82DE59307006DCB08 /* CustomDNSCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDNSCoordinator.swift; sourceTree = "<group>"; };
F998EFF92D3656B100D88D01 /* SKProduct+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Sorting.swift"; sourceTree = "<group>"; };
F9E3BCF62DD35B78009986C3 /* ListAccessViewModelBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListAccessViewModelBridge.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -3256,6 +3261,7 @@
583FE01A29C19777006E85F9 /* VPNSettings */ = {
isa = PBXGroup;
children = (
+ F97C38D82DE59307006DCB08 /* CustomDNSCoordinator.swift */,
7A6F2FAA2AFD3097006D0856 /* CustomDNSCellFactory.swift */,
7A6F2FA82AFD0842006D0856 /* CustomDNSDataSource.swift */,
7A6F2FAC2AFD3DA7006D0856 /* CustomDNSViewController.swift */,
@@ -4347,6 +4353,7 @@
7A8A18F72CE34E8F000BCB5B /* Multihop */ = {
isa = PBXGroup;
children = (
+ F97C38C92DE49869006DCB08 /* MultihopSettingsCoordinator.swift */,
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */,
7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */,
);
@@ -5941,7 +5948,7 @@
7A5869C32B5820CE00640D27 /* IPOverrideRepositoryTests.swift in Sources */,
A9A5FA392ACB05910083449F /* UIColor+Palette.swift in Sources */,
7A5468AD2C6B5E4B00590086 /* LocationRelays.swift in Sources */,
- F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */,
+ F97C38C82DE48AAE006DCB08 /* Color+Mullvad.swift in Sources */,
A9A5FA3A2ACB05910083449F /* UIEdgeInsets+Extensions.swift in Sources */,
A9A5FA3B2ACB05910083449F /* UIMetrics.swift in Sources */,
58B07C182AEFDD6C00A09625 /* StoreTransactionLog.swift in Sources */,
@@ -6483,6 +6490,7 @@
7A6389E22B7E3BD6008E77E1 /* CustomListInteractor.swift in Sources */,
F07CFF2029F2720E008C0343 /* NewDeviceNotificationProvider.swift in Sources */,
7A6389E12B7E3BD6008E77E1 /* CustomListSectionIdentifier.swift in Sources */,
+ F97C38CA2DE49869006DCB08 /* MultihopSettingsCoordinator.swift in Sources */,
58CEB2F32AFD0BA100E6E088 /* TextCellContentView.swift in Sources */,
7A6389E72B7E42BE008E77E1 /* CustomListViewController.swift in Sources */,
586C0D7C2B03BDD100E7CDD7 /* AccessMethodViewModel.swift in Sources */,
@@ -6613,6 +6621,7 @@
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */,
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */,
+ F97C38D92DE5930F006DCB08 /* CustomDNSCoordinator.swift in Sources */,
58FF9FF42B07C61B00E4C97D /* AccessMethodValidationError.swift in Sources */,
5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
5871167F2910035700D41AAC /* VPNSettingsInteractor.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AppRoutes.swift b/ios/MullvadVPN/Classes/AppRoutes.swift
index 5fb75bdf5d..697c59f79a 100644
--- a/ios/MullvadVPN/Classes/AppRoutes.swift
+++ b/ios/MullvadVPN/Classes/AppRoutes.swift
@@ -83,6 +83,26 @@ enum AppRoute: AppRouteProtocol {
case settings(SettingsNavigationRoute?)
/**
+ Settings route. Contains sub-route to display.
+ */
+ case vpnSettings(VPNSettingsSection?)
+
+ /**
+ Multihop standalone route (not subsetting).
+ */
+ case multihop
+
+ /**
+ DNS settings standalone route (not subsetting).
+ */
+ case dnsSettings
+
+ /**
+ Ip overrides standalone route (not subsetting).
+ */
+ case ipOverrides
+
+ /**
DAITA standalone route (not subsetting).
*/
case daita
@@ -133,7 +153,7 @@ enum AppRoute: AppRouteProtocol {
return .selectLocation
case .account:
return .account
- case .settings, .daita, .changelog:
+ case .settings, .daita, .changelog, .vpnSettings, .multihop, .dnsSettings, .ipOverrides:
return .settings
case let .alert(id):
return .alert(id)
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index 87a2cbf24e..b429c9053e 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -151,6 +151,18 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
case .alert:
presentAlert(animated: animated, context: context, completion: completion)
+ case let .vpnSettings(section):
+ presentVPNSettings(
+ section: section,
+ animated: animated,
+ completion: completion
+ )
+ case .multihop:
+ presentMultihop(animated: animated, completion: completion)
+ case .dnsSettings:
+ presentDNSSettings(animated: animated, completion: completion)
+ case .ipOverrides:
+ presentIPOverride(animated: animated, completion: completion)
}
}
@@ -502,6 +514,10 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
self?.router.present(.selectLocation, animated: true)
}
+ tunnelCoordinator.showFeatureSetting = { [weak self] route in
+ self?.router.present(route, animated: true)
+ }
+
return tunnelCoordinator
}
@@ -605,6 +621,35 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
}
}
+ func presentVPNSettings(
+ section: VPNSettingsSection?,
+ animated: Bool,
+ completion: @escaping @Sendable (Coordinator) -> Void
+ ) {
+ let interactorFactory = SettingsInteractorFactory(
+ tunnelManager: tunnelManager,
+ apiProxy: apiProxy,
+ relayCacheTracker: relayCacheTracker,
+ ipOverrideRepository: ipOverrideRepository
+ )
+ let coordinator = VPNSettingsCoordinator(
+ navigationController: CustomNavigationController(),
+ interactorFactory: interactorFactory,
+ ipOverrideRepository: ipOverrideRepository,
+ route: .vpnSettings(section)
+ )
+
+ coordinator.didFinish = { [weak self] _ in
+ self?.router.dismiss(.vpnSettings(section), animated: true)
+ }
+
+ coordinator.start(animated: animated)
+
+ presentChild(coordinator, animated: animated) {
+ completion(coordinator)
+ }
+ }
+
private func presentDAITA(animated: Bool, completion: @escaping @Sendable (Coordinator) -> Void) {
let viewModel = DAITATunnelSettingsViewModel(tunnelManager: tunnelManager)
let coordinator = DAITASettingsCoordinator(
@@ -624,6 +669,67 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
}
}
+ private func presentMultihop(animated: Bool, completion: @escaping @Sendable (Coordinator) -> Void) {
+ let viewModel = MultihopTunnelSettingsViewModel(
+ tunnelManager: tunnelManager
+ )
+ let coordinator = MultihopSettingsCoordinator(
+ navigationController: CustomNavigationController(),
+ route: .multihop,
+ viewModel: viewModel
+ )
+
+ coordinator.didFinish = { [weak self] _ in
+ self?.router.dismiss(.multihop, animated: true)
+ }
+
+ coordinator.start(animated: animated)
+
+ presentChild(coordinator, animated: animated) {
+ completion(coordinator)
+ }
+ }
+
+ private func presentIPOverride(animated: Bool, completion: @escaping @Sendable (Coordinator) -> Void) {
+ let coordinator = IPOverrideCoordinator(
+ navigationController: CustomNavigationController(),
+ repository: ipOverrideRepository,
+ tunnelManager: tunnelManager,
+ route: .ipOverrides
+ )
+
+ coordinator.didFinish = { [weak self] _ in
+ self?.router.dismiss(.ipOverrides, animated: true)
+ }
+
+ coordinator.start(animated: animated)
+
+ presentChild(coordinator, animated: animated) {
+ completion(coordinator)
+ }
+ }
+
+ private func presentDNSSettings(animated: Bool, completion: @escaping @Sendable (Coordinator) -> Void) {
+ let coordinator = CustomDNSCoordinator(
+ navigationController: CustomNavigationController(),
+ interactor: VPNSettingsInteractor(
+ tunnelManager: tunnelManager,
+ relayCacheTracker: relayCacheTracker
+ ),
+ route: .dnsSettings
+ )
+
+ coordinator.didFinish = { [weak self] _ in
+ self?.router.dismiss(.dnsSettings, animated: true)
+ }
+
+ coordinator.start(animated: animated)
+
+ presentChild(coordinator, animated: animated) {
+ completion(coordinator)
+ }
+ }
+
private func addTunnelObserver() {
let tunnelObserver =
TunnelBlockObserver(
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
index 6e43027a95..953189d5a2 100644
--- a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
@@ -11,21 +11,29 @@ import MullvadTypes
import Routing
import UIKit
-class IPOverrideCoordinator: Coordinator, Presenting, SettingsChildCoordinator {
+class IPOverrideCoordinator: Coordinator, Presentable, Presenting, SettingsChildCoordinator {
private let navigationController: UINavigationController
private let interactor: IPOverrideInteractor
-
+ private let route: AppRoute?
var presentationContext: UIViewController {
navigationController
}
+ var presentedViewController: UIViewController {
+ navigationController
+ }
+
+ var didFinish: ((IPOverrideCoordinator) -> Void)?
+
init(
navigationController: UINavigationController,
repository: IPOverrideRepositoryProtocol,
- tunnelManager: TunnelManager
+ tunnelManager: TunnelManager,
+ route: AppRoute?
) {
self.navigationController = navigationController
interactor = IPOverrideInteractor(repository: repository, tunnelManager: tunnelManager)
+ self.route = route
}
func start(animated: Bool) {
@@ -36,6 +44,17 @@ class IPOverrideCoordinator: Coordinator, Presenting, SettingsChildCoordinator {
controller.delegate = self
+ if route == .ipOverrides {
+ let doneButton = UIBarButtonItem(
+ systemItem: .done,
+ primaryAction: UIAction(handler: { [weak self] _ in
+ guard let self else { return }
+ didFinish?(self)
+ })
+ )
+ controller.navigationItem.rightBarButtonItem = doneButton
+ }
+
navigationController.pushViewController(controller, animated: animated)
}
}
diff --git a/ios/MullvadVPN/Coordinators/Settings/Multihop/MultihopSettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/Multihop/MultihopSettingsCoordinator.swift
new file mode 100644
index 0000000000..b54bab2368
--- /dev/null
+++ b/ios/MullvadVPN/Coordinators/Settings/Multihop/MultihopSettingsCoordinator.swift
@@ -0,0 +1,69 @@
+//
+// DAITASettingsCoordinator.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-01-20.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Routing
+import SwiftUI
+
+class MultihopSettingsCoordinator: Coordinator, SettingsChildCoordinator, Presentable, Presenting {
+ private let navigationController: UINavigationController
+ private let viewModel: MultihopTunnelSettingsViewModel
+ private var alertPresenter: AlertPresenter?
+ private let route: AppRoute
+
+ var presentedViewController: UIViewController {
+ navigationController
+ }
+
+ var didFinish: ((MultihopSettingsCoordinator) -> Void)?
+
+ init(
+ navigationController: UINavigationController,
+ route: AppRoute,
+ viewModel: MultihopTunnelSettingsViewModel
+ ) {
+ self.navigationController = navigationController
+ self.route = route
+ self.viewModel = viewModel
+
+ super.init()
+
+ alertPresenter = AlertPresenter(context: self)
+ }
+
+ func start(animated: Bool) {
+ let view = SettingsMultihopView(tunnelViewModel: self.viewModel)
+
+ let host = UIHostingController(rootView: view)
+ host.title = NSLocalizedString(
+ "NAVIGATION_TITLE_MULTIHOP",
+ tableName: "Settings",
+ value: "Multihop",
+ comment: ""
+ )
+ host.view.setAccessibilityIdentifier(.multihopView)
+ customiseNavigation(on: host)
+
+ navigationController.pushViewController(host, animated: animated)
+ }
+
+ private func customiseNavigation(on viewController: UIViewController) {
+ if route == .multihop {
+ navigationController.navigationItem.largeTitleDisplayMode = .always
+ navigationController.navigationBar.prefersLargeTitles = true
+
+ let doneButton = UIBarButtonItem(
+ systemItem: .done,
+ primaryAction: UIAction(handler: { [weak self] _ in
+ guard let self else { return }
+ didFinish?(self)
+ })
+ )
+ viewController.navigationItem.rightBarButtonItem = doneButton
+ }
+ }
+}
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
index 33119bf4b3..ea2ef51e32 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
@@ -76,7 +76,8 @@ struct SettingsViewControllerFactory {
return .childCoordinator(VPNSettingsCoordinator(
navigationController: navigationController,
interactorFactory: interactorFactory,
- ipOverrideRepository: ipOverrideRepository
+ ipOverrideRepository: ipOverrideRepository,
+ route: .settings(.vpnSettings)
))
}
diff --git a/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift b/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift
index 0ff34e9062..e7a562f9a6 100644
--- a/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift
@@ -24,6 +24,7 @@ class TunnelCoordinator: Coordinator, Presenting {
}
var showSelectLocationPicker: (() -> Void)?
+ var showFeatureSetting: ((AppRoute) -> Void)?
init(
tunnelManager: TunnelManager,
@@ -49,6 +50,23 @@ class TunnelCoordinator: Coordinator, Presenting {
controller.shouldShowCancelTunnelAlert = { [weak self] in
self?.showCancelTunnelAlert()
}
+
+ controller.shouldShowSettingsForFeature = { [weak self] feature in
+ switch feature {
+ case .daita:
+ self?.showFeatureSetting?(.daita)
+ case .multihop:
+ self?.showFeatureSetting?(.multihop)
+ case .quantumResistance:
+ self?.showFeatureSetting?(.vpnSettings(.quantumResistance))
+ case .obfuscation:
+ self?.showFeatureSetting?(.vpnSettings(.obfuscation))
+ case .dns:
+ self?.showFeatureSetting?(.dnsSettings)
+ case .ipOverrides:
+ self?.showFeatureSetting?(.ipOverrides)
+ }
+ }
}
func start() {
diff --git a/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift
index dc099d6166..805cdd2a5a 100644
--- a/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift
@@ -11,35 +11,66 @@ import MullvadTypes
import Routing
import UIKit
-class VPNSettingsCoordinator: Coordinator, Presenting, SettingsChildCoordinator {
+enum VPNSettingsSection: Equatable {
+ case quantumResistance
+ case obfuscation
+}
+
+class VPNSettingsCoordinator: Coordinator, Presenting, Presentable, SettingsChildCoordinator {
private let navigationController: UINavigationController
private let interactorFactory: SettingsInteractorFactory
private let ipOverrideRepository: IPOverrideRepositoryProtocol
+ private let route: AppRoute
var presentationContext: UIViewController {
navigationController
}
+ var presentedViewController: UIViewController {
+ navigationController
+ }
+
+ var didFinish: ((VPNSettingsCoordinator) -> Void)?
+
init(
navigationController: UINavigationController,
interactorFactory: SettingsInteractorFactory,
- ipOverrideRepository: IPOverrideRepositoryProtocol
+ ipOverrideRepository: IPOverrideRepositoryProtocol,
+ route: AppRoute
) {
self.navigationController = navigationController
self.interactorFactory = interactorFactory
self.ipOverrideRepository = ipOverrideRepository
+ self.route = route
}
func start(animated: Bool) {
+ let section: VPNSettingsSection? = if case let .vpnSettings(route) = route { route } else {
+ nil
+ }
let controller = VPNSettingsViewController(
interactor: interactorFactory.makeVPNSettingsInteractor(),
- alertPresenter: AlertPresenter(context: self)
+ alertPresenter: AlertPresenter(context: self),
+ section: section
)
controller.delegate = self
-
+ customiseNavigation(on: controller)
navigationController.pushViewController(controller, animated: animated)
}
+
+ private func customiseNavigation(on viewController: UIViewController) {
+ if case .vpnSettings = route {
+ let doneButton = UIBarButtonItem(
+ systemItem: .done,
+ primaryAction: UIAction(handler: { [weak self] _ in
+ guard let self else { return }
+ didFinish?(self)
+ })
+ )
+ viewController.navigationItem.rightBarButtonItem = doneButton
+ }
+ }
}
extension VPNSettingsCoordinator: @preconcurrency VPNSettingsViewControllerDelegate {
@@ -47,7 +78,8 @@ extension VPNSettingsCoordinator: @preconcurrency VPNSettingsViewControllerDeleg
let coordinator = IPOverrideCoordinator(
navigationController: navigationController,
repository: ipOverrideRepository,
- tunnelManager: interactorFactory.tunnelManager
+ tunnelManager: interactorFactory.tunnelManager,
+ route: nil
)
addChild(coordinator)
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift b/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift
index 1db4841b8a..fe2a1e0a55 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift
@@ -49,7 +49,12 @@ class SettingsHeaderView: UITableViewHeaderFooterView {
}
}
- var didCollapseHandler: CollapseHandler?
+ var didCollapseHandler: CollapseHandler? {
+ didSet {
+ collapseButton.isHidden = didCollapseHandler == nil
+ }
+ }
+
var infoButtonHandler: InfoButtonHandler? { didSet {
infoButton.isHidden = infoButtonHandler == nil
}}
@@ -68,6 +73,7 @@ class SettingsHeaderView: UITableViewHeaderFooterView {
for: .touchUpInside
)
+ collapseButton.isHidden = true
collapseButton.addTarget(
self,
action: #selector(handleCollapseButton(_:)),
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift
index 8b67445b6f..6c31610be4 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift
@@ -58,35 +58,37 @@ struct ChipContainerView<ViewModel>: View where ViewModel: ChipViewModelProtocol
nonisolated(unsafe) var height = CGFloat.zero
return ForEach(chips) { data in
- ChipView(item: data)
- .padding(
- EdgeInsets(
- top: verticalPadding,
- leading: 0,
- bottom: verticalPadding,
- trailing: UIMetrics.FeatureIndicators.chipViewTrailingMargin
- )
+ ChipView(item: data) {
+ viewModel.onPressed(item: data)
+ }
+ .padding(
+ EdgeInsets(
+ top: verticalPadding,
+ leading: 0,
+ bottom: verticalPadding,
+ trailing: UIMetrics.FeatureIndicators.chipViewTrailingMargin
)
- .alignmentGuide(.leading) { dimension in
- if abs(width - dimension.width) > containerWidth {
- width = 0
- height -= dimension.height
- }
- let result = width
- if data.id == chips.last?.id {
- width = 0
- } else {
- width -= dimension.width
- }
- return result
+ )
+ .alignmentGuide(.leading) { dimension in
+ if abs(width - dimension.width) > containerWidth {
+ width = 0
+ height -= dimension.height
}
- .alignmentGuide(.top) { _ in
- let result = height
- if data.id == chips.last?.id {
- height = 0
- }
- return result
+ let result = width
+ if data.id == chips.last?.id {
+ width = 0
+ } else {
+ width -= dimension.width
}
+ return result
+ }
+ .alignmentGuide(.top) { _ in
+ let result = height
+ if data.id == chips.last?.id {
+ height = 0
+ }
+ return result
+ }
}
}
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
index dd26aa654f..02cc17f5e7 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
@@ -13,12 +13,23 @@ import SwiftUI
// to be able to fetch the string value at a later point (eg. in ChipViewModelProtocol,
// when calculating the text widths of the chips).
-protocol ChipFeature {
+protocol ChipFeature: Identifiable {
+ var id: FeatureType { get }
var isEnabled: Bool { get }
var name: String { get }
}
+enum FeatureType {
+ case daita
+ case multihop
+ case quantumResistance
+ case obfuscation
+ case dns
+ case ipOverrides
+}
+
struct DaitaFeature: ChipFeature {
+ let id: FeatureType = .daita
let state: TunnelState
var isEnabled: Bool {
@@ -36,6 +47,7 @@ struct DaitaFeature: ChipFeature {
}
struct QuantumResistanceFeature: ChipFeature {
+ let id: FeatureType = .quantumResistance
let state: TunnelState
var isEnabled: Bool {
@@ -53,6 +65,7 @@ struct QuantumResistanceFeature: ChipFeature {
}
struct MultihopFeature: ChipFeature {
+ let id: FeatureType = .multihop
let state: TunnelState
var isEnabled: Bool {
state.isMultihop
@@ -69,6 +82,7 @@ struct MultihopFeature: ChipFeature {
}
struct ObfuscationFeature: ChipFeature {
+ let id: FeatureType = .obfuscation
let settings: LatestTunnelSettings
let state: ObservedState
@@ -100,6 +114,7 @@ struct ObfuscationFeature: ChipFeature {
}
struct DNSFeature: ChipFeature {
+ let id: FeatureType = .dns
let settings: LatestTunnelSettings
var isEnabled: Bool {
@@ -126,6 +141,7 @@ struct DNSFeature: ChipFeature {
}
struct IPOverrideFeature: ChipFeature {
+ let id: FeatureType = .ipOverrides
let state: TunnelState
let overrides: [IPOverride]
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift
index 829459b15f..097bcbb552 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift
@@ -10,6 +10,6 @@ import Foundation
import SwiftUI
struct ChipModel: Identifiable {
- var id: String { name }
+ var id: FeatureType
let name: String
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift
index de9376cc13..f79743faf7 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift
@@ -10,33 +10,38 @@ import SwiftUI
struct ChipView: View {
let item: ChipModel
+ let onPress: (() -> Void)?
private let borderWidth: CGFloat = 1
var body: some View {
- Text(item.name)
- .font(.subheadline)
- .lineLimit(1)
- .foregroundStyle(UIColor.primaryTextColor.color)
- .padding(.horizontal, UIMetrics.FeatureIndicators.chipViewHorisontalPadding)
- .padding(.vertical, 4)
- .background(
- RoundedRectangle(cornerRadius: 8)
- .stroke(
- UIColor.primaryColor.color,
- lineWidth: borderWidth
- )
- .background(
- RoundedRectangle(cornerRadius: 8)
- .fill(UIColor.secondaryColor.color)
- )
- .padding(borderWidth)
- )
+ Button {
+ onPress?()
+ } label: {
+ Text(item.name)
+ .font(.subheadline)
+ .lineLimit(1)
+ .foregroundStyle(UIColor.primaryTextColor.color)
+ .padding(.horizontal, UIMetrics.FeatureIndicators.chipViewHorisontalPadding)
+ .padding(.vertical, 4)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(
+ UIColor.primaryColor.color,
+ lineWidth: borderWidth
+ )
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(UIColor.secondaryColor.color)
+ )
+ .padding(borderWidth)
+ )
+ }
}
}
#Preview {
ZStack {
- ChipView(item: ChipModel(name: "Example"))
+ ChipView(item: ChipModel(id: .daita, name: "Example")) {}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(UIColor.secondaryColor.color)
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift
index 6d2a6cfb2c..b5fbd83e06 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift
@@ -10,6 +10,7 @@ import SwiftUI
protocol ChipViewModelProtocol: ObservableObject {
var chips: [ChipModel] { get }
+ func onPressed(item: ChipModel)
}
extension ChipViewModelProtocol {
@@ -53,13 +54,15 @@ extension ChipViewModelProtocol {
}
class MockFeatureIndicatorsViewModel: ChipViewModelProtocol {
+ func onPressed(item: ChipModel) {}
+
@Published var chips: [ChipModel] = [
- ChipModel(name: "DAITA"),
- ChipModel(name: "Obfuscation"),
- ChipModel(name: "Quantum resistance"),
- ChipModel(name: "Multihop"),
- ChipModel(name: "DNS content blockers"),
- ChipModel(name: "Custom DNS"),
- ChipModel(name: "Server IP override"),
+ ChipModel(id: .daita, name: "DAITA"),
+ ChipModel(id: .obfuscation, name: "Obfuscation"),
+ ChipModel(id: .quantumResistance, name: "Quantum resistance"),
+ ChipModel(id: .multihop, name: "Multihop"),
+ ChipModel(id: .dns, name: "DNS content blockers"),
+ ChipModel(id: .dns, name: "Custom DNS"),
+ ChipModel(id: .ipOverrides, name: "Server IP override"),
]
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift
index 5458efb7a8..5b2ca87561 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift
@@ -15,7 +15,7 @@ class FeatureIndicatorsViewModel: ChipViewModelProtocol {
@Published var ipOverrides: [IPOverride]
@Published var tunnelState: TunnelState
@Published var observedState: ObservedState
-
+ var onFeaturePressed: ((FeatureType) -> Void)?
init(
tunnelSettings: LatestTunnelSettings,
ipOverrides: [IPOverride],
@@ -33,7 +33,7 @@ class FeatureIndicatorsViewModel: ChipViewModelProtocol {
switch tunnelState {
case .connecting, .reconnecting, .negotiatingEphemeralPeer,
.connected, .pendingReconnect:
- let features: [ChipFeature] = [
+ let features: [any ChipFeature] = [
DaitaFeature(state: tunnelState),
QuantumResistanceFeature(state: tunnelState),
MultihopFeature(state: tunnelState),
@@ -44,9 +44,13 @@ class FeatureIndicatorsViewModel: ChipViewModelProtocol {
return features
.filter { $0.isEnabled }
- .map { ChipModel(name: $0.name) }
+ .map { ChipModel(id: $0.id, name: $0.name) }
default:
return []
}
}
+
+ func onPressed(item: ChipModel) {
+ onFeaturePressed?(item.id)
+ }
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
index fa81a6cbbd..6c3c0332a9 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
@@ -25,6 +25,7 @@ class TunnelViewController: UIViewController, RootContainment {
var shouldShowSelectLocationPicker: (() -> Void)?
var shouldShowCancelTunnelAlert: (() -> Void)?
+ var shouldShowSettingsForFeature: ((FeatureType) -> Void)?
let activityIndicator: SpinnerActivityIndicatorView = {
let activityIndicator = SpinnerActivityIndicatorView(style: .large)
@@ -91,6 +92,9 @@ class TunnelViewController: UIViewController, RootContainment {
override func viewDidLoad() {
super.viewDidLoad()
+ indicatorsViewViewModel.onFeaturePressed = { [weak self] feature in
+ self?.shouldShowSettingsForFeature?(feature)
+ }
interactor.didUpdateDeviceState = { [weak self] _, _ in
self?.setNeedsHeaderBarStyleAppearanceUpdate()
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCoordinator.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCoordinator.swift
new file mode 100644
index 0000000000..b7da14dc1d
--- /dev/null
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCoordinator.swift
@@ -0,0 +1,42 @@
+import Routing
+import UIKit
+
+class CustomDNSCoordinator: Coordinator, Presentable, Presenting {
+ private let navigationController: UINavigationController
+ private let interactor: VPNSettingsInteractor
+ private let route: AppRoute
+
+ var didFinish: ((CustomDNSCoordinator) -> Void)?
+
+ var presentedViewController: UIViewController {
+ navigationController
+ }
+
+ init(navigationController: UINavigationController, interactor: VPNSettingsInteractor, route: AppRoute) {
+ self.interactor = interactor
+ self.navigationController = navigationController
+ self.route = route
+ }
+
+ func start(animated: Bool) {
+ let alertPresenter = AlertPresenter(context: self)
+ let viewController = CustomDNSViewController(interactor: interactor, alertPresenter: alertPresenter)
+ customiseNavigation(on: viewController)
+ navigationController.pushViewController(viewController, animated: animated)
+ }
+
+ private func customiseNavigation(on viewController: UIViewController) {
+ if route == .dnsSettings {
+ navigationController.navigationItem.largeTitleDisplayMode = .always
+ navigationController.navigationBar.prefersLargeTitles = true
+ let doneButton = UIBarButtonItem(
+ systemItem: .done,
+ primaryAction: UIAction(handler: { [weak self] _ in
+ guard let self else { return }
+ didFinish?(self)
+ })
+ )
+ viewController.navigationItem.rightBarButtonItem = doneButton
+ }
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
index 60ab057b82..7b1227cfe0 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
@@ -48,9 +48,13 @@ class CustomDNSViewController: UITableViewController {
value: "DNS settings",
comment: ""
)
-
- navigationItem.rightBarButtonItem = editButtonItem
- navigationItem.rightBarButtonItem?.setAccessibilityIdentifier(.dnsSettingsEditButton)
+ if navigationItem.rightBarButtonItem != nil {
+ navigationItem.leftBarButtonItem = editButtonItem
+ navigationItem.leftBarButtonItem?.setAccessibilityIdentifier(.dnsSettingsEditButton)
+ } else {
+ navigationItem.rightBarButtonItem = editButtonItem
+ navigationItem.rightBarButtonItem?.setAccessibilityIdentifier(.dnsSettingsEditButton)
+ }
interactor.tunnelSettingsDidChange = { [weak self] newSettings in
self?.dataSource?.update(from: newSettings)
@@ -69,6 +73,13 @@ class CustomDNSViewController: UITableViewController {
dataSource?.setEditing(editing, animated: animated)
navigationItem.setHidesBackButton(editing, animated: animated)
+ if navigationItem.rightBarButtonItem != editButtonItem {
+ if #available(iOS 16.0, *) {
+ navigationItem.rightBarButtonItem?.isHidden = editing
+ } else {
+ navigationItem.rightBarButtonItem?.isEnabled = !editing
+ }
+ }
// Disable swipe to dismiss when editing
isModalInPresentation = editing
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index 75f5e974a7..84df606905 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -249,7 +249,12 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
].compactMap { indexPath(for: $0) }
}
- init(tableView: UITableView) {
+ var onlyShowSection: Section?
+
+ init(
+ tableView: UITableView,
+ section: VPNSettingsSection? = nil
+ ) {
self.tableView = tableView
let vpnSettingsCellFactory = VPNSettingsCellFactory(
@@ -258,6 +263,12 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
)
self.vpnSettingsCellFactory = vpnSettingsCellFactory
+ self.onlyShowSection = switch section {
+ case .obfuscation: .wireGuardObfuscation
+ case .quantumResistance: .quantumResistance
+ default: nil
+ }
+
super.init(tableView: tableView) { _, indexPath, itemIdentifier in
vpnSettingsCellFactory.makeCell(for: itemIdentifier, indexPath: indexPath)
}
@@ -506,22 +517,45 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
private func updateSnapshot(animated: Bool = false, completion: (() -> Void)? = nil) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
-
- snapshot.appendSections(Section.allCases)
- snapshot.appendItems([.dnsSettings], toSection: .dnsSettings)
- snapshot.appendItems([.ipOverrides], toSection: .ipOverrides)
+ if let onlyShowSection {
+ snapshot.appendSections([onlyShowSection])
+ } else {
+ snapshot.appendSections(Section.allCases)
+ }
+ if snapshot.sectionIdentifiers.contains(.dnsSettings) {
+ snapshot.appendItems([.dnsSettings], toSection: .dnsSettings)
+ }
+ if snapshot.sectionIdentifiers.contains(.ipOverrides) {
+ snapshot.appendItems([.ipOverrides], toSection: .ipOverrides)
+ }
#if DEBUG
- snapshot.appendItems(
- [.localNetworkSharing(viewModel.localNetworkSharing)],
- toSection: .localNetworkSharing
- )
- snapshot
- .appendItems(
- [.includeAllNetworks(viewModel.includeAllNetworks)],
+ if snapshot.sectionIdentifiers.contains(.localNetworkSharing) {
+ snapshot.appendItems(
+ [.localNetworkSharing(viewModel.localNetworkSharing)],
toSection: .localNetworkSharing
)
+ snapshot
+ .appendItems(
+ [.includeAllNetworks(viewModel.includeAllNetworks)],
+ toSection: .localNetworkSharing
+ )
+ }
#endif
+ if onlyShowSection == .wireGuardObfuscation {
+ snapshot
+ .appendItems(
+ Item.wireGuardObfuscation,
+ toSection: .wireGuardObfuscation
+ )
+ } else if onlyShowSection == .quantumResistance {
+ snapshot
+ .appendItems(
+ Item.quantumResistance,
+ toSection: .quantumResistance
+ )
+ }
+
applySnapshot(snapshot, animated: animated, completion: completion)
}
@@ -599,17 +633,19 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
header.titleLabel.text = title
header.accessibilityCustomActionName = title
header.isExpanded = isExpanded(.wireGuardObfuscation)
- header.didCollapseHandler = { [weak self] header in
- guard let self else { return }
+ if onlyShowSection == nil || onlyShowSection != .wireGuardObfuscation {
+ 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)
+ 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.isExpanded.toggle()
- applySnapshot(snapshot, animated: true)
}
header.infoButtonHandler = { [weak self] in
@@ -629,19 +665,20 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
header.titleLabel.text = title
header.accessibilityCustomActionName = title
header.isExpanded = isExpanded(.quantumResistance)
- header.didCollapseHandler = { [weak self] header in
- guard let self else { return }
+ if onlyShowSection == nil || onlyShowSection != .quantumResistance {
+ header.didCollapseHandler = { [weak self] header in
+ guard let self else { return }
- var snapshot = snapshot()
- if header.isExpanded {
- snapshot.deleteItems(Item.quantumResistance)
- } else {
- snapshot.appendItems(Item.quantumResistance, toSection: .quantumResistance)
+ var snapshot = snapshot()
+ if header.isExpanded {
+ snapshot.deleteItems(Item.quantumResistance)
+ } else {
+ snapshot.appendItems(Item.quantumResistance, toSection: .quantumResistance)
+ }
+ header.isExpanded.toggle()
+ applySnapshot(snapshot, animated: true)
}
- header.isExpanded.toggle()
- applySnapshot(snapshot, animated: true)
}
-
header.infoButtonHandler = { [weak self] in
self.map { $0.delegate?.showInfo(for: .quantumResistance) }
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
index 551813209c..377cd76247 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
@@ -18,17 +18,21 @@ class VPNSettingsViewController: UITableViewController {
private let interactor: VPNSettingsInteractor
private var dataSource: VPNSettingsDataSource?
private let alertPresenter: AlertPresenter
-
+ private let section: VPNSettingsSection?
weak var delegate: VPNSettingsViewControllerDelegate?
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
- init(interactor: VPNSettingsInteractor, alertPresenter: AlertPresenter) {
+ init(
+ interactor: VPNSettingsInteractor,
+ alertPresenter: AlertPresenter,
+ section: VPNSettingsSection?
+ ) {
self.interactor = interactor
self.alertPresenter = alertPresenter
-
+ self.section = section
super.init(style: .grouped)
}
@@ -47,7 +51,11 @@ class VPNSettingsViewController: UITableViewController {
tableView.estimatedSectionHeaderHeight = tableView.estimatedRowHeight
tableView.allowsMultipleSelection = true
- dataSource = VPNSettingsDataSource(tableView: tableView)
+ dataSource = VPNSettingsDataSource(
+ tableView: tableView,
+ section: section
+ )
+
dataSource?.delegate = self
navigationItem.title = NSLocalizedString(
@@ -67,9 +75,10 @@ class VPNSettingsViewController: UITableViewController {
self?.dataSource?.setAvailablePortRanges(cachedRelays.relays.wireguard.portRanges)
}
+ let showsSingleSection = section != nil
tableView.tableHeaderView = UIView(frame: CGRect(
origin: .zero,
- size: CGSize(width: 0, height: UIMetrics.TableView.sectionSpacing)
+ size: CGSize(width: 0, height: showsSingleSection ? 0 : UIMetrics.TableView.sectionSpacing)
))
}
}