6
6
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7
7
//
8
8
9
- import Foundation
10
9
import MullvadSettings
11
10
import MullvadTypes
12
11
import UIKit
13
12
14
- class AddLocationsDataSource : UITableViewDiffableDataSource < LocationSection , LocationCellViewModel > {
15
- private let tableView : UITableView
13
+ class AddLocationsDataSource :
14
+ UITableViewDiffableDataSource < LocationSection , LocationCellViewModel > ,
15
+ LocationDiffableDataSourceProtocol
16
+ {
16
17
private let nodes : [ LocationNode ]
17
- private var customListLocationNode : CustomListLocationNode
18
- var didUpdateCustomList : ( ( CustomListLocationNode ) -> Void ) ?
18
+ private var selectedLocations : [ RelayLocation ]
19
+ var didUpdateLocations : ( ( [ RelayLocation ] ) -> Void ) ?
20
+ let tableView : UITableView
21
+ let sections : [ LocationSection ]
19
22
20
23
init (
21
24
tableView: UITableView ,
22
- allLocations : [ LocationNode ] ,
23
- customList : CustomList
25
+ allLocationNodes : [ LocationNode ] ,
26
+ selectedLocations : [ RelayLocation ]
24
27
) {
25
28
self . tableView = tableView
26
- self . nodes = allLocations
29
+ self . nodes = allLocationNodes
30
+ self . selectedLocations = selectedLocations
27
31
28
- self . customListLocationNode = CustomListLocationNodeBuilder (
29
- customList: customList,
30
- allLocations: self . nodes
31
- ) . customListLocationNode
32
+ let sections : [ LocationSection ] = [ . customLists]
33
+ self . sections = sections
32
34
33
35
super. init ( tableView: tableView) { _, indexPath, itemIdentifier in
34
36
let cell = tableView. dequeueReusableView (
35
- withIdentifier: LocationSection . allCases [ indexPath. section] ,
37
+ withIdentifier: sections [ indexPath. section] ,
36
38
for: indexPath
37
- // swiftlint:disable:next force_cast
38
- ) as! LocationCell
39
+ ) as! LocationCell // swiftlint:disable:this force_cast
39
40
cell. configure ( item: itemIdentifier, behavior: . add)
40
41
cell. selectionStyle = . none
41
42
return cell
@@ -48,86 +49,35 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<LocationSection, Loc
48
49
}
49
50
50
51
private func reloadWithSelectedLocations( ) {
51
- var locationsList : [ LocationCellViewModel ] = [ ]
52
- nodes. forEach { node in
53
- let viewModel = LocationCellViewModel (
54
- section: . customLists,
55
- node: node,
56
- isSelected: customListLocationNode. children. contains ( node)
57
- )
58
- locationsList. append ( viewModel)
59
-
60
- // Determine if the node should be expanded.
61
- guard isLocationInCustomList ( node: node) else {
62
- return
63
- }
64
-
65
- // Walk tree backwards to determine which nodes should be expanded.
66
- node. forEachAncestor { node in
67
- node. showsChildren = true
52
+ var items = nodes. flatMap { node in
53
+ // Create a "faux" root node to use for constructing a node tree.
54
+ let rootNode = RootLocationNode ( children: [ node] )
55
+
56
+ // Only parents with partially selected children should be expanded.
57
+ node. forEachDescendant { descendantNode in
58
+ if selectedLocations. containsAny ( locations: descendantNode. locations) {
59
+ descendantNode. parent? . showsChildren = true
60
+ }
68
61
}
69
62
70
- locationsList. append ( contentsOf: recursivelyCreateCellViewModelTree (
71
- for: node,
63
+ // Construct node tree.
64
+ return recursivelyCreateCellViewModelTree (
65
+ for: rootNode,
72
66
in: . customLists,
73
- indentationLevel: 1
74
- ) )
75
- }
76
- updateDataSnapshot ( with: locationsList)
77
- }
78
-
79
- private func updateDataSnapshot(
80
- with list: [ LocationCellViewModel ] ,
81
- animated: Bool = false ,
82
- completion: ( ( ) -> Void ) ? = nil
83
- ) {
84
- var snapshot = NSDiffableDataSourceSnapshot < LocationSection , LocationCellViewModel > ( )
85
-
86
- snapshot. appendSections ( [ . customLists] )
87
- snapshot. appendItems ( list, toSection: . customLists)
88
-
89
- apply ( snapshot, animatingDifferences: animated, completion: completion)
90
- }
91
-
92
- private func recursivelyCreateCellViewModelTree(
93
- for node: LocationNode ,
94
- in section: LocationSection ,
95
- indentationLevel: Int
96
- ) -> [ LocationCellViewModel ] {
97
- var viewModels = [ LocationCellViewModel] ( )
98
- for childNode in node. children {
99
- viewModels. append (
100
- LocationCellViewModel (
101
- section: . customLists,
102
- node: childNode,
103
- indentationLevel: indentationLevel,
104
- isSelected: customListLocationNode. children. contains ( childNode)
105
- )
67
+ indentationLevel: 0
106
68
)
69
+ }
107
70
108
- let indentationLevel = indentationLevel + 1
109
-
110
- // Walk tree forward to determine which nodes should be expanded.
111
- if isLocationInCustomList ( node: childNode) {
112
- viewModels. append (
113
- contentsOf: recursivelyCreateCellViewModelTree (
114
- for: childNode,
115
- in: section,
116
- indentationLevel: indentationLevel
117
- )
118
- )
71
+ // Apply selection to node tree.
72
+ items = items. map { item in
73
+ var item = item
74
+ if selectedLocations. containsAny ( locations: item. node. locations) {
75
+ item. isSelected = true
119
76
}
77
+ return item
120
78
}
121
79
122
- return viewModels
123
- }
124
-
125
- private func isLocationInCustomList( node: LocationNode ) -> Bool {
126
- customListLocationNode. children. contains ( where: { containsChild ( parent: node, child: $0) } )
127
- }
128
-
129
- private func containsChild( parent: LocationNode , child: LocationNode ) -> Bool {
130
- parent. flattened. contains ( child)
80
+ updateDataSnapshot ( with: [ items] , reloadExisting: false )
131
81
}
132
82
133
83
override func tableView( _ tableView: UITableView , cellForRowAt indexPath: IndexPath ) -> UITableViewCell {
@@ -146,68 +96,40 @@ extension AddLocationsDataSource: UITableViewDelegate {
146
96
147
97
extension AddLocationsDataSource : LocationCellDelegate {
148
98
func toggleExpanding( cell: LocationCell ) {
149
- guard let indexPath = tableView. indexPath ( for: cell) ,
150
- let item = itemIdentifier ( for: indexPath) else { return }
151
- let isExpanded = item. node. showsChildren
152
-
153
- item. node. showsChildren = !isExpanded
154
-
155
- var locationList = snapshot ( ) . itemIdentifiers
156
-
157
- if !isExpanded {
158
- locationList. addSubNodes ( from: item, at: indexPath)
159
- } else {
160
- locationList. removeSubNodes ( from: item. node)
161
- }
162
-
163
- updateDataSnapshot ( with: locationList, animated: true , completion: {
164
- self . scroll ( to: item, animated: true )
165
- } )
99
+ toggle ( cell: cell)
166
100
}
167
101
168
- func toggleSelection ( cell: LocationCell ) {
102
+ func toggleSelecting ( cell: LocationCell ) {
169
103
guard let index = tableView. indexPath ( for: cell) ? . row else { return }
170
104
171
- var locationList = snapshot ( ) . itemIdentifiers
172
- let item = locationList [ index]
105
+ var items = snapshot ( ) . itemIdentifiers
106
+ let item = items [ index]
107
+
108
+ guard let nodeLocation = item. node. locations. first else { return }
109
+
173
110
let isSelected = !item. isSelected
174
- locationList [ index] . isSelected = isSelected
111
+ items [ index] . isSelected = isSelected
175
112
176
- locationList . deselectAncestors ( from: item. node)
177
- locationList . toggleSelectionSubNodes ( from: item. node, isSelected: isSelected)
113
+ items . deselectAncestors ( from: item. node)
114
+ items . toggleSelectionSubNodes ( from: item. node, isSelected: isSelected)
178
115
179
116
if isSelected {
180
- customListLocationNode . add ( selectedLocation : item . node )
117
+ selectedLocations . append ( nodeLocation )
181
118
} else {
182
- customListLocationNode . remove ( selectedLocation : item . node , with : locationList )
119
+ selectedLocations . removeAll { $0 == nodeLocation }
183
120
}
184
- updateDataSnapshot ( with: locationList, completion: {
185
- self . didUpdateCustomList ? ( self . customListLocationNode)
121
+
122
+ updateDataSnapshot ( with: [ items] , reloadExisting: true , completion: {
123
+ self . didUpdateLocations ? ( self . selectedLocations)
186
124
} )
187
125
}
188
126
}
189
127
190
- extension AddLocationsDataSource {
191
- private func scroll( to item: LocationCellViewModel , animated: Bool ) {
192
- guard
193
- let visibleIndexPaths = tableView. indexPathsForVisibleRows,
194
- let indexPath = indexPath ( for: item)
195
- else { return }
196
-
197
- if item. node. children. count > visibleIndexPaths. count {
198
- tableView. scrollToRow ( at: indexPath, at: . top, animated: animated)
199
- } else {
200
- if let last = item. node. children. last {
201
- if let lastInsertedIndexPath = self . indexPath ( for: LocationCellViewModel (
202
- section: . customLists,
203
- node: last
204
- ) ) ,
205
- let lastVisibleIndexPath = visibleIndexPaths. last,
206
- lastInsertedIndexPath >= lastVisibleIndexPath {
207
- tableView. scrollToRow ( at: lastInsertedIndexPath, at: . bottom, animated: animated)
208
- }
209
- }
210
- }
128
+ fileprivate extension [ RelayLocation ] {
129
+ func containsAny( locations: [ RelayLocation ] ) -> Bool {
130
+ locations. contains ( where: { location in
131
+ contains ( location)
132
+ } )
211
133
}
212
134
}
213
135
@@ -232,49 +154,3 @@ fileprivate extension [LocationCellViewModel] {
232
154
}
233
155
}
234
156
}
235
-
236
- // MARK: - Update custom list
237
-
238
- fileprivate extension CustomListLocationNode {
239
- func remove( selectedLocation: LocationNode , with locationList: [ LocationCellViewModel ] ) {
240
- if let index = children. firstIndex ( of: selectedLocation) {
241
- children. remove ( at: index)
242
- }
243
- removeAncestors ( node: selectedLocation)
244
- addSiblings ( from: locationList, for: selectedLocation)
245
- }
246
-
247
- func add( selectedLocation: LocationNode ) {
248
- children. append ( selectedLocation)
249
- removeSubNodes ( node: selectedLocation)
250
- }
251
-
252
- private func removeSubNodes( node: LocationNode ) {
253
- node. forEachDescendant { child in
254
- // removing children if they are already added to custom list
255
- if let index = children. firstIndex ( of: child) {
256
- children. remove ( at: index)
257
- }
258
- }
259
- }
260
-
261
- private func removeAncestors( node: LocationNode ) {
262
- node. forEachAncestor { parent in
263
- if let index = children. firstIndex ( of: parent) {
264
- children. remove ( at: index)
265
- }
266
- }
267
- }
268
-
269
- private func addSiblings( from locationList: [ LocationCellViewModel ] , for node: LocationNode ) {
270
- guard let parent = node. parent else { return }
271
- parent. children. forEach { child in
272
- // adding siblings if they are already selected in snapshot
273
- if let item = locationList. first ( where: { $0. node == child } ) ,
274
- item. isSelected && !children. contains ( child) {
275
- children. append ( child)
276
- }
277
- }
278
- addSiblings ( from: locationList, for: parent)
279
- }
280
- }
0 commit comments