Skip to content

Commit 872cac1

Browse files
committed
Refactor RelayFilterDataSource
1 parent 8cc5623 commit 872cac1

15 files changed

+452
-380
lines changed

ios/MullvadREST/Relay/RelayCandidates.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
77
//
88

9-
public struct RelayCandidates {
9+
public struct RelayCandidates: Equatable {
1010
public let entryRelays: [RelayWithLocation<REST.ServerRelay>]?
1111
public let exitRelays: [RelayWithLocation<REST.ServerRelay>]
1212
public init(

ios/MullvadREST/Relay/RelaySelector.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public enum RelaySelector {
1515
// MARK: - public
1616

1717
/// Determines whether a `REST.ServerRelay` satisfies the given relay filter.
18-
static func relayMatchesFilter(_ relay: AnyRelay, filter: RelayFilter) -> Bool {
18+
public static func relayMatchesFilter(_ relay: AnyRelay, filter: RelayFilter) -> Bool {
1919
if case let .only(providers) = filter.providers, providers.contains(relay.provider) == false {
2020
return false
2121
}

ios/MullvadREST/Relay/RelayWithLocation.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@ public struct RelayWithLocation<T: AnyRelay> {
6060
}
6161
}
6262

63-
extension RelayWithLocation: Equatable {
63+
extension RelayWithLocation: Hashable {
6464
public static func == (lhs: RelayWithLocation<T>, rhs: RelayWithLocation<T>) -> Bool {
6565
lhs.relay.hostname == rhs.relay.hostname
6666
}
67+
68+
public func hash(into hasher: inout Hasher) {
69+
hasher.combine(relay.hostname)
70+
}
6771
}

ios/MullvadTypes/RelayConstraint.swift

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import Foundation
1010

1111
private let anyConstraint = "any"
1212

13-
public enum RelayConstraint<T>: Codable, Equatable,
14-
CustomDebugStringConvertible where T: Codable & Equatable {
13+
public enum RelayConstraint<T>: Codable, Equatable, CustomDebugStringConvertible, Sendable
14+
where T: Codable & Equatable & Sendable {
1515
case any
1616
case only(T)
1717

@@ -34,7 +34,7 @@ public enum RelayConstraint<T>: Codable, Equatable,
3434
return output
3535
}
3636

37-
private struct OnlyRepr: Codable {
37+
private struct OnlyRepr: Codable, Sendable {
3838
var only: T
3939
}
4040

@@ -46,7 +46,6 @@ public enum RelayConstraint<T>: Codable, Equatable,
4646
self = .any
4747
} else {
4848
let onlyVariant = try container.decode(OnlyRepr.self)
49-
5049
self = .only(onlyVariant.only)
5150
}
5251
}

ios/MullvadTypes/RelayFilter.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
import Foundation
1010

11-
public struct RelayFilter: Codable, Equatable {
12-
public enum Ownership: Codable {
11+
public struct RelayFilter: Codable, Equatable, Sendable {
12+
public enum Ownership: Codable, Sendable {
1313
case any
1414
case owned
1515
case rented

ios/MullvadVPN.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@
933933
F0164EBE2B4BFF940020268D /* ShadowsocksLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */; };
934934
F0164EC32B4C49D30020268D /* ShadowsocksLoaderStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */; };
935935
F0164ED12B4F2DCB0020268D /* AccessMethodIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */; };
936+
F017F8E02D78AC020076EC01 /* RelayFilterDataSourceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F017F8DF2D78ABE90076EC01 /* RelayFilterDataSourceItem.swift */; };
936937
F01DAE332C2B032A00521E46 /* RelaySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01DAE322C2B032A00521E46 /* RelaySelection.swift */; };
937938
F022EBA62CF0C6AE009484B9 /* ConsolidatedApplicationLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */; };
938939
F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */; };
@@ -2356,6 +2357,7 @@
23562357
F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoader.swift; sourceTree = "<group>"; };
23572358
F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderStub.swift; sourceTree = "<group>"; };
23582359
F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodIterator.swift; sourceTree = "<group>"; };
2360+
F017F8DF2D78ABE90076EC01 /* RelayFilterDataSourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterDataSourceItem.swift; sourceTree = "<group>"; };
23592361
F01DAE322C2B032A00521E46 /* RelaySelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelaySelection.swift; sourceTree = "<group>"; };
23602362
F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherViewController.swift; sourceTree = "<group>"; };
23612363
F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCreditSucceededViewController.swift; sourceTree = "<group>"; };
@@ -4329,6 +4331,7 @@
43294331
F0B583D32D6DCE0D007F5AE4 /* FilterDescriptor.swift */,
43304332
7A1A26482A29D48A00B978AA /* RelayFilterCellFactory.swift */,
43314333
7A1A26462A29CF0800B978AA /* RelayFilterDataSource.swift */,
4334+
F017F8DF2D78ABE90076EC01 /* RelayFilterDataSourceItem.swift */,
43324335
7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */,
43334336
7A1A26442A29CEF700B978AA /* RelayFilterViewController.swift */,
43344337
7AF9BE8D2A331C7B00DBFEDB /* RelayFilterViewModel.swift */,
@@ -6395,6 +6398,7 @@
63956398
7A5869B92B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift in Sources */,
63966399
586C0D7A2B039CE300E7CDD7 /* ShadowsocksCipherPicker.swift in Sources */,
63976400
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */,
6401+
F017F8E02D78AC020076EC01 /* RelayFilterDataSourceItem.swift in Sources */,
63986402
586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */,
63996403
58B26E262943522400D5980C /* NotificationProvider.swift in Sources */,
64006404
58CE5E64224146200008646E /* AppDelegate.swift in Sources */,

ios/MullvadVPN/View controllers/RelayFilter/FilterDescriptor.swift

+4-11
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ struct FilterDescriptor {
1515
var isEnabled: Bool {
1616
let exitCount = relayFilterResult.exitRelays.count
1717
let entryCount = relayFilterResult.entryRelays?.count ?? 0
18-
let totalcount = exitCount + entryCount
19-
let isMultihopEnabled = settings.tunnelMultihopState.isEnabled
20-
return (isMultihopEnabled && totalcount > 1) || (!isMultihopEnabled && totalcount > 0)
18+
let isMultihopEnabled = settings.tunnelMultihopState.isEnabled || settings.daita.isAutomaticRouting
19+
return (isMultihopEnabled && entryCount > 1 && exitCount > 1) || (!isMultihopEnabled && exitCount > 0)
2120
}
2221

2322
var title: String {
@@ -73,13 +72,7 @@ struct FilterDescriptor {
7372
number > 100 ? "99+" : "\(number)"
7473
}
7574

76-
if isMultihopEnabled && isDirectOnly {
77-
return String(
78-
format: "Show %@ entry & %@ exit servers",
79-
displayNumber(entryCount),
80-
displayNumber(exitCount)
81-
)
82-
}
83-
return String(format: "Show %@ servers", displayNumber(exitCount))
75+
let numberOfRelays = Set(relayFilterResult.entryRelays ?? []).union(relayFilterResult.exitRelays).count
76+
return String(format: "Show %@ servers", displayNumber(numberOfRelays))
8477
}
8578
}

ios/MullvadVPN/View controllers/RelayFilter/RelayFilterCellFactory.swift

+35-40
Original file line numberDiff line numberDiff line change
@@ -13,78 +13,73 @@ struct RelayFilterCellFactory: @preconcurrency CellFactoryProtocol {
1313
let tableView: UITableView
1414

1515
func makeCell(for item: RelayFilterDataSource.Item, indexPath: IndexPath) -> UITableViewCell {
16-
let cell = tableView.dequeueReusableCell(withIdentifier: item.reuseIdentifier.rawValue, for: indexPath)
16+
let cell = tableView.dequeueReusableCell(
17+
withIdentifier: RelayFilterDataSource.CellReuseIdentifiers.allCases[indexPath.section].rawValue,
18+
for: indexPath
19+
)
1720
configureCell(cell, item: item, indexPath: indexPath)
1821

1922
return cell
2023
}
2124

2225
func configureCell(_ cell: UITableViewCell, item: RelayFilterDataSource.Item, indexPath: IndexPath) {
23-
switch item {
26+
switch item.type {
2427
case .ownershipAny, .ownershipOwned, .ownershipRented:
25-
configureOwnershipCell(cell, item: item)
28+
configureOwnershipCell(cell as? SelectableSettingsCell, item: item)
2629
case .allProviders, .provider:
27-
configureProviderCell(cell, item: item)
30+
configureProviderCell(cell as? CheckableSettingsCell, item: item)
2831
}
2932
}
3033

31-
private func configureOwnershipCell(_ cell: UITableViewCell, item: RelayFilterDataSource.Item) {
32-
guard let cell = cell as? SelectableSettingsCell else { return }
33-
34-
var title = ""
35-
switch item {
36-
case .ownershipAny:
37-
title = "Any"
38-
cell.setAccessibilityIdentifier(.ownershipAnyCell)
39-
case .ownershipOwned:
40-
title = "Mullvad owned only"
41-
cell.setAccessibilityIdentifier(.ownershipMullvadOwnedCell)
42-
case .ownershipRented:
43-
title = "Rented only"
44-
cell.setAccessibilityIdentifier(.ownershipRentedCell)
45-
default:
46-
assertionFailure("Item mismatch. Got: \(item)")
47-
}
34+
private func configureOwnershipCell(_ cell: SelectableSettingsCell?, item: RelayFilterDataSource.Item) {
35+
guard let cell = cell else { return }
4836

4937
cell.titleLabel.text = NSLocalizedString(
5038
"RELAY_FILTER_CELL_LABEL",
5139
tableName: "Relay filter ownership cell",
52-
value: title,
40+
value: item.name,
5341
comment: ""
5442
)
5543

44+
let accessibilityIdentifier: AccessibilityIdentifier
45+
switch item.type {
46+
case .ownershipAny:
47+
accessibilityIdentifier = .ownershipAnyCell
48+
case .ownershipOwned:
49+
accessibilityIdentifier = .ownershipMullvadOwnedCell
50+
case .ownershipRented:
51+
accessibilityIdentifier = .ownershipRentedCell
52+
default:
53+
assertionFailure("Unexpected ownership item: \(item)")
54+
return
55+
}
56+
57+
cell.setAccessibilityIdentifier(accessibilityIdentifier)
5658
cell.applySubCellStyling()
5759
}
5860

59-
private func configureProviderCell(_ cell: UITableViewCell, item: RelayFilterDataSource.Item) {
60-
guard let cell = cell as? CheckableSettingsCell else { return }
61-
62-
let title: String
63-
64-
switch item {
65-
case .allProviders:
66-
title = "All providers"
67-
setFontWeight(.semibold, to: cell.titleLabel)
68-
case let .provider(name):
69-
title = name
70-
setFontWeight(.regular, to: cell.titleLabel)
71-
default:
72-
title = ""
73-
assertionFailure("Item mismatch. Got: \(item)")
74-
}
61+
private func configureProviderCell(_ cell: CheckableSettingsCell?, item: RelayFilterDataSource.Item) {
62+
guard let cell = cell else { return }
7563

7664
cell.titleLabel.text = NSLocalizedString(
7765
"RELAY_FILTER_CELL_LABEL",
7866
tableName: "Relay filter provider cell",
79-
value: title,
67+
value: item.name,
8068
comment: ""
8169
)
8270

71+
if item.type == .allProviders {
72+
setFontWeight(.semibold, to: cell.titleLabel)
73+
} else {
74+
setFontWeight(.regular, to: cell.titleLabel)
75+
}
76+
8377
cell.applySubCellStyling()
8478
cell.setAccessibilityIdentifier(.relayFilterProviderCell)
79+
cell.isEnabled = item.isEnabled
8580
}
8681

8782
private func setFontWeight(_ weight: UIFont.Weight, to label: UILabel) {
88-
label.font = UIFont.systemFont(ofSize: label.font.pointSize, weight: .semibold)
83+
label.font = UIFont.systemFont(ofSize: label.font.pointSize, weight: weight)
8984
}
9085
}

0 commit comments

Comments
 (0)