Skip to content

Commit a6727ee

Browse files
author
mojganii
committed
refactoring location datasource
1 parent 32e4537 commit a6727ee

11 files changed

+608
-410
lines changed

ios/MullvadVPN.xcodeproj/project.pbxproj

+28
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,13 @@
770770
F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; };
771771
F04F95A12B21D24400431E08 /* shadowsocks.h in Headers */ = {isa = PBXBuildFile; fileRef = F04F95A02B21D24400431E08 /* shadowsocks.h */; settings = {ATTRIBUTES = (Private, ); }; };
772772
F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; };
773+
F050AE4C2B70D5A7003F4EDB /* SelectLocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE4B2B70D5A7003F4EDB /* SelectLocationNode.swift */; };
774+
F050AE4E2B70D7F8003F4EDB /* LocationCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */; };
775+
F050AE502B70DC4F003F4EDB /* SelectLocationNodeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE4F2B70DC4F003F4EDB /* SelectLocationNodeProtocol.swift */; };
776+
F050AE522B70DFC0003F4EDB /* SelectLocationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE512B70DFC0003F4EDB /* SelectLocationGroup.swift */; };
777+
F050AE5E2B739A73003F4EDB /* LocationDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */; };
778+
F050AE602B73A41E003F4EDB /* AllLocationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */; };
779+
F050AE622B74DBAC003F4EDB /* CustomListsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */; };
773780
F05F39942B21C6C6006E60A7 /* relays.json in Resources */ = {isa = PBXBuildFile; fileRef = 58F3C0A524A50155003E76BE /* relays.json */; };
774781
F05F39972B21C735006E60A7 /* RelayCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820675A26E6576800655B05 /* RelayCache.swift */; };
775782
F05F39982B21C73C006E60A7 /* CachedRelays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA87626B024A600B8C587 /* CachedRelays.swift */; };
@@ -1857,6 +1864,13 @@
18571864
F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncreasedHitButton.swift; sourceTree = "<group>"; };
18581865
F04F95A02B21D24400431E08 /* shadowsocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shadowsocks.h; sourceTree = "<group>"; };
18591866
F04FBE602A8379EE009278D7 /* AppPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = "<group>"; };
1867+
F050AE4B2B70D5A7003F4EDB /* SelectLocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationNode.swift; sourceTree = "<group>"; };
1868+
F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCellViewModel.swift; sourceTree = "<group>"; };
1869+
F050AE4F2B70DC4F003F4EDB /* SelectLocationNodeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationNodeProtocol.swift; sourceTree = "<group>"; };
1870+
F050AE512B70DFC0003F4EDB /* SelectLocationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationGroup.swift; sourceTree = "<group>"; };
1871+
F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataSourceProtocol.swift; sourceTree = "<group>"; };
1872+
F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllLocationDataSource.swift; sourceTree = "<group>"; };
1873+
F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSource.swift; sourceTree = "<group>"; };
18601874
F06045E52B231EB700B2D37A /* URLSessionTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTransport.swift; sourceTree = "<group>"; };
18611875
F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksTransport.swift; sourceTree = "<group>"; };
18621876
F06045EB2B2322A500B2D37A /* Jittered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Jittered.swift; sourceTree = "<group>"; };
@@ -2274,9 +2288,16 @@
22742288
583FE01729C196F3006E85F9 /* SelectLocation */ = {
22752289
isa = PBXGroup;
22762290
children = (
2291+
F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */,
2292+
F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */,
22772293
58435AC129CB2A350099C71B /* LocationCellFactory.swift */,
2294+
F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */,
22782295
583DA21325FA4B5C00318683 /* LocationDataSource.swift */,
2296+
F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */,
22792297
5888AD82227B11080051EB06 /* SelectLocationCell.swift */,
2298+
F050AE512B70DFC0003F4EDB /* SelectLocationGroup.swift */,
2299+
F050AE4B2B70D5A7003F4EDB /* SelectLocationNode.swift */,
2300+
F050AE4F2B70DC4F003F4EDB /* SelectLocationNodeProtocol.swift */,
22802301
5888AD86227B17950051EB06 /* SelectLocationViewController.swift */,
22812302
);
22822303
path = SelectLocation;
@@ -4878,6 +4899,7 @@
48784899
58968FAE28743E2000B799DC /* TunnelInteractor.swift in Sources */,
48794900
7A1A26472A29CF0800B978AA /* RelayFilterDataSource.swift in Sources */,
48804901
5864AF0929C78850005B0CD9 /* PreferencesCellFactory.swift in Sources */,
4902+
F050AE4E2B70D7F8003F4EDB /* LocationCellViewModel.swift in Sources */,
48814903
58CEB30C2AFD586600E6E088 /* DynamicBackgroundConfiguration.swift in Sources */,
48824904
587B7536266528A200DEF7E9 /* NotificationManager.swift in Sources */,
48834905
5820EDA9288FE064006BF4E4 /* DeviceManagementInteractor.swift in Sources */,
@@ -4891,6 +4913,7 @@
48914913
5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */,
48924914
F0E8E4C52A60499100ED26A3 /* AccountDeletionViewController.swift in Sources */,
48934915
7A9CCCC12A96302800DD6A34 /* AccountCoordinator.swift in Sources */,
4916+
F050AE502B70DC4F003F4EDB /* SelectLocationNodeProtocol.swift in Sources */,
48944917
58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */,
48954918
5846227326E22A160035F7C2 /* StorePaymentObserver.swift in Sources */,
48964919
F0E3618B2A4ADD2F00AEEF2B /* WelcomeContentView.swift in Sources */,
@@ -4932,6 +4955,7 @@
49324955
7A9CCCBE2A96302800DD6A34 /* AccountDeletionCoordinator.swift in Sources */,
49334956
588527B4276B4F2F00BAA373 /* SetAccountOperation.swift in Sources */,
49344957
58FF9FE02B075ABC00E4C97D /* EditAccessMethodViewController.swift in Sources */,
4958+
F050AE622B74DBAC003F4EDB /* CustomListsDataSource.swift in Sources */,
49354959
F0DA87472A9CB9A2006044F1 /* AccountExpiryRow.swift in Sources */,
49364960
585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */,
49374961
E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */,
@@ -4964,6 +4988,7 @@
49644988
063F026628FFE11C001FA09F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
49654989
58DF28A52417CB4B00E836B0 /* StorePaymentManager.swift in Sources */,
49664990
583DA21425FA4B5C00318683 /* LocationDataSource.swift in Sources */,
4991+
F050AE602B73A41E003F4EDB /* AllLocationDataSource.swift in Sources */,
49674992
587EB6742714520600123C75 /* PreferencesDataSourceDelegate.swift in Sources */,
49684993
582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */,
49694994
7AF9BE8E2A331C7B00DBFEDB /* RelayFilterViewModel.swift in Sources */,
@@ -5069,7 +5094,9 @@
50695094
584592612639B4A200EF967F /* TermsOfServiceContentView.swift in Sources */,
50705095
5875960A26F371FC00BF6711 /* Tunnel+Messaging.swift in Sources */,
50715096
586C0D912B03D8A400E7CDD7 /* AccessMethodHeaderFooterReuseIdentifier.swift in Sources */,
5097+
F050AE4C2B70D5A7003F4EDB /* SelectLocationNode.swift in Sources */,
50725098
7A2960F62A963F7500389B82 /* AlertCoordinator.swift in Sources */,
5099+
F050AE522B70DFC0003F4EDB /* SelectLocationGroup.swift in Sources */,
50735100
063687BA28EB234F00BE7161 /* PacketTunnelTransport.swift in Sources */,
50745101
A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */,
50755102
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
@@ -5089,6 +5116,7 @@
50895116
5827B0BF2B14B37D00CCBBA1 /* Publisher+PreviousValue.swift in Sources */,
50905117
7A9CCCB62A96302800DD6A34 /* OutOfTimeCoordinator.swift in Sources */,
50915118
5827B0AA2B0F4C9100CCBBA1 /* EditAccessMethodViewControllerDelegate.swift in Sources */,
5119+
F050AE5E2B739A73003F4EDB /* LocationDataSourceProtocol.swift in Sources */,
50925120
7A5869A82B5140C200640D27 /* MethodSettingsValidationErrorContentView.swift in Sources */,
50935121
7A5869A22B502EA800640D27 /* MethodSettingsSectionIdentifier.swift in Sources */,
50945122
586C0D812B03CA8400E7CDD7 /* CurrentValueSubject+UIActionBindings.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// AllLocationDataSource.swift
3+
// MullvadVPN
4+
//
5+
// Created by Mojgan on 2024-02-07.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import MullvadREST
11+
import MullvadTypes
12+
13+
class AllLocationDataSource: LocationDataSourceProtocol {
14+
var nodeByLocation = [RelayLocation: SelectLocationNode]()
15+
private var locationList = [RelayLocation]()
16+
17+
func search(by text: String) -> [RelayLocation] {
18+
if text.isEmpty {
19+
return locationList
20+
} else {
21+
var filteredLocations: [RelayLocation] = []
22+
locationList.forEach { location in
23+
guard let countryNode = nodeByLocation[location] else { return }
24+
countryNode.showsChildren = false
25+
26+
if text.isEmpty || countryNode.displayName.fuzzyMatch(text) {
27+
filteredLocations.append(countryNode.location)
28+
}
29+
30+
for cityNode in countryNode.children {
31+
cityNode.showsChildren = false
32+
33+
let relaysContainSearchString = cityNode.children
34+
.contains(where: { $0.displayName.fuzzyMatch(text) })
35+
36+
if cityNode.displayName.fuzzyMatch(text) || relaysContainSearchString {
37+
if !filteredLocations.contains(where: { $0 == countryNode.location }) {
38+
filteredLocations.append(countryNode.location)
39+
}
40+
41+
filteredLocations.append(cityNode.location)
42+
countryNode.showsChildren = true
43+
44+
if relaysContainSearchString {
45+
cityNode.children.map { $0.location }.forEach {
46+
filteredLocations.append($0)
47+
}
48+
cityNode.showsChildren = true
49+
}
50+
}
51+
}
52+
}
53+
54+
return filteredLocations
55+
}
56+
}
57+
58+
func reload(
59+
_ response: MullvadREST.REST.ServerRelaysResponse,
60+
relays: [MullvadREST.REST.ServerRelay]
61+
) -> [RelayLocation] {
62+
nodeByLocation.removeAll()
63+
let rootNode = self.makeRootNode(name: SelectLocationGroup.allLocations.description)
64+
65+
for relay in relays {
66+
guard case let .city(countryCode, cityCode) = RelayLocation(dashSeparatedString: relay.location),
67+
let serverLocation = response.locations[relay.location] else { continue }
68+
69+
let relayLocation = RelayLocation.hostname(countryCode, cityCode, relay.hostname)
70+
71+
for ascendantOrSelf in relayLocation.ascendants + [relayLocation] {
72+
guard !nodeByLocation.keys.contains(ascendantOrSelf) else {
73+
continue
74+
}
75+
76+
// Maintain the `showsChildren` state when transitioning between relay lists
77+
let wasShowingChildren = nodeByLocation[ascendantOrSelf]?.showsChildren ?? false
78+
79+
let node = createNode(
80+
root: rootNode,
81+
ascendantOrSelf: ascendantOrSelf,
82+
serverLocation: serverLocation,
83+
relay: relay,
84+
wasShowingChildren: wasShowingChildren
85+
)
86+
nodeByLocation[ascendantOrSelf] = node
87+
}
88+
}
89+
90+
rootNode.sortChildrenRecursive()
91+
rootNode.computeActiveChildrenRecursive()
92+
locationList = rootNode.flatRelayLocationList()
93+
return locationList
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// CustomListsDataSource.swift
3+
// MullvadVPN
4+
//
5+
// Created by Mojgan on 2024-02-08.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import MullvadREST
11+
import MullvadTypes
12+
13+
class CustomListsDataSource: LocationDataSourceProtocol {
14+
var nodeByLocation = [RelayLocation: SelectLocationNode]()
15+
private var locationList = [RelayLocation]()
16+
17+
func search(by text: String) -> [RelayLocation] {
18+
return []
19+
}
20+
21+
func reload(
22+
_ response: MullvadREST.REST.ServerRelaysResponse,
23+
relays: [MullvadREST.REST.ServerRelay]
24+
) -> [RelayLocation] {
25+
locationList
26+
}
27+
}

ios/MullvadVPN/View controllers/SelectLocation/LocationCellFactory.swift

+12-8
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,26 @@ import MullvadTypes
1010
import UIKit
1111

1212
protocol LocationCellEventHandler {
13-
func collapseCell(for item: RelayLocation)
13+
func collapseCell(for item: LocationCellViewModel)
14+
func node(for item: LocationCellViewModel) -> SelectLocationNode?
1415
}
1516

1617
final class LocationCellFactory: CellFactoryProtocol {
17-
var nodeByLocation = [RelayLocation: LocationDataSource.Node]()
1818
var delegate: LocationCellEventHandler?
1919
let tableView: UITableView
20+
let reuseIdentifier: String
2021

21-
init(tableView: UITableView, nodeByLocation: [RelayLocation: LocationDataSource.Node]) {
22+
init(
23+
tableView: UITableView,
24+
reuseIdentifier: String
25+
) {
2226
self.tableView = tableView
23-
self.nodeByLocation = nodeByLocation
27+
self.reuseIdentifier = reuseIdentifier
2428
}
2529

26-
func makeCell(for item: RelayLocation, indexPath: IndexPath) -> UITableViewCell {
30+
func makeCell(for item: LocationCellViewModel, indexPath: IndexPath) -> UITableViewCell {
2731
let cell = tableView.dequeueReusableCell(
28-
withIdentifier: LocationDataSource.CellReuseIdentifiers.locationCell.rawValue,
32+
withIdentifier: reuseIdentifier,
2933
for: indexPath
3034
)
3135

@@ -34,9 +38,9 @@ final class LocationCellFactory: CellFactoryProtocol {
3438
return cell
3539
}
3640

37-
func configureCell(_ cell: UITableViewCell, item: RelayLocation, indexPath: IndexPath) {
41+
func configureCell(_ cell: UITableViewCell, item: LocationCellViewModel, indexPath: IndexPath) {
3842
guard let cell = cell as? SelectLocationCell,
39-
let node = nodeByLocation[item] else { return }
43+
let node = delegate?.node(for: item) else { return }
4044

4145
cell.accessibilityIdentifier = node.location.stringRepresentation
4246
cell.isDisabled = !node.isActive
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// LocationCellViewModel.swift
3+
// MullvadVPN
4+
//
5+
// Created by Mojgan on 2024-02-05.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import MullvadTypes
10+
11+
struct LocationCellViewModel: Hashable {
12+
let group: SelectLocationGroup
13+
let location: RelayLocation
14+
}

0 commit comments

Comments
 (0)