-
Notifications
You must be signed in to change notification settings - Fork 384
/
Copy pathLocationCellViewModel.swift
112 lines (93 loc) · 3.89 KB
/
LocationCellViewModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//
// LocationCellViewModel.swift
// MullvadVPN
//
// Created by Mojgan on 2024-02-05.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
import MullvadTypes
struct LocationCellViewModel: Hashable {
let section: LocationSection
let node: LocationNode
var indentationLevel = 0
var isSelected = false
var excludedRelayTitle: String?
func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(node.children.count)
hasher.combine(section)
hasher.combine(isSelected)
}
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.node == rhs.node &&
lhs.node.children.count == rhs.node.children.count &&
lhs.section == rhs.section &&
lhs.isSelected == rhs.isSelected
}
}
extension [LocationCellViewModel] {
mutating func addSubNodes(from item: LocationCellViewModel, at indexPath: IndexPath) {
let section = LocationSection.allCases[indexPath.section]
let row = indexPath.row + 1
let locations = item.node.children.map {
LocationCellViewModel(
section: section,
node: $0,
indentationLevel: item.indentationLevel + 1,
isSelected: false
)
}
if row < count {
insert(contentsOf: locations, at: row)
} else {
append(contentsOf: locations)
}
}
mutating func removeSubNodes(from node: LocationNode) {
for node in node.children {
node.showsChildren = false
removeAll(where: { node == $0.node })
removeSubNodes(from: node)
}
}
}
extension LocationCellViewModel {
/* Exclusion of other locations in the same node tree as the currently excluded location
happens when there are no more hosts in that tree that can be selected.
We check this by doing the following, in order:
1. Count host names in the tree. More than one means that there are other locations than
the excluded one for the relay selector to choose from. No exlusion.
2. Count host names in the excluded node. More than one means that there are multiple
locations for the relay selector to choose from. No exlusion.
3. Check existance of a location in the tree that match the currently excluded location.
No match means no exclusion.
*/
func shouldExcludeLocation(_ excludedLocation: LocationCellViewModel?) -> Bool {
guard let excludedLocation else {
return false
}
let proxyNode = RootLocationNode(children: [node])
let allLocations = Set(proxyNode.flattened.flatMap { $0.locations })
let hostCount = allLocations.filter { location in
if case .hostname = location { true } else { false }
}.count
// If the there are more than one selectable relay in the current node we don't need
// show this in the location tree and can return early.
guard hostCount == 1 else { return false }
let proxyExcludedNode = RootLocationNode(children: [excludedLocation.node])
let allExcludedLocations = Set(proxyExcludedNode.flattened.flatMap { $0.locations })
let excludedHostCount = allExcludedLocations.filter { location in
if case .hostname = location { true } else { false }
}.count
// If the there are more than one selectable relay in the excluded node we don't need
// show this in the location tree and can return early.
guard excludedHostCount == 1 else { return false }
var containsExcludedLocation = false
if allLocations.contains(where: { allExcludedLocations.contains($0) }) {
containsExcludedLocation = true
}
// If the tree doesn't contain the excluded node we do nothing, otherwise the
// required conditions have now all been met.
return containsExcludedLocation
}
}