summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2024-10-30 09:18:37 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-10-30 09:18:37 +0100
commit2da598ba40d6bf59509ad4b2ba0c417572d5ed14 (patch)
tree956a39f5b1e0587c00396f35554dbf65ddec581c
parent72b4281b0d19e6f0a67f073cce8f02f2b2bca9bd (diff)
parent80eb27b57db7ffc9b6f623022e836ed34ac14c5f (diff)
downloadmullvadvpn-2da598ba40d6bf59509ad4b2ba0c417572d5ed14.tar.xz
mullvadvpn-2da598ba40d6bf59509ad4b2ba0c417572d5ed14.zip
Merge branch 'change-each-cell-to-show-the-new-behavior-ios-878'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj10
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift3
-rw-r--r--ios/MullvadVPN/UI appearance/UIMetrics.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Settings/CheckableSettingsCell.swift18
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SelectableSettingsCell.swift17
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SelectableSettingsDetailsCell.swift73
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsAddDNSEntryCell.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCell.swift95
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift58
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift46
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift1
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDetailsButtonItem.swift12
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift6
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift5
-rw-r--r--ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift4
16 files changed, 282 insertions, 74 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 2bd37ffa6c..7fe94e8467 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -466,6 +466,8 @@
7A27E3C92CAE85710088BCFF /* SettingsInfoButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */; };
7A27E3CB2CAE861D0088BCFF /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */; };
7A27E3CD2CB814EF0088BCFF /* DAITAInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */; };
+ 7A27E3CF2CBD4A8C0088BCFF /* SelectableSettingsDetailsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */; };
+ 7A27E3D12CC299F90088BCFF /* VPNSettingsDetailsButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3D02CC299E60088BCFF /* VPNSettingsDetailsButtonItem.swift */; };
7A28826A2BA8336600FD9F20 /* VPNSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2882692BA8336600FD9F20 /* VPNSettingsCoordinator.swift */; };
7A2960F62A963F7500389B82 /* AlertCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2960F52A963F7500389B82 /* AlertCoordinator.swift */; };
7A2960FD2A964BB700389B82 /* AlertPresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2960FC2A964BB700389B82 /* AlertPresentation.swift */; };
@@ -1803,6 +1805,8 @@
7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoButtonItem.swift; sourceTree = "<group>"; };
7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITAInfoView.swift; sourceTree = "<group>"; };
+ 7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsDetailsCell.swift; sourceTree = "<group>"; };
+ 7A27E3D02CC299E60088BCFF /* VPNSettingsDetailsButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsDetailsButtonItem.swift; sourceTree = "<group>"; };
7A2882692BA8336600FD9F20 /* VPNSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsCoordinator.swift; sourceTree = "<group>"; };
7A2960F52A963F7500389B82 /* AlertCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCoordinator.swift; sourceTree = "<group>"; };
7A2960FC2A964BB700389B82 /* AlertPresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresentation.swift; sourceTree = "<group>"; };
@@ -2802,7 +2806,9 @@
isa = PBXGroup;
children = (
7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */,
+ F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */,
+ 7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */,
5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */,
582BB1AE229566420055B6EF /* SettingsCell.swift */,
5864AF0029C7879B005B0CD9 /* SettingsCellFactory.swift */,
@@ -2815,7 +2821,6 @@
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */,
58677711290976FB006F721F /* SettingsInteractor.swift */,
5867770F290975E8006F721F /* SettingsInteractorFactory.swift */,
- F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */,
58CCA01122424D11004F3011 /* SettingsViewController.swift */,
7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */,
@@ -2845,6 +2850,7 @@
5864AF0229C7879B005B0CD9 /* VPNSettingsCellFactory.swift */,
584D26C3270C855A004EA533 /* VPNSettingsDataSource.swift */,
587EB6732714520600123C75 /* VPNSettingsDataSourceDelegate.swift */,
+ 7A27E3D02CC299E60088BCFF /* VPNSettingsDetailsButtonItem.swift */,
7A6F2FAE2AFE36E7006D0856 /* VPNSettingsInfoButtonItem.swift */,
5871167E2910035700D41AAC /* VPNSettingsInteractor.swift */,
58ACF6482655365700ACE4B7 /* VPNSettingsViewController.swift */,
@@ -5658,6 +5664,7 @@
A91614D62B10B26B00F416EB /* TunnelControlViewModel.swift in Sources */,
7A5869972B32EA4500640D27 /* AppButton.swift in Sources */,
586C0D8F2B03D88100E7CDD7 /* ProxyProtocolConfigurationItemIdentifier.swift in Sources */,
+ 7A27E3CF2CBD4A8C0088BCFF /* SelectableSettingsDetailsCell.swift in Sources */,
7A27E3CB2CAE861D0088BCFF /* SettingsViewModel.swift in Sources */,
588527B2276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift in Sources */,
7A9F29392CABFAFC005F2089 /* InfoHeaderView.swift in Sources */,
@@ -5854,6 +5861,7 @@
F0DA87492A9CBA9F006044F1 /* AccountDeviceRow.swift in Sources */,
58FF9FE42B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift in Sources */,
5878A27329091D6D0096FC88 /* TunnelBlockObserver.swift in Sources */,
+ 7A27E3D12CC299F90088BCFF /* VPNSettingsDetailsButtonItem.swift in Sources */,
A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */,
58CEB2E92AFBBA4A00E6E088 /* AddAccessMethodCoordinator.swift in Sources */,
58DFF7D02B02560400F864E0 /* NSAttributedString+Extensions.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index 6cfbce70a1..c706fd9298 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -174,7 +174,8 @@ public enum AccessibilityIdentifier: String {
case wireGuardObfuscationAutomatic
case wireGuardObfuscationPort
case wireGuardObfuscationOff
- case wireGuardObfuscationOn
+ case wireGuardObfuscationUdpOverTcp
+ case wireGuardObfuscationShadowsocks
case wireGuardPort
// Custom DNS
diff --git a/ios/MullvadVPN/UI appearance/UIMetrics.swift b/ios/MullvadVPN/UI appearance/UIMetrics.swift
index 9e4997da1e..ce108dceb8 100644
--- a/ios/MullvadVPN/UI appearance/UIMetrics.swift
+++ b/ios/MullvadVPN/UI appearance/UIMetrics.swift
@@ -93,6 +93,8 @@ enum UIMetrics {
static let customListsCellHeight: CGFloat = 44
static let apiAccessSwitchCellTrailingMargin: CGFloat = apiAccessInsetLayoutMargins.trailing - 4
static let apiAccessPickerListContentInsetTop: CGFloat = 16
+ static let verticalDividerHeight: CGFloat = 22
+ static let detailsButtonSize: CGFloat = 60
}
enum InAppBannerNotification {
diff --git a/ios/MullvadVPN/View controllers/Settings/CheckableSettingsCell.swift b/ios/MullvadVPN/View controllers/Settings/CheckableSettingsCell.swift
index a732234473..6f7b0cb5ef 100644
--- a/ios/MullvadVPN/View controllers/Settings/CheckableSettingsCell.swift
+++ b/ios/MullvadVPN/View controllers/Settings/CheckableSettingsCell.swift
@@ -14,7 +14,7 @@ class CheckableSettingsCell: SettingsCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
- setLeftView(checkboxView, spacing: UIMetrics.SettingsCell.checkableSettingsCellLeftViewSpacing)
+ setCheckboxView()
selectedBackgroundView?.backgroundColor = .clear
}
@@ -24,8 +24,7 @@ class CheckableSettingsCell: SettingsCell {
override func prepareForReuse() {
super.prepareForReuse()
-
- setLeftView(checkboxView, spacing: UIMetrics.SettingsCell.checkableSettingsCellLeftViewSpacing)
+ setCheckboxView()
}
override func setSelected(_ selected: Bool, animated: Bool) {
@@ -39,4 +38,17 @@ class CheckableSettingsCell: SettingsCell {
contentView.layoutMargins.left = 0
}
+
+ private func setCheckboxView() {
+ setLeadingView { superview in
+ superview.addConstrainedSubviews([checkboxView]) {
+ checkboxView.pinEdgesToSuperview(PinnableEdges([
+ .leading(0),
+ .trailing(UIMetrics.SettingsCell.checkableSettingsCellLeftViewSpacing),
+ .top(0),
+ .bottom(0),
+ ]))
+ }
+ }
+ }
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SelectableSettingsCell.swift b/ios/MullvadVPN/View controllers/Settings/SelectableSettingsCell.swift
index 3cc5bb1e76..9da9564d27 100644
--- a/ios/MullvadVPN/View controllers/Settings/SelectableSettingsCell.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SelectableSettingsCell.swift
@@ -11,6 +11,7 @@ import UIKit
class SelectableSettingsCell: SettingsCell {
let tickImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "IconTick"))
+ imageView.contentMode = .center
imageView.tintColor = .white
imageView.alpha = 0
return imageView
@@ -19,7 +20,7 @@ class SelectableSettingsCell: SettingsCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
- setLeftView(tickImageView, spacing: UIMetrics.SettingsCell.selectableSettingsCellLeftViewSpacing)
+ setTickView()
selectedBackgroundView?.backgroundColor = UIColor.Cell.Background.selected
}
@@ -29,8 +30,7 @@ class SelectableSettingsCell: SettingsCell {
override func prepareForReuse() {
super.prepareForReuse()
-
- setLeftView(tickImageView, spacing: UIMetrics.SettingsCell.selectableSettingsCellLeftViewSpacing)
+ setTickView()
}
override func setSelected(_ selected: Bool, animated: Bool) {
@@ -38,4 +38,15 @@ class SelectableSettingsCell: SettingsCell {
tickImageView.alpha = selected ? 1 : 0
}
+
+ private func setTickView() {
+ setLeadingView { superview in
+ superview.addConstrainedSubviews([tickImageView]) {
+ tickImageView.pinEdgesToSuperview(PinnableEdges([
+ .leading(0), .top(0), .bottom(0),
+ .trailing(UIMetrics.SettingsCell.selectableSettingsCellLeftViewSpacing),
+ ]))
+ }
+ }
+ }
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SelectableSettingsDetailsCell.swift b/ios/MullvadVPN/View controllers/Settings/SelectableSettingsDetailsCell.swift
new file mode 100644
index 0000000000..33708d1b86
--- /dev/null
+++ b/ios/MullvadVPN/View controllers/Settings/SelectableSettingsDetailsCell.swift
@@ -0,0 +1,73 @@
+//
+// SelectableSettingsDetailsCell.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2024-10-14.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+class SelectableSettingsDetailsCell: SelectableSettingsCell {
+ let viewContainer = UIView()
+
+ var buttonAction: (() -> Void)?
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
+
+ let actionButton = IncreasedHitButton(type: .system)
+ var actionButtonConfiguration = actionButton.configuration ?? .plain()
+ actionButtonConfiguration.image = UIImage(systemName: "ellipsis")?
+ .withRenderingMode(.alwaysOriginal)
+ .withTintColor(.white)
+ actionButton.configuration = actionButtonConfiguration
+
+ actionButton.addTarget(
+ self,
+ action: #selector(didPressActionButton),
+ for: .touchUpInside
+ )
+
+ let separatorView = UIView()
+ separatorView.backgroundColor = .secondaryColor
+
+ viewContainer.addConstrainedSubviews([separatorView, actionButton]) {
+ separatorView.leadingAnchor.constraint(equalTo: viewContainer.leadingAnchor, constant: 16)
+ separatorView.centerYAnchor.constraint(equalTo: viewContainer.centerYAnchor)
+ separatorView.heightAnchor.constraint(equalToConstant: UIMetrics.SettingsCell.verticalDividerHeight)
+ separatorView.widthAnchor.constraint(equalToConstant: 1)
+
+ actionButton.pinEdgesToSuperview(.all().excluding(.leading))
+ actionButton.leadingAnchor.constraint(equalTo: separatorView.trailingAnchor)
+ actionButton.widthAnchor.constraint(equalToConstant: UIMetrics.SettingsCell.detailsButtonSize)
+ }
+
+ setViewContainer()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ setViewContainer()
+ }
+
+ private func setViewContainer() {
+ #if DEBUG
+ setTrailingView { superview in
+ superview.addConstrainedSubviews([viewContainer]) {
+ viewContainer.pinEdgesToSuperview()
+ }
+ }
+ #endif
+ }
+
+ // MARK: - Actions
+
+ @objc private func didPressActionButton() {
+ buttonAction?()
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsAddDNSEntryCell.swift b/ios/MullvadVPN/View controllers/Settings/SettingsAddDNSEntryCell.swift
index 11f2c416bc..51f7131ff9 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsAddDNSEntryCell.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsAddDNSEntryCell.swift
@@ -9,7 +9,7 @@
import UIKit
class SettingsAddDNSEntryCell: SettingsCell {
- var action: (() -> Void)?
+ var tapAction: (() -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
@@ -29,7 +29,7 @@ class SettingsAddDNSEntryCell: SettingsCell {
@objc func handleTap(_ sender: UIGestureRecognizer) {
if case .ended = sender.state {
- action?()
+ tapAction?()
}
}
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCell.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCell.swift
index c55b5f0aa2..591857be9b 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCell.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCell.swift
@@ -31,11 +31,10 @@ enum SettingsDisclosureType {
class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
typealias InfoButtonHandler = () -> Void
- let contentContainerSubviewMaxCount = 2
- let titleLabel = UILabel()
- let detailTitleLabel = UILabel()
let disclosureImageView = UIImageView(image: nil)
- let contentContainer = UIStackView()
+ let mainContentContainer = UIView()
+ let leftContentContainer = UIView()
+ let rightContentContainer = UIView()
var infoButtonHandler: InfoButtonHandler? { didSet {
infoButton.isHidden = infoButtonHandler == nil
}}
@@ -59,8 +58,27 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
}
}
+ let titleLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFont.systemFont(ofSize: 17)
+ label.textColor = UIColor.Cell.titleTextColor
+ label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+ label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
+ return label
+ }()
+
+ let detailTitleLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFont.systemFont(ofSize: 13)
+ label.textColor = UIColor.Cell.detailTextColor
+ label.setContentHuggingPriority(.defaultLow, for: .horizontal)
+ label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+ return label
+ }()
+
private var subCellLeadingIndentation: CGFloat = 0
private let buttonWidth: CGFloat = 24
+
private let infoButton: UIButton = {
let button = UIButton(type: .custom)
button.accessibilityIdentifier = .infoButton
@@ -83,38 +101,29 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
backgroundColor = .clear
contentView.backgroundColor = .clear
- infoButton.isHidden = true
infoButton.addTarget(self, action: #selector(handleInfoButton(_:)), for: .touchUpInside)
subCellLeadingIndentation = contentView.layoutMargins.left + UIMetrics.TableView.cellIndentationWidth
- titleLabel.translatesAutoresizingMaskIntoConstraints = false
- titleLabel.font = UIFont.systemFont(ofSize: 17)
- titleLabel.textColor = UIColor.Cell.titleTextColor
-
- detailTitleLabel.translatesAutoresizingMaskIntoConstraints = false
- detailTitleLabel.font = UIFont.systemFont(ofSize: 13)
- detailTitleLabel.textColor = UIColor.Cell.detailTextColor
-
- titleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
- detailTitleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
-
- titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
- detailTitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+ rightContentContainer.setContentHuggingPriority(.required, for: .horizontal)
setLayoutMargins()
let buttonAreaWidth = UIMetrics.contentLayoutMargins.leading + UIMetrics
.contentLayoutMargins.trailing + buttonWidth
- let content = UIView()
- content.addConstrainedSubviews([titleLabel, infoButton, detailTitleLabel]) {
+ let infoButtonConstraint = infoButton.trailingAnchor.constraint(
+ greaterThanOrEqualTo: mainContentContainer.trailingAnchor
+ )
+ infoButtonConstraint.priority = .defaultLow
+
+ mainContentContainer.addConstrainedSubviews([titleLabel, infoButton, detailTitleLabel]) {
switch style {
case .subtitle:
titleLabel.pinEdgesToSuperview(.init([.top(0), .leading(0)]))
- detailTitleLabel.pinEdgesToSuperview(.all().excluding(.top))
- detailTitleLabel.topAnchor.constraint(equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1)
- infoButton.trailingAnchor.constraint(greaterThanOrEqualTo: content.trailingAnchor)
+ detailTitleLabel.pinEdgesToSuperview(.all().excluding([.top, .trailing]))
+ detailTitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor)
+ infoButtonConstraint
default:
titleLabel.pinEdgesToSuperview(.all().excluding(.trailing))
@@ -122,8 +131,6 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
detailTitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: infoButton.trailingAnchor)
}
- infoButton.pinEdgesToSuperview(.init([.top(0)]))
- infoButton.bottomAnchor.constraint(lessThanOrEqualTo: content.bottomAnchor)
infoButton.leadingAnchor.constraint(
equalTo: titleLabel.trailingAnchor,
constant: -UIMetrics.interButtonSpacing
@@ -132,10 +139,17 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
infoButton.widthAnchor.constraint(equalToConstant: buttonAreaWidth)
}
- contentContainer.addArrangedSubview(content)
+ contentView.addConstrainedSubviews([leftContentContainer, mainContentContainer, rightContentContainer]) {
+ mainContentContainer.pinEdgesToSuperviewMargins(.all().excluding([.leading, .trailing]))
- contentView.addConstrainedSubviews([contentContainer]) {
- contentContainer.pinEdgesToSuperviewMargins()
+ leftContentContainer.pinEdgesToSuperviewMargins(.all().excluding(.trailing))
+ leftContentContainer.trailingAnchor.constraint(equalTo: mainContentContainer.leadingAnchor)
+
+ rightContentContainer.pinEdgesToSuperview(.all().excluding(.leading))
+ rightContentContainer.leadingAnchor.constraint(equalTo: mainContentContainer.trailingAnchor)
+ rightContentContainer.widthAnchor.constraint(
+ greaterThanOrEqualToConstant: UIMetrics.TableView.cellIndentationWidth
+ )
}
}
@@ -147,7 +161,8 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
super.prepareForReuse()
infoButton.isHidden = true
- removeLeftView()
+ removeLeadingView()
+ removeTrailingView()
setLayoutMargins()
}
@@ -156,20 +171,22 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
backgroundView?.backgroundColor = UIColor.Cell.Background.indentationLevelOne
}
- func setLeftView(_ view: UIView, spacing: CGFloat) {
- removeLeftView()
+ func setLeadingView(superviewProvider: (UIView) -> Void) {
+ removeLeadingView()
+ superviewProvider(leftContentContainer)
+ }
- if contentContainer.arrangedSubviews.count <= 1 {
- contentContainer.insertArrangedSubview(view, at: 0)
- }
+ func removeLeadingView() {
+ leftContentContainer.subviews.forEach { $0.removeFromSuperview() }
+ }
- contentContainer.spacing = spacing
+ func setTrailingView(superviewProvider: (UIView) -> Void) {
+ removeTrailingView()
+ superviewProvider(rightContentContainer)
}
- func removeLeftView() {
- if contentContainer.arrangedSubviews.count >= contentContainerSubviewMaxCount {
- contentContainer.arrangedSubviews.first?.removeFromSuperview()
- }
+ func removeTrailingView() {
+ rightContentContainer.subviews.forEach { $0.removeFromSuperview() }
}
@objc private func handleInfoButton(_ sender: UIControl) {
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
index 639fa2d17b..b8c0c7c098 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
@@ -191,7 +191,7 @@ final class CustomDNSCellFactory: CellFactoryProtocol {
comment: ""
)
cell.accessibilityIdentifier = .dnsSettingsAddServerCell
- cell.action = { [weak self] in
+ cell.tapAction = { [weak self] in
self?.delegate?.addDNSEntry()
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
index 16d6ff24a5..51038d4b68 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
@@ -11,6 +11,7 @@ import UIKit
protocol VPNSettingsCellEventHandler {
func showInfo(for button: VPNSettingsInfoButtonItem)
+ func showDetails(for button: VPNSettingsDetailsButtonItem)
func addCustomPort(_ port: UInt16)
func selectCustomPortEntry(_ port: UInt16) -> Bool
func selectObfuscationState(_ state: WireGuardObfuscationState)
@@ -69,7 +70,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
guard let cell = cell as? SelectableSettingsCell else { return }
var portString = NSLocalizedString(
- "WIRE_GUARD_PORT_CELL_LABEL",
+ "WIREGUARD_PORT_CELL_LABEL",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
@@ -86,13 +87,13 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
guard let cell = cell as? SettingsInputCell else { return }
cell.titleLabel.text = NSLocalizedString(
- "WIRE_GUARD_CUSTOM_PORT_CELL_LABEL",
+ "WIREGUARD_CUSTOM_PORT_CELL_LABEL",
tableName: "VPNSettings",
value: "Custom",
comment: ""
)
cell.textField.placeholder = NSLocalizedString(
- "WIRE_GUARD_CUSTOM_PORT_CELL_INPUT_PLACEHOLDER",
+ "WIREGUARD_CUSTOM_PORT_CELL_INPUT_PLACEHOLDER",
tableName: "VPNSettings",
value: "Port",
comment: ""
@@ -127,7 +128,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
guard let cell = cell as? SelectableSettingsCell else { return }
cell.titleLabel.text = NSLocalizedString(
- "WIRE_GUARD_OBFUSCATION_AUTOMATIC_LABEL",
+ "WIREGUARD_OBFUSCATION_AUTOMATIC_LABEL",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
@@ -135,22 +136,59 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()
- case .wireGuardObfuscationOn:
- guard let cell = cell as? SelectableSettingsCell else { return }
+ case .wireGuardObfuscationUdpOverTcp:
+ guard let cell = cell as? SelectableSettingsDetailsCell else { return }
+
+ cell.titleLabel.text = NSLocalizedString(
+ "WIREGUARD_OBFUSCATION_UDP_TCP_LABEL",
+ tableName: "VPNSettings",
+ value: "UDP-over-TCP",
+ comment: ""
+ )
+ #if DEBUG
+ cell.detailTitleLabel.text = String(format: NSLocalizedString(
+ "WIREGUARD_OBFUSCATION_UDP_TCP_PORT",
+ tableName: "VPNSettings",
+ value: "Port: %d",
+ comment: ""
+ ), viewModel.obfuscationPort.portValue)
+ #endif
+ cell.accessibilityIdentifier = item.accessibilityIdentifier
+ cell.applySubCellStyling()
+
+ cell.buttonAction = { [weak self] in
+ self?.delegate?.showDetails(for: .udpOverTcp)
+ }
+
+ case .wireGuardObfuscationShadowsocks:
+ guard let cell = cell as? SelectableSettingsDetailsCell else { return }
cell.titleLabel.text = NSLocalizedString(
- "WIRE_GUARD_OBFUSCATION_ON_LABEL",
+ "WIREGUARD_OBFUSCATION_SHADOWSOCKS_LABEL",
tableName: "VPNSettings",
- value: "On (UDP-over-TCP)",
+ value: "Shadowsocks",
comment: ""
)
+ #if DEBUG
+ cell.detailTitleLabel.text = String(format: NSLocalizedString(
+ "WIREGUARD_OBFUSCATION_SHADOWSOCKS_PORT",
+ tableName: "VPNSettings",
+ value: "Port: %d",
+ comment: ""
+ ), viewModel.obfuscationPort.portValue)
+ #endif
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()
+
+ cell.buttonAction = { [weak self] in
+ self?.delegate?.showDetails(for: .wireguardOverShadowsocks)
+ }
+
case .wireGuardObfuscationOff:
guard let cell = cell as? SelectableSettingsCell else { return }
cell.titleLabel.text = NSLocalizedString(
- "WIRE_GUARD_OBFUSCATION_OFF_LABEL",
+ "WIREGUARD_OBFUSCATION_OFF_LABEL",
tableName: "VPNSettings",
value: "Off",
comment: ""
@@ -163,7 +201,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
let portString = port == 0 ? "Automatic" : "\(port)"
cell.titleLabel.text = NSLocalizedString(
- "WIRE_GUARD_OBFUSCATION_PORT_LABEL",
+ "WIREGUARD_OBFUSCATION_PORT_LABEL",
tableName: "VPNSettings",
value: portString,
comment: ""
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index 2aa5f6dd56..5c0f8856d3 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -21,6 +21,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardPort
case wireGuardCustomPort
case wireGuardObfuscation
+ case wireGuardObfuscationOption
case wireGuardObfuscationPort
case quantumResistance
case multihop
@@ -35,6 +36,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return SelectableSettingsCell.self
case .wireGuardCustomPort:
return SettingsInputCell.self
+ case .wireGuardObfuscationOption:
+ return SelectableSettingsDetailsCell.self
case .wireGuardObfuscation:
return SelectableSettingsCell.self
case .wireGuardObfuscationPort:
@@ -73,7 +76,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardPort(_ port: UInt16?)
case wireGuardCustomPort
case wireGuardObfuscationAutomatic
- case wireGuardObfuscationOn
+ case wireGuardObfuscationUdpOverTcp
+ case wireGuardObfuscationShadowsocks
case wireGuardObfuscationOff
case wireGuardObfuscationPort(_ port: UInt16)
case quantumResistanceAutomatic
@@ -89,11 +93,21 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}
static var wireGuardObfuscation: [Item] {
- [.wireGuardObfuscationAutomatic, .wireGuardObfuscationOn, wireGuardObfuscationOff]
+ var items: [Item] = [
+ .wireGuardObfuscationAutomatic,
+ .wireGuardObfuscationUdpOverTcp,
+ .wireGuardObfuscationOff,
+ ]
+
+ #if DEBUG
+ items.insert(.wireGuardObfuscationShadowsocks, at: 1)
+ #endif
+
+ return items
}
static var wireGuardObfuscationPort: [Item] {
- [.wireGuardObfuscationPort(0), wireGuardObfuscationPort(80), wireGuardObfuscationPort(5001)]
+ [.wireGuardObfuscationPort(0), .wireGuardObfuscationPort(80), .wireGuardObfuscationPort(5001)]
}
static var quantumResistance: [Item] {
@@ -112,8 +126,10 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return .wireGuardCustomPort
case .wireGuardObfuscationAutomatic:
return .wireGuardObfuscationAutomatic
- case .wireGuardObfuscationOn:
- return .wireGuardObfuscationOn
+ case .wireGuardObfuscationUdpOverTcp:
+ return .wireGuardObfuscationUdpOverTcp
+ case .wireGuardObfuscationShadowsocks:
+ return .wireGuardObfuscationShadowsocks
case .wireGuardObfuscationOff:
return .wireGuardObfuscationOff
case .wireGuardObfuscationPort:
@@ -139,8 +155,10 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
return .wireGuardPort
case .wireGuardCustomPort:
return .wireGuardCustomPort
- case .wireGuardObfuscationAutomatic, .wireGuardObfuscationOn, .wireGuardObfuscationOff:
+ case .wireGuardObfuscationAutomatic, .wireGuardObfuscationOff:
return .wireGuardObfuscation
+ case .wireGuardObfuscationUdpOverTcp, .wireGuardObfuscationShadowsocks:
+ return .wireGuardObfuscationOption
case .wireGuardObfuscationPort:
return .wireGuardObfuscationPort
case .quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff:
@@ -174,8 +192,9 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
let obfuscationStateItem: Item = switch viewModel.obfuscationState {
case .automatic: .wireGuardObfuscationAutomatic
case .off: .wireGuardObfuscationOff
- case .on: .wireGuardObfuscationOn
+ case .on: .wireGuardObfuscationUdpOverTcp
}
+
let quantumResistanceItem: Item = switch viewModel.quantumResistance {
case .automatic: .quantumResistanceAutomatic
case .off: .quantumResistanceOff
@@ -288,9 +307,14 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .wireGuardObfuscationAutomatic:
selectObfuscationState(.automatic)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
- case .wireGuardObfuscationOn:
+ case .wireGuardObfuscationUdpOverTcp:
selectObfuscationState(.on)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
+ // TODO: When ready, add implementation for selected obfuscation.
+ case .wireGuardObfuscationShadowsocks:
+ selectObfuscationState(.on)
+ delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
+ // TODO: When ready, add implementation for selected obfuscation.
case .wireGuardObfuscationOff:
selectObfuscationState(.off)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
@@ -445,7 +469,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
private func configureWireguardPortsHeader(_ header: SettingsHeaderView) {
let title = NSLocalizedString(
- "WIRE_GUARD_PORTS_HEADER_LABEL",
+ "WIREGUARD_PORTS_HEADER_LABEL",
tableName: "VPNSettings",
value: "WireGuard ports",
comment: ""
@@ -610,6 +634,10 @@ extension VPNSettingsDataSource: VPNSettingsCellEventHandler {
delegate?.showInfo(for: button)
}
+ func showDetails(for button: VPNSettingsDetailsButtonItem) {
+ delegate?.showDetails(for: button)
+ }
+
func addCustomPort(_ port: UInt16) {
viewModel.setWireGuardPort(port)
delegate?.didSelectWireGuardPort(port)
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift
index de1a602fce..98695d1f7f 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSourceDelegate.swift
@@ -17,6 +17,7 @@ protocol DNSSettingsDataSourceDelegate: AnyObject {
protocol VPNSettingsDataSourceDelegate: AnyObject {
func didUpdateTunnelSettings(_ update: TunnelSettingsUpdate)
func showInfo(for: VPNSettingsInfoButtonItem)
+ func showDetails(for: VPNSettingsDetailsButtonItem)
func showDNSSettings()
func showIPOverrides()
func didSelectWireGuardPort(_ port: UInt16?)
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDetailsButtonItem.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDetailsButtonItem.swift
new file mode 100644
index 0000000000..1dc3121bef
--- /dev/null
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDetailsButtonItem.swift
@@ -0,0 +1,12 @@
+//
+// VPNSettingsDetailsButtonItem.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2024-10-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+enum VPNSettingsDetailsButtonItem {
+ case udpOverTcp
+ case wireguardOverShadowsocks
+}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
index ac57e39db1..472802d11e 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
@@ -45,7 +45,7 @@ enum VPNSettingsInfoButtonItem: CustomStringConvertible {
case let .wireGuardPorts(portsString):
String(
format: NSLocalizedString(
- "VPN_SETTINGS_WIRE_GUARD_PORTS_GENERAL",
+ "VPN_SETTINGS_WIREGUARD_PORTS_GENERAL",
tableName: "WireGuardPorts",
value: """
The automatic setting will randomly choose from the valid port ranges shown below.
@@ -58,7 +58,7 @@ enum VPNSettingsInfoButtonItem: CustomStringConvertible {
)
case .wireGuardObfuscation:
NSLocalizedString(
- "VPN_SETTINGS_WIRE_GUARD_OBFUSCATION_GENERAL",
+ "VPN_SETTINGS_WIREGUARD_OBFUSCATION_GENERAL",
tableName: "WireGuardObfuscation",
value: """
Obfuscation hides the WireGuard traffic inside another protocol. \
@@ -69,7 +69,7 @@ enum VPNSettingsInfoButtonItem: CustomStringConvertible {
)
case .wireGuardObfuscationPort:
NSLocalizedString(
- "VPN_SETTINGS_WIRE_GUARD_OBFUSCATION_PORT_GENERAL",
+ "VPN_SETTINGS_WIREGUARD_OBFUSCATION_PORT_GENERAL",
tableName: "WireGuardObfuscation",
value: "Which TCP port the UDP-over-TCP obfuscation protocol should connect to on the VPN server.",
comment: ""
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
index c86db1b9d0..10a519a737 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
@@ -111,6 +111,11 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
alertPresenter.showAlert(presentation: presentation, animated: true)
}
+ func showDetails(for: VPNSettingsDetailsButtonItem) {
+ // TODO: When ready, add navigation to detail views for selecting obfuscation options for
+ // UDP-over-TCP and shadowsocks.
+ }
+
func showDNSSettings() {
let viewController = CustomDNSViewController(interactor: interactor, alertPresenter: alertPresenter)
navigationController?.pushViewController(viewController, animated: true)
diff --git a/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift b/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
index 2b2f8bcfe4..570f32ab08 100644
--- a/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
+++ b/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
@@ -103,7 +103,7 @@ class VPNSettingsPage: Page {
}
@discardableResult func tapWireGuardObfuscationOnCell() -> Self {
- app.cells[AccessibilityIdentifier.wireGuardObfuscationOn].tap()
+ app.cells[AccessibilityIdentifier.wireGuardObfuscationUdpOverTcp].tap()
return self
}
@@ -157,7 +157,7 @@ class VPNSettingsPage: Page {
}
@discardableResult func verifyWireGuardObfuscationOnSelected() -> Self {
- let onCell = app.cells[AccessibilityIdentifier.wireGuardObfuscationOn]
+ let onCell = app.cells[AccessibilityIdentifier.wireGuardObfuscationUdpOverTcp]
XCTAssertTrue(onCell.isSelected)
return self
}