blob: 0848981922031ad9974e688d50ea8f0f21fd6622 (
plain)
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
|
//
// CustomListsDataSource.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-02-22.
// Copyright © 2025 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]) {
let expandedRelays = nodes.flatMap { [$0] + $0.flattened }.filter { $0.showsChildren }.map { $0.code }
nodes = repository.fetchAll().map { list in
let customListWrapper = CustomListLocationNodeBuilder(customList: list, allLocations: allLocationNodes)
let listNode = customListWrapper.customListLocationNode
listNode.showsChildren = expandedRelays.contains(listNode.code)
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])
node.showsChildren = expandedRelays.contains(node.code)
}
return 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)
}
}
|