summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2021-05-05 11:28:54 +0200
committerAndrej Mihajlov <and@mullvad.net>2021-05-05 11:28:54 +0200
commitb1bbae1c858990087995b59af29af63cb1536fd0 (patch)
tree770070fa9594cca4191460c05169af929ce4a9c6
parentba947da093dc21f3e0dbb35f47ee27bbfdf7c530 (diff)
parentcdea0cf3498c511bb3e9883801d8c09494e99ea1 (diff)
downloadmullvadvpn-b1bbae1c858990087995b59af29af63cb1536fd0.tar.xz
mullvadvpn-b1bbae1c858990087995b59af29af63cb1536fd0.zip
Merge branch 'wireup-controller-delegates'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj3
-rw-r--r--ios/MullvadVPN/AppDelegate.swift391
-rw-r--r--ios/MullvadVPN/ConnectViewController.swift182
-rw-r--r--ios/MullvadVPN/ConsentViewController.swift6
-rw-r--r--ios/MullvadVPN/RelayCache.swift2
-rw-r--r--ios/MullvadVPN/RootContainerViewController.swift8
6 files changed, 369 insertions, 223 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 63276bead6..55425ced99 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -153,8 +153,8 @@
58B0A2AA238EE6A900BC001D /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; };
58B0A2AC238EE6D500BC001D /* IPAddress+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840250022B1124600E4CFEC /* IPAddress+Codable.swift */; };
58B0A2AD238EE6EC00BC001D /* MullvadEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */; };
- 58B67B482602079E008EF58E /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; };
58B43C1925F77DB60002C8C3 /* ConnectMainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* ConnectMainContentView.swift */; };
+ 58B67B482602079E008EF58E /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; };
58B8743222B25A7600015324 /* WireguardAssociatedAddresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */; };
58B8743B22B788D200015324 /* PacketTunnelSettingsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B8743722B25EAB00015324 /* PacketTunnelSettingsGenerator.swift */; };
58B9814E24FEA70D00C0D59E /* WireguardKeysViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58B9814D24FEA70D00C0D59E /* WireguardKeysViewController.xib */; };
@@ -879,7 +879,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 58D9AF6B2501111800B6FAB5 /* ConnectViewController.xib in Resources */,
58F3C0A624A50157003E76BE /* relays.json in Resources */,
58CE5E6E224146210008646E /* LaunchScreen.storyboard in Resources */,
58CE5E6B224146210008646E /* Assets.xcassets in Resources */,
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index f36a4c8560..3276d5c433 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -14,20 +14,39 @@ import Logging
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
- var rootContainer: RootContainerViewController?
+
+ private var logger: Logger?
#if targetEnvironment(simulator)
- let simulatorTunnelProvider = SimulatorTunnelProviderHost()
+ private let simulatorTunnelProvider = SimulatorTunnelProviderHost()
#endif
#if DEBUG
private let packetTunnelLogForwarder = LogStreamer<UTF8>(fileURLs: [ApplicationConfiguration.packetTunnelLogFileURL!])
#endif
+ private var rootContainer: RootContainerViewController?
+ private var selectLocationViewController: SelectLocationViewController?
+ private var connectController: ConnectViewController?
+
+ private var cachedRelays: CachedRelays? {
+ didSet {
+ if let cachedRelays = cachedRelays {
+ self.selectLocationViewController?.setCachedRelays(cachedRelays)
+ }
+ }
+ }
+ private var relayConstraints: RelayConstraints?
+ private let alertPresenter = AlertPresenter()
+
+ // MARK: - Application lifecycle
+
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Setup logging
initLoggingSystem(bundleIdentifier: Bundle.main.bundleIdentifier!)
+ self.logger = Logger(label: "AppDelegate")
+
#if DEBUG
let stdoutStream = TextFileOutputStream.standardOutputStream()
packetTunnelLogForwarder.start { (str) in
@@ -49,43 +68,54 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.window?.rootViewController = launchController
// Update relays
+ RelayCache.shared.addObserver(self)
RelayCache.shared.updateRelays()
+ // Load initial relays
+ RelayCache.shared.read { (result) in
+ DispatchQueue.main.async {
+ switch result {
+ case .success(let cachedRelays):
+ self.cachedRelays = cachedRelays
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to fetch initial relays")
+ }
+ }
+ }
+
// Load tunnels
- let accountToken = Account.shared.token
- TunnelManager.shared.loadTunnel(accountToken: accountToken) { (result) in
+ TunnelManager.shared.loadTunnel(accountToken: Account.shared.token) { (result) in
DispatchQueue.main.async {
if case .failure(let error) = result {
fatalError(error.displayChain(message: "Failed to load the tunnel for account"))
}
- let rootViewController = RootContainerViewController()
- rootViewController.delegate = self
+ TunnelManager.shared.getRelayConstraints { (result) in
+ DispatchQueue.main.async {
+ switch result {
+ case .success(let relayConstraints):
+ self.relayConstraints = relayConstraints
- let showMainController = { (_ animated: Bool) in
- self.showConnectController(in: rootViewController, animated: animated) {
- self.didPresentTheMainController()
- }
- }
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to load relay constraints")
+ }
- if Account.shared.isAgreedToTermsOfService {
- showMainController(false)
- } else {
- self.showTermsOfService(in: rootViewController) {
- Account.shared.agreeToTermsOfService()
+ self.rootContainer = RootContainerViewController()
+ self.rootContainer?.delegate = self
+ self.window?.rootViewController = self.rootContainer
- showMainController(true)
+ self.setupPhoneUI()
}
}
-
- self.window?.rootViewController = rootViewController
- self.rootContainer = rootViewController
}
}
// Show the window
self.window?.makeKeyAndVisible()
+ startPaymentQueueHandling()
+
return true
}
@@ -93,79 +123,207 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
TunnelManager.shared.refreshTunnelState(completionHandler: nil)
}
- private func didPresentTheMainController() {
- let paymentManager = AppStorePaymentManager.shared
- paymentManager.delegate = self
+ // MARK: - Private
- paymentManager.startPaymentQueueMonitoring()
- Account.shared.startPaymentMonitoring(with: paymentManager)
+ private func setupPhoneUI() {
+ let showNextController = { [weak self] (_ animated: Bool) in
+ guard let self = self else { return }
+
+ let loginViewController = self.makeLoginController()
+ var viewControllers: [UIViewController] = [loginViewController]
+
+ if Account.shared.isLoggedIn {
+ let connectController = self.makeConnectViewController()
+ viewControllers.append(connectController)
+ self.connectController = connectController
+ }
+
+ self.rootContainer?.setViewControllers(viewControllers, animated: animated) {
+ self.showAccountSettingsControllerIfAccountExpired()
+ }
+ }
+
+ if Account.shared.isAgreedToTermsOfService {
+ showNextController(false)
+ } else {
+ let consentViewController = self.makeConsentController { (consentController) in
+ showNextController(true)
+ }
+
+ self.rootContainer?.setViewControllers([consentViewController], animated: false)
+ }
}
- private func showTermsOfService(in rootViewController: RootContainerViewController, completionHandler: @escaping () -> Void) {
- let consentViewController = ConsentViewController()
- consentViewController.completionHandler = completionHandler
+ private func makeConnectViewController() -> ConnectViewController {
+ let connectController = ConnectViewController()
+ connectController.delegate = self
- rootViewController.setViewControllers([consentViewController], animated: false)
+ return connectController
}
- private func showConnectController(
- in rootViewController: RootContainerViewController,
- animated: Bool,
- completionHandler: @escaping () -> Void)
- {
- let loginViewController = LoginViewController()
- loginViewController.delegate = self
+ private func makeSelectLocationController() -> SelectLocationViewController {
+ let selectLocationController = SelectLocationViewController()
+ selectLocationController.delegate = self
- var viewControllers: [UIViewController] = [loginViewController]
+ if let cachedRelays = cachedRelays {
+ selectLocationController.setCachedRelays(cachedRelays)
+ }
- if Account.shared.isLoggedIn {
- viewControllers.append(ConnectViewController())
+ if let relayLocation = relayConstraints?.location.value {
+ selectLocationController.setSelectedRelayLocation(relayLocation, animated: false, scrollPosition: .middle)
}
- rootViewController.setViewControllers(viewControllers, animated: animated, completion: completionHandler)
+ return selectLocationController
}
-}
+ private func makeConsentController(completion: @escaping (UIViewController) -> Void) -> ConsentViewController {
+ let consentViewController = ConsentViewController()
-extension AppDelegate: RootContainerViewControllerDelegate {
+ consentViewController.completionHandler = { (consentViewController) in
+ Account.shared.agreeToTermsOfService()
+ completion(consentViewController)
+ }
- func rootContainerViewControllerShouldShowSettings(_ controller: RootContainerViewController, navigateTo route: SettingsNavigationRoute?, animated: Bool) {
- let settingsController = SettingsViewController(style: .grouped)
- settingsController.settingsDelegate = self
+ return consentViewController
+ }
- let navController = SettingsNavigationController(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
- navController.pushViewController(settingsController, animated: false)
+ private func makeLoginController() -> LoginViewController {
+ let controller = LoginViewController()
+ controller.delegate = self
+
+ return controller
+ }
+
+ private func makeSettingsNavigationController(route: SettingsNavigationRoute?) -> SettingsNavigationController {
+ let navController = SettingsNavigationController()
+ navController.settingsDelegate = self
+ navController.presentationController?.delegate = navController
if let route = route {
- settingsController.navigate(to: route)
+ navController.navigate(to: route, animated: false)
}
- controller.present(navController, animated: animated)
+ return navController
+ }
+
+ private func showAccountSettingsControllerIfAccountExpired() {
+ guard let accountExpiry = Account.shared.expiry, AccountExpiry(date: accountExpiry).isExpired else { return }
+
+ rootContainer?.showSettings(navigateTo: .account, animated: true)
+ }
+
+ private func startPaymentQueueHandling() {
+ let paymentManager = AppStorePaymentManager.shared
+ paymentManager.delegate = self
+ paymentManager.startPaymentQueueMonitoring()
+
+ Account.shared.startPaymentMonitoring(with: paymentManager)
+ }
+
+}
+
+// MARK: - RootContainerViewControllerDelegate
+
+extension AppDelegate: RootContainerViewControllerDelegate {
+ func rootContainerViewControllerShouldShowSettings(_ controller: RootContainerViewController, navigateTo route: SettingsNavigationRoute?, animated: Bool) {
+ let navController = makeSettingsNavigationController(route: route)
+
+ // On iPad the login controller can be presented modally above the root container.
+ // in that case we have to use the presented controller to present the next modal.
+ if let presentedController = controller.presentedViewController {
+ presentedController.present(navController, animated: true)
+ } else {
+ controller.present(navController, animated: true)
+ }
}
func rootContainerViewSupportedInterfaceOrientations(_ controller: RootContainerViewController) -> UIInterfaceOrientationMask {
- switch self.window?.traitCollection.userInterfaceIdiom {
+ switch UIDevice.current.userInterfaceIdiom {
case .pad:
return [.landscape, .portrait]
case .phone:
return [.portrait]
default:
- fatalError("Not supported")
+ return controller.supportedInterfaceOrientations
}
}
}
+// MARK: - LoginViewControllerDelegate
+
extension AppDelegate: LoginViewControllerDelegate {
+ func loginViewController(_ controller: LoginViewController, loginWithAccountToken accountToken: String, completion: @escaping (Result<AccountResponse, Account.Error>) -> Void) {
+ self.rootContainer?.setEnableSettingsButton(false)
+
+ Account.shared.login(with: accountToken) { (result) in
+ switch result {
+ case .success:
+ self.logger?.debug("Logged in with existing token")
+ // RootContainer's settings button will be re-enabled in `loginViewControllerDidLogin`
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to log in with existing account")
+ self.rootContainer?.setEnableSettingsButton(true)
+ }
+
+ completion(result)
+ }
+ }
+
+ func loginViewControllerLoginWithNewAccount(_ controller: LoginViewController, completion: @escaping (Result<AccountResponse, Account.Error>) -> Void) {
+ self.rootContainer?.setEnableSettingsButton(false)
+
+ Account.shared.loginWithNewAccount { (result) in
+ switch result {
+ case .success:
+ self.logger?.debug("Logged in with new account token")
+ // RootContainer's settings button will be re-enabled in `loginViewControllerDidLogin`
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to log in with new account")
+ self.rootContainer?.setEnableSettingsButton(true)
+ }
+
+ completion(result)
+ }
+ }
+
func loginViewControllerDidLogin(_ controller: LoginViewController) {
- rootContainer?.pushViewController(ConnectViewController(), animated: true)
+ self.window?.isUserInteractionEnabled = false
+
+ TunnelManager.shared.getRelayConstraints { [weak self] (result) in
+ guard let self = self else { return }
+
+ DispatchQueue.main.async {
+ switch result {
+ case .success(let relayConstraints):
+ self.relayConstraints = relayConstraints
+ self.selectLocationViewController?.setSelectedRelayLocation(relayConstraints.location.value, animated: false, scrollPosition: .middle)
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to load relay constraints after log in")
+ }
+
+ let connectController = self.makeConnectViewController()
+ self.rootContainer?.pushViewController(connectController, animated: true) {
+ self.showAccountSettingsControllerIfAccountExpired()
+ }
+ self.connectController = connectController
+
+ self.window?.isUserInteractionEnabled = true
+ self.rootContainer?.setEnableSettingsButton(true)
+ }
+ }
}
}
-extension AppDelegate: SettingsViewControllerDelegate {
+// MARK: - SettingsNavigationControllerDelegate
- func settingsViewController(_ controller: SettingsViewController, didFinishWithReason reason: SettingsDismissReason) {
+extension AppDelegate: SettingsNavigationControllerDelegate {
+
+ func settingsNavigationController(_ controller: SettingsNavigationController, didFinishWithReason reason: SettingsDismissReason) {
if case .userLoggedOut = reason {
rootContainer?.popToRootViewController(animated: false)
@@ -178,6 +336,130 @@ extension AppDelegate: SettingsViewControllerDelegate {
}
+// MARK: - ConnectViewControllerDelegate
+
+extension AppDelegate: ConnectViewControllerDelegate {
+
+ func connectViewControllerShouldShowSelectLocationPicker(_ controller: ConnectViewController) {
+ let contentController = makeSelectLocationController()
+ contentController.navigationItem.title = NSLocalizedString("Select location", comment: "Navigation title")
+ contentController.navigationItem.largeTitleDisplayMode = .never
+ contentController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDismissSelectLocationController(_:)))
+
+ let navController = SelectLocationNavigationController(contentController: contentController)
+ self.rootContainer?.present(navController, animated: true)
+ self.selectLocationViewController = contentController
+ }
+
+ func connectViewControllerShouldConnectTunnel(_ controller: ConnectViewController) {
+ connectTunnel()
+ }
+
+ func connectViewControllerShouldDisconnectTunnel(_ controller: ConnectViewController) {
+ disconnectTunnel()
+ }
+
+ func connectViewControllerShouldReconnectTunnel(_ controller: ConnectViewController) {
+ TunnelManager.shared.reconnectTunnel {
+ self.logger?.debug("Re-connected VPN tunnel")
+ }
+ }
+
+ @objc private func handleDismissSelectLocationController(_ sender: Any) {
+ self.selectLocationViewController?.dismiss(animated: true)
+ }
+
+ private func connectTunnel() {
+ TunnelManager.shared.startTunnel { (result) in
+ DispatchQueue.main.async {
+ switch result {
+ case .success:
+ self.logger?.debug("Connected VPN tunnel")
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to start the VPN tunnel")
+ self.presentTunnelError(error, alertTitle: NSLocalizedString("Failed to start the VPN tunnel", comment: ""))
+ }
+ }
+ }
+ }
+
+ private func disconnectTunnel() {
+ TunnelManager.shared.stopTunnel { (result) in
+ switch result {
+ case .success:
+ self.logger?.debug("Disconnected VPN tunnel")
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to stop the VPN tunnel")
+ self.presentTunnelError(error, alertTitle: NSLocalizedString("Failed to stop the VPN tunnel", comment: ""))
+ }
+ }
+ }
+
+ private func presentTunnelError(_ error: TunnelManager.Error, alertTitle: String) {
+ let alertController = UIAlertController(
+ title: alertTitle,
+ message: error.errorChainDescription,
+ preferredStyle: .alert
+ )
+ alertController.addAction(
+ UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel)
+ )
+
+ self.alertPresenter.enqueue(alertController, presentingController: self.rootContainer!)
+ }
+}
+
+// MARK: - SelectLocationViewControllerDelegate
+
+extension AppDelegate: SelectLocationViewControllerDelegate {
+ func selectLocationViewController(_ controller: SelectLocationViewController, didSelectRelayLocation relayLocation: RelayLocation) {
+ self.window?.isUserInteractionEnabled = false
+ DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) {
+ self.window?.isUserInteractionEnabled = true
+ controller.dismiss(animated: true) {
+ self.selectLocationControllerDidSelectRelayLocation(relayLocation)
+ }
+ }
+ }
+
+ private func selectLocationControllerDidSelectRelayLocation(_ relayLocation: RelayLocation) {
+ let relayConstraints = RelayConstraints(location: .only(relayLocation))
+
+ TunnelManager.shared.setRelayConstraints(relayConstraints) { [weak self] (result) in
+ guard let self = self else { return }
+
+ DispatchQueue.main.async {
+ self.relayConstraints = relayConstraints
+
+ switch result {
+ case .success:
+ self.logger?.debug("Updated relay constraints: \(relayConstraints)")
+ self.connectTunnel()
+
+ case .failure(let error):
+ self.logger?.error(chainedError: error, message: "Failed to update relay constraints")
+ }
+ }
+ }
+ }
+}
+
+// MARK: - RelayCacheObserver
+
+extension AppDelegate: RelayCacheObserver {
+
+ func relayCache(_ relayCache: RelayCache, didUpdateCachedRelays cachedRelays: CachedRelays) {
+ DispatchQueue.main.async {
+ self.cachedRelays = cachedRelays
+ }
+ }
+
+}
+
+// MARK: - AppStorePaymentManagerDelegate
+
extension AppDelegate: AppStorePaymentManagerDelegate {
func appStorePaymentManager(_ manager: AppStorePaymentManager,
@@ -187,4 +469,5 @@ extension AppDelegate: AppStorePaymentManagerDelegate {
// app launches, we assume that all successful purchases belong to the active account token.
return Account.shared.token
}
+
}
diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift
index 90c6c5f205..8dd31f3346 100644
--- a/ios/MullvadVPN/ConnectViewController.swift
+++ b/ios/MullvadVPN/ConnectViewController.swift
@@ -7,21 +7,27 @@
//
import UIKit
-import NetworkExtension
import Logging
+protocol ConnectViewControllerDelegate: class {
+ func connectViewControllerShouldShowSelectLocationPicker(_ controller: ConnectViewController)
+ func connectViewControllerShouldConnectTunnel(_ controller: ConnectViewController)
+ func connectViewControllerShouldDisconnectTunnel(_ controller: ConnectViewController)
+ func connectViewControllerShouldReconnectTunnel(_ controller: ConnectViewController)
+}
+
class ConnectViewController: UIViewController, RootContainment, TunnelObserver
{
- private lazy var mainContentView: ConnectMainContentView = {
+ weak var delegate: ConnectViewControllerDelegate?
+
+ private let mainContentView: ConnectMainContentView = {
let view = ConnectMainContentView(frame: UIScreen.main.bounds)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
- private var relayConstraints: RelayConstraints?
-
private let logger = Logger(label: "ConnectViewController")
- private let alertPresenter = AlertPresenter()
+
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
@@ -49,8 +55,6 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver
}
}
- private var showedAccountView = false
-
override func viewDidLoad() {
super.viewDidLoad()
@@ -61,24 +65,20 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver
mainContentView.selectLocationButton.addTarget(self, action: #selector(handleSelectLocation(_:)), for: .touchUpInside)
+ TunnelManager.shared.addObserver(self)
+ self.tunnelState = TunnelManager.shared.tunnelState
+
+ addSubviews()
+ }
+
+ private func addSubviews() {
view.addSubview(mainContentView)
NSLayoutConstraint.activate([
mainContentView.topAnchor.constraint(equalTo: view.topAnchor),
mainContentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mainContentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- mainContentView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ mainContentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
-
- TunnelManager.shared.addObserver(self)
- self.tunnelState = TunnelManager.shared.tunnelState
-
- fetchRelayConstraints()
- }
-
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
-
- showAccountViewForExpiredAccount()
}
// MARK: - TunnelObserver
@@ -135,137 +135,6 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver
}
}
- private func connectTunnel() {
- TunnelManager.shared.startTunnel { (result) in
- DispatchQueue.main.async {
- switch result {
- case .success:
- break
-
- case .failure(let error):
- self.logger.error(chainedError: error, message: "Failed to start the VPN tunnel")
-
- let alertController = UIAlertController(
- title: NSLocalizedString("Failed to start the VPN tunnel", comment: ""),
- message: error.errorChainDescription,
- preferredStyle: .alert
- )
- alertController.addAction(
- UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel)
- )
-
- self.alertPresenter.enqueue(alertController, presentingController: self)
- }
- }
- }
- }
-
- private func disconnectTunnel() {
- TunnelManager.shared.stopTunnel { (result) in
- if case .failure(let error) = result {
- self.logger.error(chainedError: error, message: "Failed to stop the VPN tunnel")
-
- let alertController = UIAlertController(
- title: NSLocalizedString("Failed to stop the VPN tunnel", comment: ""),
- message: error.errorChainDescription,
- preferredStyle: .alert
- )
- alertController.addAction(
- UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel)
- )
-
- self.alertPresenter.enqueue(alertController, presentingController: self)
- }
- }
- }
-
- private func reconnectTunnel() {
- TunnelManager.shared.reconnectTunnel(completionHandler: nil)
- }
-
- private func showAccountViewForExpiredAccount() {
- guard !showedAccountView else { return }
-
- showedAccountView = true
-
- if let accountExpiry = Account.shared.expiry, AccountExpiry(date: accountExpiry).isExpired {
- rootContainerController?.showSettings(navigateTo: .account, animated: true)
- }
- }
-
- private func showSelectLocationModal() {
- let contentController = SelectLocationViewController()
- contentController.navigationItem.title = NSLocalizedString("Select location", comment: "Navigation title")
- contentController.navigationItem.largeTitleDisplayMode = .never
- contentController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDismissSelectLocationController(_:)))
-
- contentController.didSelectRelayLocation = { [weak self] (controller, relayLocation) in
- controller.view.isUserInteractionEnabled = false
- DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) {
- controller.view.isUserInteractionEnabled = true
- controller.dismiss(animated: true) {
- self?.selectLocationControllerDidSelectRelayLocation(relayLocation)
- }
- }
- }
-
- let navController = SelectLocationNavigationController(contentController: contentController)
-
- view.isUserInteractionEnabled = false
- contentController.setSelectedRelayLocation(self.relayConstraints?.location.value, animated: false, scrollPosition: .none)
- contentController.prefetchData { (error) in
- if let error = error {
- self.logger.error(chainedError: error, message: "Failed to prefetch the relays for SelectLocationViewController")
- }
-
- self.present(navController, animated: true) {
- self.view.isUserInteractionEnabled = true
- }
- }
- }
-
- private func fetchRelayConstraints() {
- TunnelManager.shared.getRelayConstraints { (result) in
- DispatchQueue.main.async {
- switch result {
- case .success(let relayConstraints):
- self.relayConstraints = relayConstraints
-
- case .failure(let error):
- self.logger.error(chainedError: error)
- }
- }
- }
- }
-
- private func selectLocationControllerDidSelectRelayLocation(_ relayLocation: RelayLocation) {
- let relayConstraints = makeRelayConstraints(relayLocation)
-
- self.setTunnelRelayConstraints(relayConstraints)
- self.relayConstraints = relayConstraints
- }
-
- private func makeRelayConstraints(_ location: RelayLocation) -> RelayConstraints {
- return RelayConstraints(location: .only(location))
- }
-
- private func setTunnelRelayConstraints(_ relayConstraints: RelayConstraints) {
- TunnelManager.shared.setRelayConstraints(relayConstraints) { [weak self] (result) in
- guard let self = self else { return }
-
- DispatchQueue.main.async {
- switch result {
- case .success:
- self.logger.debug("Updated relay constraints: \(relayConstraints)")
- self.connectTunnel()
-
- case .failure(let error):
- self.logger.error(chainedError: error, message: "Failed to update relay constraints")
- }
- }
- }
- }
-
// MARK: - Actions
@objc func handleConnectionPanelButton(_ sender: Any) {
@@ -273,25 +142,20 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver
}
@objc func handleConnect(_ sender: Any) {
- connectTunnel()
+ delegate?.connectViewControllerShouldConnectTunnel(self)
}
@objc func handleDisconnect(_ sender: Any) {
- disconnectTunnel()
+ delegate?.connectViewControllerShouldDisconnectTunnel(self)
}
@objc func handleReconnect(_ sender: Any) {
- reconnectTunnel()
+ delegate?.connectViewControllerShouldReconnectTunnel(self)
}
@objc func handleSelectLocation(_ sender: Any) {
- showSelectLocationModal()
- }
-
- @objc func handleDismissSelectLocationController(_ sender: Any) {
- self.presentedViewController?.dismiss(animated: true)
+ delegate?.connectViewControllerShouldShowSelectLocationPicker(self)
}
-
}
private extension TunnelState {
diff --git a/ios/MullvadVPN/ConsentViewController.swift b/ios/MullvadVPN/ConsentViewController.swift
index ab029ed1ec..33beab0f54 100644
--- a/ios/MullvadVPN/ConsentViewController.swift
+++ b/ios/MullvadVPN/ConsentViewController.swift
@@ -13,14 +13,14 @@ private let kPrivacyPolicyURL = URL(string: "https://mullvad.net/en/help/privacy
class ConsentViewController: UIViewController, RootContainment, SFSafariViewControllerDelegate {
- var completionHandler: (() -> Void)?
+ var completionHandler: ((UIViewController) -> Void)?
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
- var preferredHeaderBarPresentation: HeaderBarPresentation {
- return HeaderBarPresentation(style: .default, showsDivider: false)
+ var preferredHeaderBarStyle: HeaderBarStyle {
+ return .default
}
var prefersHeaderBarHidden: Bool {
diff --git a/ios/MullvadVPN/RelayCache.swift b/ios/MullvadVPN/RelayCache.swift
index c561303e49..2d15f9a1fd 100644
--- a/ios/MullvadVPN/RelayCache.swift
+++ b/ios/MullvadVPN/RelayCache.swift
@@ -195,7 +195,7 @@ class RelayCache {
}
case .failure(let readError):
- self.logger.error(chainedError: readError, message: "Failed to read the relay cache")
+ self.logger.error(chainedError: readError, message: "Failed to read the relay cache to determine if it needs to be updated")
if Self.shouldDownloadRelaysOnReadFailure(readError) {
self.downloadRelays()
diff --git a/ios/MullvadVPN/RootContainerViewController.swift b/ios/MullvadVPN/RootContainerViewController.swift
index 9a3fa411fa..3057266315 100644
--- a/ios/MullvadVPN/RootContainerViewController.swift
+++ b/ios/MullvadVPN/RootContainerViewController.swift
@@ -169,16 +169,16 @@ class RootContainerViewController: UIViewController {
)
}
- func pushViewController(_ viewController: UIViewController, animated: Bool) {
+ func pushViewController(_ viewController: UIViewController, animated: Bool, completion: CompletionHandler? = nil) {
var newViewControllers = viewControllers.filter({ $0 != viewController })
newViewControllers.append(viewController)
- setViewControllersInternal(newViewControllers, isUnwinding: false, animated: animated)
+ setViewControllersInternal(newViewControllers, isUnwinding: false, animated: animated, completion: completion)
}
- func popToRootViewController(animated: Bool) {
+ func popToRootViewController(animated: Bool, completion: CompletionHandler? = nil) {
if let rootController = self.viewControllers.first, self.viewControllers.count > 1 {
- setViewControllersInternal([rootController], isUnwinding: true, animated: animated)
+ setViewControllersInternal([rootController], isUnwinding: true, animated: animated, completion: completion)
}
}