Skip to content

Commit 8f29add

Browse files
committedJan 30, 2025
Fix router getting stuck under certain circumstances
1 parent fafec04 commit 8f29add

17 files changed

+210
-509
lines changed
 

‎ios/MullvadVPN.xcodeproj/project.pbxproj

+12
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,9 @@
627627
7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */; };
628628
7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */; };
629629
7AB4CCBB2B691BBB006037F5 /* IPOverrideInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */; };
630+
7AB931282D48FB8B005FCEBA /* DAITASettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB931272D48FB16005FCEBA /* DAITASettingsViewController.swift */; };
631+
7AB9312A2D4A16BE005FCEBA /* MultihopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB931292D4A16B8005FCEBA /* MultihopViewController.swift */; };
632+
7AB9312C2D4A1732005FCEBA /* ChangeLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB9312B2D4A172C005FCEBA /* ChangeLogViewController.swift */; };
630633
7ABCA5B32A9349F20044A708 /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; };
631634
7ABCA5B42A9349F20044A708 /* Routing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
632635
7ABCA5B72A9353C60044A708 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F72983D36800BE19F7 /* Coordinator.swift */; };
@@ -2024,6 +2027,9 @@
20242027
7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewControllerWrapper.swift; sourceTree = "<group>"; };
20252028
7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideTests.swift; sourceTree = "<group>"; };
20262029
7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideInteractor.swift; sourceTree = "<group>"; };
2030+
7AB931272D48FB16005FCEBA /* DAITASettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITASettingsViewController.swift; sourceTree = "<group>"; };
2031+
7AB931292D4A16B8005FCEBA /* MultihopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopViewController.swift; sourceTree = "<group>"; };
2032+
7AB9312B2D4A172C005FCEBA /* ChangeLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogViewController.swift; sourceTree = "<group>"; };
20272033
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = "<group>"; };
20282034
7ABFB09D2BA316220074A49E /* RelayConstraintsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraintsTests.swift; sourceTree = "<group>"; };
20292035
7AC8A3AD2ABC6FBB00DC4939 /* SettingsHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = "<group>"; };
@@ -4082,6 +4088,7 @@
40824088
isa = PBXGroup;
40834089
children = (
40844090
7A8A19152CEF2696000BCB5B /* MultihopTunnelSettingsViewModel.swift */,
4091+
7AB931292D4A16B8005FCEBA /* MultihopViewController.swift */,
40854092
7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */,
40864093
);
40874094
path = Multihop;
@@ -4104,6 +4111,7 @@
41044111
children = (
41054112
7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */,
41064113
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
4114+
7AB931272D48FB16005FCEBA /* DAITASettingsViewController.swift */,
41074115
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */,
41084116
7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */,
41094117
);
@@ -4592,6 +4600,7 @@
45924600
F048BFA12D31842B00251CB9 /* ChangeLogModel.swift */,
45934601
F09C97222D3122E400ADE747 /* ChangeLogReader.swift */,
45944602
F09C97202D311D7A00ADE747 /* ChangeLogView.swift */,
4603+
7AB9312B2D4A172C005FCEBA /* ChangeLogViewController.swift */,
45954604
F0EF50D42A949F8E0031E8DF /* ChangeLogViewModel.swift */,
45964605
);
45974606
path = ChangeLog;
@@ -5916,6 +5925,7 @@
59165925
58E511E628DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
59175926
58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */,
59185927
7A9CCCC32A96302800DD6A34 /* ApplicationCoordinator.swift in Sources */,
5928+
7AB9312A2D4A16BE005FCEBA /* MultihopViewController.swift in Sources */,
59195929
5864AF0729C78843005B0CD9 /* SettingsCellFactory.swift in Sources */,
59205930
F0ADC3742CD3C47400A1AD97 /* ChipFlowLayout.swift in Sources */,
59215931
587B75412668FD7800DEF7E9 /* AccountExpirySystemNotificationProvider.swift in Sources */,
@@ -6076,6 +6086,7 @@
60766086
7A0EAE9A2D01B41500D3EB8B /* MainButtonStyle.swift in Sources */,
60776087
58CEB3022AFD365600E6E088 /* SwitchCellContentConfiguration.swift in Sources */,
60786088
7AA130A12D01B1E200640DF9 /* SplitMainButton.swift in Sources */,
6089+
7AB931282D48FB8B005FCEBA /* DAITASettingsViewController.swift in Sources */,
60796090
7AA1309F2D007B2500640DF9 /* VisualEffectView.swift in Sources */,
60806091
7A9CCCB52A96302800DD6A34 /* AddCreditSucceededCoordinator.swift in Sources */,
60816092
7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */,
@@ -6262,6 +6273,7 @@
62626273
A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */,
62636274
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
62646275
7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */,
6276+
7AB9312C2D4A1732005FCEBA /* ChangeLogViewController.swift in Sources */,
62656277
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */,
62666278
58FF9FF42B07C61B00E4C97D /* AccessMethodValidationError.swift in Sources */,
62676279
5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */,

‎ios/MullvadVPN/Classes/AppRoutes.swift

-33
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,6 @@ enum AppRouteGroup: AppRouteGroupProtocol {
4242
Alert group. Alert id should match the id of the alert being contained.
4343
*/
4444
case alert(_ alertId: String)
45-
46-
var isModal: Bool {
47-
switch self {
48-
case .primary:
49-
return false
50-
51-
case .selectLocation, .account, .settings, .changelog, .alert:
52-
return true
53-
}
54-
}
55-
56-
var modalLevel: Int {
57-
switch self {
58-
case .primary:
59-
return 0
60-
case .account, .selectLocation, .changelog:
61-
return 1
62-
case .settings:
63-
return 2
64-
case .alert:
65-
// Alerts should always be topmost.
66-
return .max
67-
}
68-
}
6945
}
7046

7147
/**
@@ -108,15 +84,6 @@ enum AppRoute: AppRouteProtocol {
10884
*/
10985
case tos, login, main, revoked, outOfTime, welcome
11086

111-
var isExclusive: Bool {
112-
switch self {
113-
case .account, .settings, .alert:
114-
return true
115-
default:
116-
return false
117-
}
118-
}
119-
12087
var supportsSubNavigation: Bool {
12188
if case .settings = self {
12289
return true

‎ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift

+29-39
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import UIKit
1717
Application coordinator managing split view and two navigation contexts.
1818
*/
1919
final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency RootContainerViewControllerDelegate,
20-
UISplitViewControllerDelegate, @preconcurrency ApplicationRouterDelegate,
20+
UISplitViewControllerDelegate, ApplicationRouterDelegate,
2121
@preconcurrency NotificationManagerDelegate {
2222
typealias RouteType = AppRoute
2323

@@ -234,7 +234,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
234234
func applicationRouter(
235235
_ router: ApplicationRouter<RouteType>,
236236
handleSubNavigationWithContext context: RouteSubnavigationContext<RouteType>,
237-
completion: @escaping @Sendable @MainActor () -> Void
237+
completion: @escaping @Sendable () -> Void
238238
) {
239239
switch context.route {
240240
case let .settings(subRoute):
@@ -309,10 +309,10 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
309309

310310
private func didDismissAccount(_ reason: AccountDismissReason) {
311311
if reason == .userLoggedOut {
312-
router.dismissAll(.primary, animated: false)
312+
router.dismiss(group: .primary, animated: false)
313313
continueFlow(animated: false)
314314
}
315-
router.dismiss(.account, animated: true)
315+
router.dismiss(route: .account, animated: true)
316316
}
317317

318318
private func presentTOS(animated: Bool, completion: @escaping (Coordinator) -> Void) {
@@ -337,14 +337,13 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
337337
)
338338

339339
coordinator.didFinish = { [weak self] _ in
340-
self?.router.dismiss(.changelog, animated: animated)
340+
self?.router.dismiss(route: .changelog, animated: animated)
341341
}
342342

343343
coordinator.start(animated: false)
344+
presentChild(coordinator, animated: animated)
344345

345-
presentChild(coordinator, animated: animated) {
346-
completion(coordinator)
347-
}
346+
completion(coordinator)
348347
}
349348

350349
private func presentMain(animated: Bool, completion: @escaping (Coordinator) -> Void) {
@@ -389,7 +388,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
389388

390389
Task { @MainActor in
391390
if shouldDismissOutOfTime() {
392-
router.dismiss(.outOfTime, animated: true)
391+
router.dismiss(route: .outOfTime, animated: true)
393392
continueFlow(animated: true)
394393
}
395394
}
@@ -411,12 +410,12 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
411410
coordinator.didFinish = { [weak self] in
412411
guard let self else { return }
413412
appPreferences.isShownOnboarding = true
414-
router.dismiss(.welcome, animated: false)
413+
router.dismiss(route: .welcome, animated: false)
415414
continueFlow(animated: false)
416415
}
417416
coordinator.didLogout = { [weak self] preferredAccountNumber in
418417
guard let self else { return }
419-
router.dismissAll(.primary, animated: true)
418+
router.dismiss(group: .primary, animated: true)
420419
DispatchQueue.main.async {
421420
self.continueFlow(animated: true)
422421
}
@@ -435,11 +434,11 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
435434

436435
private func presentSelectLocation(animated: Bool, completion: @escaping (Coordinator) -> Void) {
437436
let coordinator = makeLocationCoordinator(forModalPresentation: true)
437+
438438
coordinator.start()
439+
presentChild(coordinator, animated: animated)
439440

440-
presentChild(coordinator, animated: animated) {
441-
completion(coordinator)
442-
}
441+
completion(coordinator)
443442
}
444443

445444
private func presentLogin(animated: Bool, completion: @escaping (Coordinator) -> Void) {
@@ -477,14 +476,13 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
477476
let coordinator = AlertCoordinator(presentation: metadata.presentation)
478477

479478
coordinator.didFinish = { [weak self] in
480-
self?.router.dismiss(context.route)
479+
self?.router.dismiss(route: context.route)
481480
}
482481

483482
coordinator.start()
483+
metadata.context.presentChild(coordinator, animated: animated)
484484

485-
metadata.context.presentChild(coordinator, animated: animated) {
486-
completion(coordinator)
487-
}
485+
completion(coordinator)
488486
}
489487

490488
private func makeTunnelCoordinator() -> TunnelCoordinator {
@@ -515,7 +513,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
515513

516514
locationCoordinator.didFinish = { [weak self] _ in
517515
if isModalPresentation {
518-
self?.router.dismiss(.selectLocation, animated: true)
516+
self?.router.dismiss(route: .selectLocation, animated: true)
519517
}
520518
}
521519

@@ -541,14 +539,11 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
541539

542540
coordinator.start(animated: animated)
543541

544-
presentChild(
545-
coordinator,
546-
animated: animated
547-
) { [weak self] in
548-
completion(coordinator)
549-
542+
presentChild(coordinator, animated: animated) { [weak self] in
550543
self?.onShowAccount?()
551544
}
545+
546+
completion(coordinator)
552547
}
553548

554549
private func presentSettings(
@@ -579,7 +574,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
579574

580575
coordinator.didFinish = { [weak self] _ in
581576
Task { @MainActor in
582-
self?.router.dismissAll(.settings, animated: true)
577+
self?.router.dismiss(group: .settings, animated: true)
583578
}
584579
}
585580

@@ -590,13 +585,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
590585
}
591586

592587
coordinator.start(initialRoute: route)
588+
presentChild(coordinator, animated: animated)
593589

594-
presentChild(
595-
coordinator,
596-
animated: animated
597-
) {
598-
completion(coordinator)
599-
}
590+
completion(coordinator)
600591
}
601592

602593
private func presentDAITA(animated: Bool, completion: @escaping @Sendable (Coordinator) -> Void) {
@@ -608,14 +599,13 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
608599
)
609600

610601
coordinator.didFinish = { [weak self] _ in
611-
self?.router.dismiss(.daita, animated: true)
602+
self?.router.dismiss(route: .daita, animated: true)
612603
}
613604

614605
coordinator.start(animated: animated)
606+
presentChild(coordinator, animated: animated)
615607

616-
presentChild(coordinator, animated: animated) {
617-
completion(coordinator)
618-
}
608+
completion(coordinator)
619609
}
620610

621611
private func addTunnelObserver() {
@@ -654,7 +644,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
654644
case (true, false):
655645
updateOutOfTimeTimer(accountData: accountData)
656646
continueFlow(animated: true)
657-
router.dismiss(.outOfTime, animated: true)
647+
router.dismiss(route: .outOfTime, animated: true)
658648
// account was expired
659649
case (false, true):
660650
router.present(.outOfTime, animated: true)
@@ -757,7 +747,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
757747
}
758748

759749
func splitViewControllerDidExpand(_ svc: UISplitViewController) {
760-
router.dismissAll(.selectLocation, animated: false)
750+
router.dismiss(group: .selectLocation, animated: false)
761751
}
762752

763753
// MARK: - RootContainerViewControllerDelegate
@@ -836,4 +826,4 @@ extension DeviceState {
836826
var splitViewMode: UISplitViewController.DisplayMode {
837827
isLoggedIn ? UISplitViewController.DisplayMode.oneBesideSecondary : .secondaryOnly
838828
}
839-
}
829+
} // swiftlint:disable:this file_length

‎ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ final class ChangeLogCoordinator: Coordinator, Presentable, SettingsChildCoordin
3232
}
3333

3434
func start(animated: Bool) {
35-
let changeLogViewController = UIHostingController(rootView: ChangeLogView(viewModel: viewModel))
36-
changeLogViewController.view.setAccessibilityIdentifier(.changeLogAlert)
35+
let changeLogViewController = ChangeLogViewController(rootView: ChangeLogView(viewModel: viewModel))
3736
changeLogViewController.navigationItem.title = NSLocalizedString(
3837
"whats_new_title",
3938
tableName: "Changelog",

‎ios/MullvadVPN/Coordinators/Settings/DAITA/DAITASettingsCoordinator.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,13 @@ class DAITASettingsCoordinator: Coordinator, SettingsChildCoordinator, Presentab
5050
)
5151
}
5252

53-
let host = UIHostingController(rootView: view)
53+
let host = DAITASettingsViewController(rootView: view)
5454
host.title = NSLocalizedString(
5555
"NAVIGATION_TITLE_DAITA",
5656
tableName: "Settings",
5757
value: "DAITA",
5858
comment: ""
5959
)
60-
host.view.setAccessibilityIdentifier(.daitaView)
6160
customiseNavigation(on: host)
6261

6362
navigationController.pushViewController(host, animated: animated)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// DAITAViewController.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2025-01-28.
6+
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
class DAITASettingsViewController: UIHostingController<SettingsDAITAView<DAITATunnelSettingsViewModel>> {
12+
override init(rootView: SettingsDAITAView<DAITATunnelSettingsViewModel>) {
13+
super.init(rootView: rootView)
14+
view.setAccessibilityIdentifier(.daitaView)
15+
}
16+
17+
required init?(coder: NSCoder) {
18+
fatalError("init(coder:) has not been implemented")
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//
2+
// DAITAViewController.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2025-01-28.
6+
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
7+
//
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// MultihopViewController.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2025-01-29.
6+
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
class MultihopViewController: UIHostingController<SettingsMultihopView<MultihopTunnelSettingsViewModel>> {
12+
override init(rootView: SettingsMultihopView<MultihopTunnelSettingsViewModel>) {
13+
super.init(rootView: rootView)
14+
view.setAccessibilityIdentifier(.multihopView)
15+
}
16+
17+
required init?(coder: NSCoder) {
18+
fatalError("init(coder:) has not been implemented")
19+
}
20+
}

0 commit comments

Comments
 (0)
Failed to load comments.