summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2024-01-15 16:46:09 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-01-23 16:35:10 +0100
commit7ad86cef3d339b7c72d081aeafef564f4f4f71b1 (patch)
tree14428c05df62360b7231354e283cfac6d3ad12b6
parent3a74932c162db828afe3bc54bcd5ba44af0cd657 (diff)
downloadmullvadvpn-7ad86cef3d339b7c72d081aeafef564f4f4f71b1.tar.xz
mullvadvpn-7ad86cef3d339b7c72d081aeafef564f4f4f71b1.zip
Add preliminary settings page for relay IP overrides
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj18
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift28
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift225
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift8
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift13
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift7
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift2
8 files changed, 300 insertions, 2 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 0b0962762f..6a43be8c0d 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -505,6 +505,8 @@
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
7A5869952B32E9C700640D27 /* LinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869942B32E9C700640D27 /* LinkButton.swift */; };
7A5869972B32EA4500640D27 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869962B32EA4500640D27 /* AppButton.swift */; };
+ 7A5869AB2B55527C00640D27 /* IPOverrideCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869AA2B55527C00640D27 /* IPOverrideCoordinator.swift */; };
+ 7A5869AD2B5552E200640D27 /* IPOverrideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869AC2B5552E200640D27 /* IPOverrideViewController.swift */; };
7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; };
7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */; };
7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
@@ -1643,7 +1645,6 @@
58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodValidationError.swift; sourceTree = "<group>"; };
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = "<group>"; };
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; };
- 7A0B31152B2B4BE7004B12E0 /* AccessbilityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessbilityIdentifier.swift; sourceTree = "<group>"; };
7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessbilityIdentifier.swift; sourceTree = "<group>"; };
7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Coordinator+Router.swift"; sourceTree = "<group>"; };
7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = "<group>"; };
@@ -1665,6 +1666,8 @@
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A5869942B32E9C700640D27 /* LinkButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkButton.swift; sourceTree = "<group>"; };
7A5869962B32EA4500640D27 /* AppButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppButton.swift; sourceTree = "<group>"; };
+ 7A5869AA2B55527C00640D27 /* IPOverrideCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideCoordinator.swift; sourceTree = "<group>"; };
+ 7A5869AC2B5552E200640D27 /* IPOverrideViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideViewController.swift; sourceTree = "<group>"; };
7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = "<group>"; };
7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryTests.swift; sourceTree = "<group>"; };
7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; };
@@ -2060,7 +2063,6 @@
581943F228F8014500B0CB5E /* MullvadTypes */ = {
isa = PBXGroup;
children = (
- 7A0B31152B2B4BE7004B12E0 /* AccessbilityIdentifier.swift */,
584D26BE270C550B004EA533 /* AnyIPAddress.swift */,
586A951329013235007BAF2B /* AnyIPEndpoint.swift */,
06AC113628F83FD70037AF9A /* Cancellable.swift */,
@@ -3200,6 +3202,7 @@
isa = PBXGroup;
children = (
58CEB2E72AFBB9F300E6E088 /* APIAccess */,
+ 7A5869A92B55516700640D27 /* IPOverride */,
58EFC7702AFB45E500E9F4CB /* SettingsChildCoordinator.swift */,
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
);
@@ -3269,6 +3272,15 @@
path = Alert;
sourceTree = "<group>";
};
+ 7A5869A92B55516700640D27 /* IPOverride */ = {
+ isa = PBXGroup;
+ children = (
+ 7A5869AA2B55527C00640D27 /* IPOverrideCoordinator.swift */,
+ 7A5869AC2B5552E200640D27 /* IPOverrideViewController.swift */,
+ );
+ path = IPOverride;
+ sourceTree = "<group>";
+ };
7A83C3FC2A55B39500DFB83A /* TestPlans */ = {
isa = PBXGroup;
children = (
@@ -4723,6 +4735,7 @@
58DFF7D82B02774C00F864E0 /* ListItemPickerViewController.swift in Sources */,
5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */,
7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */,
+ 7A5869AB2B55527C00640D27 /* IPOverrideCoordinator.swift in Sources */,
5867771429097BCD006F721F /* PaymentState.swift in Sources */,
F0EF50D32A8FA47E0031E8DF /* ChangeLogInteractor.swift in Sources */,
7AC8A3AF2ABC71D600DC4939 /* TermsOfServiceCoordinator.swift in Sources */,
@@ -4948,6 +4961,7 @@
58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */,
58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */,
58FF9FEA2B07653800E4C97D /* ButtonCellContentView.swift in Sources */,
+ 7A5869AD2B5552E200640D27 /* IPOverrideViewController.swift in Sources */,
F0E8E4C12A602CCB00ED26A3 /* AccountDeletionContentView.swift in Sources */,
58EF87512B16176300C098B2 /* AccessMethodActionSheetContentConfiguration.swift in Sources */,
58B26E1E2943514300D5980C /* InAppNotificationDescriptor.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index a69c70c32d..7cb2e0e607 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -35,6 +35,7 @@ public enum AccessibilityIdentifier: String {
case problemReportCell
case faqCell
case apiAccessCell
+ case ipOverrideCell
case relayFilterOwnershipCell
case relayFilterProviderCell
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
new file mode 100644
index 0000000000..8ba9a072a4
--- /dev/null
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
@@ -0,0 +1,28 @@
+//
+// IPOverrideCoordinator.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2024-01-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadSettings
+import Routing
+import UIKit
+
+class IPOverrideCoordinator: Coordinator, Presenting, SettingsChildCoordinator {
+ let navigationController: UINavigationController
+
+ var presentationContext: UIViewController {
+ navigationController
+ }
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ func start(animated: Bool) {
+ let viewController = IPOverrideViewController(alertPresenter: AlertPresenter(context: self))
+ navigationController.pushViewController(viewController, animated: animated)
+ }
+}
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift
new file mode 100644
index 0000000000..80a675b936
--- /dev/null
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift
@@ -0,0 +1,225 @@
+//
+// IPOverrideViewController.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2024-01-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+class IPOverrideViewController: UIViewController {
+ let alertPresenter: AlertPresenter
+
+ private lazy var containerView: UIStackView = {
+ let view = UIStackView()
+ view.axis = .vertical
+ view.spacing = 20
+ return view
+ }()
+
+ private lazy var clearButton: AppButton = {
+ let button = AppButton(style: .danger)
+ button.addTarget(self, action: #selector(didTapClearButton), for: .touchUpInside)
+ button.setTitle(NSLocalizedString(
+ "IP_OVERRIDE_CLEAR_BUTTON",
+ tableName: "IPOverride",
+ value: "Clear all overrides",
+ comment: ""
+ ), for: .normal)
+ return button
+ }()
+
+ init(alertPresenter: AlertPresenter) {
+ self.alertPresenter = alertPresenter
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ navigationController?.navigationBar.prefersLargeTitles = false
+ view.backgroundColor = .secondaryColor
+
+ addHeader()
+ addPreamble()
+ addImportButtons()
+ addStatusLabel()
+
+ view.addConstrainedSubviews([containerView, clearButton]) {
+ containerView.pinEdgesToSuperviewMargins(.all().excluding(.bottom))
+ clearButton.pinEdgesToSuperviewMargins(.all().excluding(.top))
+ }
+ }
+
+ private func addHeader() {
+ let label = UILabel()
+ label.font = .systemFont(ofSize: 32, weight: .bold)
+ label.textColor = .white
+ label.text = NSLocalizedString(
+ "IP_OVERRIDE_HEADER",
+ tableName: "IPOverride",
+ value: "Server IP override",
+ comment: ""
+ )
+
+ let infoButton = UIButton(type: .custom)
+ infoButton.tintColor = .white
+ infoButton.setImage(UIImage(resource: .iconInfo), for: .normal)
+ infoButton.addTarget(self, action: #selector(didTapInfoButton), for: .touchUpInside)
+ infoButton.heightAnchor.constraint(equalToConstant: 24).isActive = true
+ infoButton.widthAnchor.constraint(equalTo: infoButton.heightAnchor, multiplier: 1).isActive = true
+
+ let headerView = UIStackView(arrangedSubviews: [label, infoButton, UIView()])
+ headerView.spacing = 8
+
+ containerView.addArrangedSubview(headerView)
+ containerView.setCustomSpacing(14, after: headerView)
+ }
+
+ private func addPreamble() {
+ let label = UILabel()
+ label.font = .systemFont(ofSize: 12, weight: .semibold)
+ label.textColor = .white.withAlphaComponent(0.6)
+ label.numberOfLines = 0
+ label.text = NSLocalizedString(
+ "IP_OVERRIDE_PREAMBLE",
+ tableName: "IPOverride",
+ value: "Import files or text with new IP addresses for the servers in the Select location view.",
+ comment: ""
+ )
+
+ containerView.addArrangedSubview(label)
+ }
+
+ private func addImportButtons() {
+ let importTextButton = AppButton(style: .default)
+ importTextButton.addTarget(self, action: #selector(didTapImportTextButton), for: .touchUpInside)
+ importTextButton.setTitle(NSLocalizedString(
+ "IP_OVERRIDE_IMPORT_TEXT_BUTTON",
+ tableName: "IPOverride",
+ value: "Import via text",
+ comment: ""
+ ), for: .normal)
+
+ let importFileButton = AppButton(style: .default)
+ importFileButton.addTarget(self, action: #selector(didTapImportFileButton), for: .touchUpInside)
+ importFileButton.setTitle(NSLocalizedString(
+ "IP_OVERRIDE_IMPORT_FILE_BUTTON",
+ tableName: "IPOverride",
+ value: "Import file",
+ comment: ""
+ ), for: .normal)
+
+ let stackView = UIStackView(arrangedSubviews: [importTextButton, importFileButton])
+ stackView.distribution = .fillEqually
+ stackView.spacing = 12
+
+ containerView.addArrangedSubview(stackView)
+ }
+
+ private func addStatusLabel() {
+ let label = UILabel()
+ label.font = .systemFont(ofSize: 22, weight: .bold)
+ label.textColor = .white
+ label.text = NSLocalizedString(
+ "IP_OVERRIDE_STATUS",
+ tableName: "IPOverride",
+ value: "Overrides active",
+ comment: ""
+ ).uppercased()
+
+ containerView.addArrangedSubview(label)
+ }
+
+ @objc private func didTapInfoButton() {
+ let message = NSLocalizedString(
+ "IP_OVERRIDE_DIALOG_MESSAGE",
+ tableName: "IPOverride",
+ value: """
+ On some networks, where various types of censorship are being used, our server IP addresses are \
+ sometimes blocked.
+
+ To circumvent this you can import a file or a text, provided by our support team, \
+ with new IP addresses that override the default addresses of the servers in the Select location view.
+
+ If you are having issues connecting to VPN servers, please contact support.
+ """,
+ comment: ""
+ )
+
+ let presentation = AlertPresentation(
+ id: "ip-override-info-alert",
+ icon: .info,
+ title: NSLocalizedString(
+ "IP_OVERRIDE_INFO_DIALOG_TITLE",
+ tableName: "IPOverride",
+ value: "Server IP override",
+ comment: ""
+ ),
+ message: message,
+ buttons: [AlertAction(
+ title: NSLocalizedString(
+ "IP_OVERRIDE_INFO_DIALOG_OK_BUTTON",
+ tableName: "IPOverride",
+ value: "Got it!",
+ comment: ""
+ ),
+ style: .default
+ )]
+ )
+
+ alertPresenter.showAlert(presentation: presentation, animated: true)
+ }
+
+ @objc private func didTapClearButton() {
+ let presentation = AlertPresentation(
+ id: "ip-override-clear-alert",
+ icon: .alert,
+ title: NSLocalizedString(
+ "IP_OVERRIDE_CLEAR_DIALOG_TITLE",
+ tableName: "IPOverride",
+ value: "Clear all overrides?",
+ comment: ""
+ ),
+ message: NSLocalizedString(
+ "IP_OVERRIDE_CLEAR_DIALOG_MESSAGE",
+ tableName: "IPOverride",
+ value: """
+ Clearing the imported overrides changes the server IPs, in the Select location view, \
+ back to default.
+ """,
+ comment: ""
+ ),
+ buttons: [
+ AlertAction(
+ title: NSLocalizedString(
+ "IP_OVERRIDE_CLEAR_DIALOG_CANCEL_BUTTON",
+ tableName: "IPOverride",
+ value: "Cancel",
+ comment: ""
+ ),
+ style: .default
+ ),
+ AlertAction(
+ title: NSLocalizedString(
+ "IP_OVERRIDE_CLEAR_DIALOG_CLEAR_BUTTON",
+ tableName: "IPOverride",
+ value: "Clear",
+ comment: ""
+ ),
+ style: .destructive
+ ),
+ ]
+ )
+
+ alertPresenter.showAlert(presentation: presentation, animated: true)
+ }
+
+ @objc private func didTapImportTextButton() {}
+ @objc private func didTapImportFileButton() {}
+}
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
index a0023dd237..06178b93e9 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
@@ -28,6 +28,9 @@ enum SettingsNavigationRoute: Equatable {
/// API access route.
case apiAccess
+
+ /// IP override route.
+ case ipOverride
}
/// Top-level settings coordinator.
@@ -255,6 +258,9 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
accessMethodRepository: accessMethodRepository
))
+ case .ipOverride:
+ return .childCoordinator(IPOverrideCoordinator(navigationController: navigationController))
+
case .faq:
// Handled separately and presented as a modal.
return .failed
@@ -274,6 +280,8 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
return .problemReport
case is ListAccessMethodViewController:
return .apiAccess
+ case is IPOverrideViewController:
+ return .ipOverride
default:
return nil
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
index 4cd7884aef..7f5f2d02a6 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
@@ -92,6 +92,19 @@ struct SettingsCellFactory: CellFactoryProtocol {
cell.detailTitleLabel.text = nil
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.disclosureType = .chevron
+
+ case .ipOverride:
+ guard let cell = cell as? SettingsCell else { return }
+
+ cell.titleLabel.text = NSLocalizedString(
+ "IP_OVERRIDE_CELL_LABEL",
+ tableName: "Settings",
+ value: "Server IP override",
+ comment: ""
+ )
+ cell.detailTitleLabel.text = nil
+ cell.accessibilityIdentifier = item.accessibilityIdentifier
+ cell.disclosureType = .chevron
}
}
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
index a7615bdf98..71c493b7c8 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
@@ -39,6 +39,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
case problemReport
case faq
case apiAccess
+ case ipOverride
var accessibilityIdentifier: AccessibilityIdentifier {
switch self {
@@ -52,6 +53,8 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
return .faqCell
case .apiAccess:
return .apiAccessCell
+ case .ipOverride:
+ return .ipOverrideCell
}
}
@@ -155,6 +158,10 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
snapshot.appendItems([.apiAccess], toSection: .main)
#endif
+ #if DEBUG
+ snapshot.appendItems([.ipOverride], toSection: .main)
+ #endif
+
snapshot.appendSections([.version, .problemReport])
snapshot.appendItems([.version], toSection: .version)
snapshot.appendItems([.problemReport, .faq], toSection: .problemReport)
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
index 4d072a1e27..7ac7167840 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
@@ -88,6 +88,8 @@ extension SettingsDataSource.Item {
return .faq
case .apiAccess:
return .apiAccess
+ case .ipOverride:
+ return .ipOverride
}
}
}