Skip to content

Commit

Permalink
Add preliminary settings page for relay IP overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Petersson committed Jan 15, 2024
1 parent 6bc4b86 commit cf3e457
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 2 deletions.
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 @@ -1623,7 +1625,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 @@ -1645,6 +1646,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 @@ -2016,7 +2019,6 @@
581943F228F8014500B0CB5E /* MullvadTypes */ = {
isa = PBXGroup;
children = (
7A0B31152B2B4BE7004B12E0 /* AccessbilityIdentifier.swift */,
584D26BE270C550B004EA533 /* AnyIPAddress.swift */,
586A951329013235007BAF2B /* AnyIPEndpoint.swift */,
06AC113628F83FD70037AF9A /* Cancellable.swift */,
Expand Down Expand Up @@ -3151,6 +3153,7 @@
isa = PBXGroup;
children = (
58CEB2E72AFBB9F300E6E088 /* APIAccess */,
7A5869A92B55516700640D27 /* IPOverride */,
58EFC7702AFB45E500E9F4CB /* SettingsChildCoordinator.swift */,
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
);
Expand Down Expand Up @@ -3218,6 +3221,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 @@ -4612,6 +4624,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 @@ -4837,6 +4850,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 @@ -34,6 +34,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()
navigationController.pushViewController(viewController, animated: animated)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//
// IPOverrideViewController.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-01-15.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import UIKit

class IPOverrideViewController: UIViewController {
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
}()

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.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 didTapImportTextButton() {}
@objc private func didTapImportFileButton() {}
@objc private func didTapClearButton() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ enum SettingsNavigationRoute: Equatable {

/// API access route.
case apiAccess

/// IP override route.
case ipOverride
}

/// Top-level settings coordinator.
Expand Down Expand Up @@ -248,6 +251,9 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
case .apiAccess:
return .childCoordinator(ListAccessMethodCoordinator(navigationController: navigationController))

case .ipOverride:
return .childCoordinator(IPOverrideCoordinator(navigationController: navigationController))

case .faq:
// Handled separately and presented as a modal.
return .failed
Expand All @@ -267,6 +273,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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
case problemReport
case faq
case apiAccess
case ipOverride

var accessibilityIdentifier: AccessibilityIdentifier {
switch self {
Expand All @@ -52,6 +53,8 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
return .faqCell
case .apiAccess:
return .apiAccessCell
case .ipOverride:
return .ipOverrideCell
}
}

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ extension SettingsDataSource.Item {
return .faq
case .apiAccess:
return .apiAccess
case .ipOverride:
return .ipOverride
}
}
}

0 comments on commit cf3e457

Please sign in to comment.