-
Notifications
You must be signed in to change notification settings - Fork 384
/
Copy pathCustomListsDataSource.swift
105 lines (90 loc) · 3.79 KB
/
CustomListsDataSource.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
//
// CustomListsDataSource.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-02-22.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadREST
import MullvadSettings
import MullvadTypes
class CustomListsDataSource: LocationDataSourceProtocol {
private(set) var nodes = [LocationNode]()
private(set) var repository: CustomListRepositoryProtocol
init(repository: CustomListRepositoryProtocol) {
self.repository = repository
}
var searchableNodes: [LocationNode] {
nodes.flatMap { $0.children }
}
/// Constructs a collection of node trees by copying each matching counterpart
/// from the complete list of nodes created in ``AllLocationDataSource``.
func reload(allLocationNodes: [LocationNode], isFiltered: Bool) {
nodes = repository.fetchAll().compactMap { customList in
let listNode = CustomListLocationNode(
name: customList.name,
code: customList.name.lowercased(),
locations: customList.locations,
customList: customList
)
listNode.children = customList.locations.compactMap { location in
copy(location, from: allLocationNodes, withParent: listNode)
}
listNode.forEachDescendant { node in
// Each item in a section in a diffable data source needs to be unique.
// Since LocationCellViewModel partly depends on LocationNode.code for
// equality, each node code needs to be prefixed with the code of its
// parent custom list to uphold this.
node.code = LocationNode.combineNodeCodes([listNode.code, node.code])
}
return (isFiltered && listNode.children.isEmpty) ? nil : listNode
}
}
func node(by relays: UserSelectedRelays, for customList: CustomList) -> LocationNode? {
guard let listNode = nodes.first(where: { $0.name == customList.name }) else { return nil }
if relays.customListSelection?.isList == true {
return listNode
} else {
// Each search for descendant nodes needs the parent custom list node code to be
// prefixed in order to get a match. See comment in reload() above.
return switch relays.locations.first {
case let .country(countryCode):
listNode.descendantNodeFor(codes: [listNode.code, countryCode])
case let .city(countryCode, cityCode):
listNode.descendantNodeFor(codes: [listNode.code, countryCode, cityCode])
case let .hostname(_, _, hostCode):
listNode.descendantNodeFor(codes: [listNode.code, hostCode])
case .none:
nil
}
}
}
func customList(by id: UUID) -> CustomList? {
repository.fetch(by: id)
}
private func copy(
_ location: RelayLocation,
from allLocationNodes: [LocationNode],
withParent parentNode: LocationNode
) -> LocationNode? {
let rootNode = RootLocationNode(children: allLocationNodes)
return switch location {
case let .country(countryCode):
rootNode
.countryFor(code: countryCode)?
.copy(withParent: parentNode)
case let .city(countryCode, cityCode):
rootNode
.countryFor(code: countryCode)?
.cityFor(codes: [countryCode, cityCode])?
.copy(withParent: parentNode)
case let .hostname(countryCode, cityCode, hostCode):
rootNode
.countryFor(code: countryCode)?
.cityFor(codes: [countryCode, cityCode])?
.hostFor(code: hostCode)?
.copy(withParent: parentNode)
}
}
}