Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preliminary settings page for relay IP overrides #5688

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand All @@ -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>"; };
Expand Down Expand Up @@ -2060,7 +2063,6 @@
581943F228F8014500B0CB5E /* MullvadTypes */ = {
isa = PBXGroup;
children = (
7A0B31152B2B4BE7004B12E0 /* AccessbilityIdentifier.swift */,
584D26BE270C550B004EA533 /* AnyIPAddress.swift */,
586A951329013235007BAF2B /* AnyIPEndpoint.swift */,
06AC113628F83FD70037AF9A /* Cancellable.swift */,
Expand Down Expand Up @@ -3200,6 +3202,7 @@
isa = PBXGroup;
children = (
58CEB2E72AFBB9F300E6E088 /* APIAccess */,
7A5869A92B55516700640D27 /* IPOverride */,
58EFC7702AFB45E500E9F4CB /* SettingsChildCoordinator.swift */,
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
);
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
1 change: 1 addition & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public enum AccessibilityIdentifier: String {
case problemReportCell
case faqCell
case apiAccessCell
case ipOverrideCell
case relayFilterOwnershipCell
case relayFilterProviderCell

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ enum SettingsNavigationRoute: Equatable {

/// API access route.
case apiAccess

/// IP override route.
case ipOverride
}

/// Top-level settings coordinator.
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down
13 changes: 13 additions & 0 deletions ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Loading
Loading