summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-01-22 13:20:13 +0100
committerJon Petersson <jon.petersson@mullvad.net>2025-01-22 13:20:13 +0100
commit598727746cd679b6d1aa327f6a5ac0506693bda8 (patch)
treed364dc3246aa498cd90ce1acf55fa404e44e7b71
parent39d978ec1e3c86dfdf3966ac970164361d71561d (diff)
parente96bb471a56aa2ca7b54842dc6be7dd9c7180086 (diff)
downloadmullvadvpn-598727746cd679b6d1aa327f6a5ac0506693bda8.tar.xz
mullvadvpn-598727746cd679b6d1aa327f6a5ac0506693bda8.zip
Merge branch 'daita-button-in-entry-location-view-doesnt-take-user-to-ios-976'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/Classes/AppRoutes.swift7
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift22
-rw-r--r--ios/MullvadVPN/Coordinators/LocationCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/DAITA/DAITASettingsCoordinator.swift124
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift84
6 files changed, 170 insertions, 73 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 5025450b41..2d714acb6d 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -486,6 +486,7 @@
7A2960FD2A964BB700389B82 /* AlertPresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2960FC2A964BB700389B82 /* AlertPresentation.swift */; };
7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307AD82A8CD8DA0017618B /* Duration.swift */; };
7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */; };
+ 7A3215742D3E5A85005DF395 /* DAITASettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */; };
7A33538F2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */; };
7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */; };
7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; };
@@ -1891,6 +1892,7 @@
7A2960FC2A964BB700389B82 /* AlertPresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresentation.swift; sourceTree = "<group>"; };
7A307AD82A8CD8DA0017618B /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; };
7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extensions.swift"; sourceTree = "<group>"; };
+ 7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITASettingsCoordinator.swift; sourceTree = "<group>"; };
7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderManager.swift; sourceTree = "<group>"; };
7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorVPNConnection.swift; sourceTree = "<group>"; };
7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelInfo.swift; sourceTree = "<group>"; };
@@ -4087,6 +4089,7 @@
7A8A19082CE5FFD7000BCB5B /* DAITA */ = {
isa = PBXGroup;
children = (
+ 7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */,
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */,
7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */,
@@ -5987,6 +5990,7 @@
588D7EDC2AF3A55E005DF40A /* ListAccessMethodInteractorProtocol.swift in Sources */,
588D7ED62AF3903F005DF40A /* ListAccessMethodViewController.swift in Sources */,
7A8A190E2CEB77C1000BCB5B /* SettingsRowViewFooter.swift in Sources */,
+ 7A3215742D3E5A85005DF395 /* DAITASettingsCoordinator.swift in Sources */,
7A6000FC2B628DF6001CF0D9 /* ListCellContentConfiguration.swift in Sources */,
582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */,
7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AppRoutes.swift b/ios/MullvadVPN/Classes/AppRoutes.swift
index 53f74f0f3d..9f2d6a92b1 100644
--- a/ios/MullvadVPN/Classes/AppRoutes.swift
+++ b/ios/MullvadVPN/Classes/AppRoutes.swift
@@ -83,6 +83,11 @@ enum AppRoute: AppRouteProtocol {
case settings(SettingsNavigationRoute?)
/**
+ DAITA standalone route (not subsetting).
+ */
+ case daita
+
+ /**
Select location route.
*/
case selectLocation
@@ -130,7 +135,7 @@ enum AppRoute: AppRouteProtocol {
return .selectLocation
case .account:
return .account
- case .settings:
+ case .settings, .daita:
return .settings
case let .alert(id):
return .alert(id)
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index 6c0c61b5a2..e2f2e6d32d 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -119,6 +119,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
case let .settings(subRoute):
presentSettings(route: subRoute, animated: animated, completion: completion)
+ case .daita:
+ presentDAITA(animated: animated, completion: completion)
+
case .selectLocation:
presentSelectLocation(animated: animated, completion: completion)
@@ -597,6 +600,25 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
}
}
+ private func presentDAITA(animated: Bool, completion: @escaping @Sendable (Coordinator) -> Void) {
+ let viewModel = DAITATunnelSettingsViewModel(tunnelManager: tunnelManager)
+ let coordinator = DAITASettingsCoordinator(
+ navigationController: CustomNavigationController(),
+ route: .daita,
+ viewModel: viewModel
+ )
+
+ coordinator.didFinish = { [weak self] _ in
+ self?.router.dismiss(.daita, animated: true)
+ }
+
+ coordinator.start(animated: animated)
+
+ presentChild(coordinator, animated: animated) {
+ completion(coordinator)
+ }
+ }
+
private func addTunnelObserver() {
let tunnelObserver =
TunnelBlockObserver(
diff --git a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
index 8703c46e5d..e62defb9b4 100644
--- a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
@@ -230,7 +230,7 @@ extension LocationCoordinator: @preconcurrency LocationViewControllerWrapperDele
}
func navigateToDaitaSettings() {
- applicationRouter?.present(.settings(nil))
+ applicationRouter?.present(.daita)
}
func didSelectExitRelays(_ relays: UserSelectedRelays) {
diff --git a/ios/MullvadVPN/Coordinators/Settings/DAITA/DAITASettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/DAITA/DAITASettingsCoordinator.swift
new file mode 100644
index 0000000000..9ff06070ac
--- /dev/null
+++ b/ios/MullvadVPN/Coordinators/Settings/DAITA/DAITASettingsCoordinator.swift
@@ -0,0 +1,124 @@
+//
+// DAITASettingsCoordinator.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-01-20.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Routing
+import SwiftUI
+
+class DAITASettingsCoordinator: Coordinator, SettingsChildCoordinator, Presentable, Presenting {
+ private let navigationController: UINavigationController
+ private let viewModel: DAITATunnelSettingsViewModel
+ private var alertPresenter: AlertPresenter?
+ private let route: AppRoute
+
+ var presentedViewController: UIViewController {
+ navigationController
+ }
+
+ var didFinish: ((DAITASettingsCoordinator) -> Void)?
+
+ init(
+ navigationController: UINavigationController,
+ route: AppRoute,
+ viewModel: DAITATunnelSettingsViewModel
+ ) {
+ self.navigationController = navigationController
+ self.route = route
+ self.viewModel = viewModel
+
+ super.init()
+
+ alertPresenter = AlertPresenter(context: self)
+ }
+
+ func start(animated: Bool) {
+ let view = SettingsDAITAView(tunnelViewModel: self.viewModel)
+
+ viewModel.didFailDAITAValidation = { [weak self] result in
+ guard let self else { return }
+
+ showPrompt(
+ for: result.item,
+ onSave: {
+ self.viewModel.value = result.setting
+ },
+ onDiscard: {}
+ )
+ }
+
+ let host = UIHostingController(rootView: view)
+ host.title = NSLocalizedString(
+ "NAVIGATION_TITLE_DAITA",
+ tableName: "Settings",
+ value: "DAITA",
+ comment: ""
+ )
+ host.view.setAccessibilityIdentifier(.daitaView)
+ customiseNavigation(on: host)
+
+ navigationController.pushViewController(host, animated: animated)
+ }
+
+ private func customiseNavigation(on viewController: UIViewController) {
+ if route == .daita {
+ 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
+ }
+ }
+
+ private func showPrompt(
+ for item: DAITASettingsPromptItem,
+ onSave: @escaping () -> Void,
+ onDiscard: @escaping () -> Void
+ ) {
+ let presentation = AlertPresentation(
+ id: "settings-daita-prompt",
+ accessibilityIdentifier: .daitaPromptAlert,
+ icon: .warning,
+ message: NSLocalizedString(
+ "SETTINGS_DAITA_ENABLE_TEXT",
+ tableName: "DAITA",
+ value: item.description,
+ comment: ""
+ ),
+ buttons: [
+ AlertAction(
+ title: String(format: NSLocalizedString(
+ "SETTINGS_DAITA_ENABLE_OK_ACTION",
+ tableName: "DAITA",
+ value: "Enable \"%@\"",
+ comment: ""
+ ), item.title),
+ style: .default,
+ accessibilityId: .daitaConfirmAlertEnableButton,
+ handler: { onSave() }
+ ),
+ AlertAction(
+ title: NSLocalizedString(
+ "SETTINGS_DAITA_ENABLE_CANCEL_ACTION",
+ tableName: "DAITA",
+ value: "Cancel",
+ comment: ""
+ ),
+ style: .default,
+ handler: { onDiscard() }
+ ),
+ ]
+ )
+
+ alertPresenter?.showAlert(presentation: presentation, animated: true)
+ }
+}
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
index f25a652d34..778900dec3 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
@@ -58,21 +58,21 @@ struct SettingsViewControllerFactory {
// Handled separately and presented as a modal.
.failed
case .vpnSettings:
- makeVPNSettingsViewController()
+ makeVPNSettingsViewCoordinator()
case .problemReport:
makeProblemReportViewController()
case .apiAccess:
- makeAPIAccessViewController()
+ makeAPIAccessCoordinator()
case .changelog:
- makeChangelogViewController()
+ makeChangelogCoordinator()
case .multihop:
makeMultihopViewController()
case .daita:
- makeDAITAViewController()
+ makeDAITASettingsCoordinator()
}
}
- private func makeVPNSettingsViewController() -> MakeChildResult {
+ private func makeVPNSettingsViewCoordinator() -> MakeChildResult {
return .childCoordinator(VPNSettingsCoordinator(
navigationController: navigationController,
interactorFactory: interactorFactory,
@@ -87,7 +87,7 @@ struct SettingsViewControllerFactory {
))
}
- private func makeAPIAccessViewController() -> MakeChildResult {
+ private func makeAPIAccessCoordinator() -> MakeChildResult {
return .childCoordinator(ListAccessMethodCoordinator(
navigationController: navigationController,
accessMethodRepository: accessMethodRepository,
@@ -95,7 +95,7 @@ struct SettingsViewControllerFactory {
))
}
- private func makeChangelogViewController() -> MakeChildResult {
+ private func makeChangelogCoordinator() -> MakeChildResult {
return .childCoordinator(
ChangeLogCoordinator(
navigationController: navigationController,
@@ -120,72 +120,14 @@ struct SettingsViewControllerFactory {
return .viewController(host)
}
- private func makeDAITAViewController() -> MakeChildResult {
+ private func makeDAITASettingsCoordinator() -> MakeChildResult {
let viewModel = DAITATunnelSettingsViewModel(tunnelManager: interactorFactory.tunnelManager)
- let view = SettingsDAITAView(tunnelViewModel: viewModel)
-
- viewModel.didFailDAITAValidation = { result in
- showPrompt(
- for: result.item,
- onSave: {
- viewModel.value = result.setting
- },
- onDiscard: {}
- )
- }
-
- let host = UIHostingController(rootView: view)
- host.title = NSLocalizedString(
- "NAVIGATION_TITLE_DAITA",
- tableName: "Settings",
- value: "DAITA",
- comment: ""
- )
- host.view.setAccessibilityIdentifier(.daitaView)
-
- return .viewController(host)
- }
-
- private func showPrompt(
- for item: DAITASettingsPromptItem,
- onSave: @escaping () -> Void,
- onDiscard: @escaping () -> Void
- ) {
- let presentation = AlertPresentation(
- id: "settings-daita-prompt",
- accessibilityIdentifier: .daitaPromptAlert,
- icon: .warning,
- message: NSLocalizedString(
- "SETTINGS_DAITA_ENABLE_TEXT",
- tableName: "DAITA",
- value: item.description,
- comment: ""
- ),
- buttons: [
- AlertAction(
- title: String(format: NSLocalizedString(
- "SETTINGS_DAITA_ENABLE_OK_ACTION",
- tableName: "DAITA",
- value: "Enable \"%@\"",
- comment: ""
- ), item.title),
- style: .default,
- accessibilityId: .daitaConfirmAlertEnableButton,
- handler: { onSave() }
- ),
- AlertAction(
- title: NSLocalizedString(
- "SETTINGS_DAITA_ENABLE_CANCEL_ACTION",
- tableName: "DAITA",
- value: "Cancel",
- comment: ""
- ),
- style: .default,
- handler: { onDiscard() }
- ),
- ]
+ let coordinator = DAITASettingsCoordinator(
+ navigationController: navigationController,
+ route: .settings(.daita),
+ viewModel: viewModel
)
- alertPresenter.showAlert(presentation: presentation, animated: true)
+ return .childCoordinator(coordinator)
}
}