summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authormojganii <mojgan.jelodar@codic.se>2024-03-05 10:19:11 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-03-12 08:31:23 +0100
commit1c89cd07fb90e23dd52a5e9441a2d144e913a7c3 (patch)
tree23fef51c6ca36fae91142eb2e4038b17e9b02acf
parent2df44a8aaa1c4673d666941a2fa3aa6afc2bfa37 (diff)
downloadmullvadvpn-1c89cd07fb90e23dd52a5e9441a2d144e913a7c3.tar.xz
mullvadvpn-1c89cd07fb90e23dd52a5e9441a2d144e913a7c3.zip
adding stub for custom lists
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift11
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/CustomListsDataSource.swift103
-rw-r--r--ios/MullvadVPN/Coordinators/LocationCoordinator.swift37
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift1
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/InMemoryCustomListRepository.swift55
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift20
7 files changed, 219 insertions, 12 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 815b5a268f..f709874aca 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -848,6 +848,7 @@
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
F09D04C02AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */; };
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
+ F0A92B3C2B8E44F900DC7B37 /* InMemoryCustomListRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A92B3B2B8E44F900DC7B37 /* InMemoryCustomListRepository.swift */; };
F0B0E6972AFE6E7E001DC66B /* XCTest+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */; };
F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */; };
F0C3333C2B31A29C00D1A478 /* MullvadSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */; };
@@ -1981,6 +1982,7 @@
F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionStub.swift; sourceTree = "<group>"; };
F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionService.swift; sourceTree = "<group>"; };
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = "<group>"; };
+ F0A92B3B2B8E44F900DC7B37 /* InMemoryCustomListRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryCustomListRepository.swift; sourceTree = "<group>"; };
F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Async.swift"; sourceTree = "<group>"; };
F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderIdentifier.swift; sourceTree = "<group>"; };
F0C6A8422AB08E54000777A8 /* RedeemVoucherViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedeemVoucherViewConfiguration.swift; sourceTree = "<group>"; };
@@ -2403,6 +2405,7 @@
children = (
F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */,
F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */,
+ F0A92B3B2B8E44F900DC7B37 /* InMemoryCustomListRepository.swift */,
5888AD82227B11080051EB06 /* LocationCell.swift */,
58435AC129CB2A350099C71B /* LocationCellFactory.swift */,
F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */,
@@ -5129,6 +5132,7 @@
7ABE318D2A1CDD4500DF4963 /* UIFont+Weight.swift in Sources */,
58C774BE29A7A249003A1A56 /* CustomNavigationController.swift in Sources */,
E1FD0DF528AA7CE400299DB4 /* StatusActivityView.swift in Sources */,
+ F0A92B3C2B8E44F900DC7B37 /* InMemoryCustomListRepository.swift in Sources */,
7A2960FD2A964BB700389B82 /* AlertPresentation.swift in Sources */,
0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */,
58968FAE28743E2000B799DC /* TunnelInteractor.swift in Sources */,
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index d0fc0c6bc3..b92a5492d8 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -43,6 +43,14 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
*/
private let secondaryNavigationContainer = RootContainerViewController()
+ private var customListRepository: CustomListRepositoryProtocol {
+ #if DEBUG
+ InMemoryCustomListRepository()
+ #else
+ CustomListRepository()
+ #endif
+ }
+
/// Posts `preferredAccountNumber` notification when user inputs the account number instead of voucher code
private let preferredAccountNumberSubject = PassthroughSubject<String, Never>()
@@ -710,7 +718,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
let locationCoordinator = LocationCoordinator(
navigationController: navigationController,
tunnelManager: tunnelManager,
- relayCacheTracker: relayCacheTracker
+ relayCacheTracker: relayCacheTracker,
+ customListRepository: customListRepository
)
locationCoordinator.didFinish = { [weak self] _ in
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/CustomListsDataSource.swift b/ios/MullvadVPN/Coordinators/CustomLists/CustomListsDataSource.swift
new file mode 100644
index 0000000000..e24a4c46dd
--- /dev/null
+++ b/ios/MullvadVPN/Coordinators/CustomLists/CustomListsDataSource.swift
@@ -0,0 +1,103 @@
+//
+// 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
+import UIKit
+
+class CustomListsDataSource: LocationDataSourceProtocol {
+ private(set) var nodes = [LocationNode]()
+ var didTapEditCustomLists: (() -> Void)?
+
+ var viewForHeader: UIView? {
+ LocationSectionHeaderView(configuration: LocationSectionHeaderView.Configuration(
+ name: LocationSection.customLists.description,
+ primaryAction: UIAction(
+ handler: { [weak self] _ in
+ self?.didTapEditCustomLists?()
+ }
+ )
+ ))
+ }
+
+ init(didTapEditCustomLists: (() -> Void)?) {
+ self.didTapEditCustomLists = didTapEditCustomLists
+ }
+
+ var searchableNodes: [LocationNode] {
+ nodes.flatMap { $0.children }
+ }
+
+ func reload(allLocationNodes: [LocationNode], customLists: [CustomList]) {
+ nodes = customLists.map { list in
+ let listNode = LocationListNode(
+ nodeName: list.name,
+ nodeCode: list.name.lowercased(),
+ locations: list.locations,
+ customList: list
+ )
+
+ listNode.children = list.locations.compactMap { location in
+ copy(location, from: allLocationNodes, withParent: listNode)
+ }
+
+ listNode.forEachDescendant { _, node in
+ node.nodeCode = "\(listNode.nodeCode)-\(node.nodeCode)"
+ }
+
+ return listNode
+ }
+ }
+
+ func node(by locations: [RelayLocation], for customList: CustomList) -> LocationNode? {
+ guard let customListNode = nodes.first(where: { $0.nodeName == customList.name })
+ else { return nil }
+
+ if locations.count > 1 {
+ return customListNode
+ } else {
+ return switch locations.first {
+ case let .country(countryCode):
+ customListNode.nodeFor(nodeCode: "\(customListNode.nodeCode)-\(countryCode)")
+ case let .city(_, cityCode):
+ customListNode.nodeFor(nodeCode: "\(customListNode.nodeCode)-\(cityCode)")
+ case let .hostname(_, _, hostCode):
+ customListNode.nodeFor(nodeCode: "\(customListNode.nodeCode)-\(hostCode)")
+ case .none:
+ nil
+ }
+ }
+ }
+
+ private func copy(
+ _ location: RelayLocation,
+ from allLocationNodes: [LocationNode],
+ withParent rootNode: LocationNode
+ ) -> LocationNode? {
+ let rootNode = RootNode(children: allLocationNodes)
+
+ return switch location {
+ case let .country(countryCode):
+ rootNode
+ .countryFor(countryCode: countryCode)?.copy(withParent: rootNode)
+
+ case let .city(countryCode, cityCode):
+ rootNode
+ .countryFor(countryCode: countryCode)?.copy(withParent: rootNode)
+ .cityFor(cityCode: cityCode)
+
+ case let .hostname(countryCode, cityCode, hostCode):
+ rootNode
+ .countryFor(countryCode: countryCode)?.copy(withParent: rootNode)
+ .cityFor(cityCode: cityCode)?
+ .hostFor(hostCode: hostCode)
+ }
+ }
+}
diff --git a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
index 39bffdaab9..fefc4a6f3a 100644
--- a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
@@ -7,6 +7,7 @@
//
import MullvadREST
+import MullvadSettings
import MullvadTypes
import Routing
import UIKit
@@ -15,6 +16,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
private let tunnelManager: TunnelManager
private let relayCacheTracker: RelayCacheTracker
private var cachedRelays: CachedRelays?
+ private var customListRepository: CustomListRepositoryProtocol
let navigationController: UINavigationController
@@ -42,17 +44,21 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
init(
navigationController: UINavigationController,
tunnelManager: TunnelManager,
- relayCacheTracker: RelayCacheTracker
+ relayCacheTracker: RelayCacheTracker,
+ customListRepository: CustomListRepositoryProtocol
) {
self.navigationController = navigationController
self.tunnelManager = tunnelManager
self.relayCacheTracker = relayCacheTracker
+ self.customListRepository = customListRepository
}
func start() {
- let selectLocationViewController = LocationViewController()
+ let locationViewController = LocationViewController(customListRepository: customListRepository)
+ locationViewController.delegate = self
+
+ locationViewController.didSelectRelays = { [weak self] locations in
- selectLocationViewController.didSelectRelays = { [weak self] locations in
guard let self else { return }
var relayConstraints = tunnelManager.settings.relayConstraints
@@ -65,7 +71,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
didFinish?(self)
}
- selectLocationViewController.navigateToFilter = { [weak self] in
+ locationViewController.navigateToFilter = { [weak self] in
guard let self else { return }
let coordinator = makeRelayFilterCoordinator(forModalPresentation: true)
@@ -74,7 +80,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
presentChild(coordinator, animated: true)
}
- selectLocationViewController.didUpdateFilter = { [weak self] filter in
+ locationViewController.didUpdateFilter = { [weak self] filter in
guard let self else { return }
var relayConstraints = tunnelManager.settings.relayConstraints
@@ -83,7 +89,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
tunnelManager.updateSettings([.relayConstraints(relayConstraints)])
}
- selectLocationViewController.didFinish = { [weak self] in
+ locationViewController.didFinish = { [weak self] in
guard let self else { return }
didFinish?(self)
@@ -93,12 +99,12 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
if let cachedRelays = try? relayCacheTracker.getCachedRelays() {
self.cachedRelays = cachedRelays
- selectLocationViewController.setCachedRelays(cachedRelays, filter: relayFilter)
+ locationViewController.setCachedRelays(cachedRelays, filter: relayFilter)
}
- selectLocationViewController.relayLocations = tunnelManager.settings.relayConstraints.locations.value
+ locationViewController.relayLocations = tunnelManager.settings.relayConstraints.locations.value
- navigationController.pushViewController(selectLocationViewController, animated: false)
+ navigationController.pushViewController(locationViewController, animated: false)
}
private func makeRelayFilterCoordinator(forModalPresentation isModalPresentation: Bool)
@@ -131,3 +137,16 @@ class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrack
selectLocationViewController?.setCachedRelays(cachedRelays, filter: relayFilter)
}
}
+
+extension LocationCoordinator: LocationViewControllerDelegate {
+ func didRequestRouteToCustomLists(_ controller: LocationViewController) {
+ let coordinator = AddCustomListCoordinator(
+ navigationController: CustomNavigationController(),
+ customListInteractor: CustomListInteractor(
+ repository: customListRepository
+ )
+ )
+ coordinator.start()
+ presentChild(coordinator, animated: true)
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift b/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift
index a6e9e1bab0..7427b78cc9 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift
@@ -9,6 +9,7 @@
import Foundation
import MullvadREST
import MullvadTypes
+import UIKit
class AllLocationDataSource: LocationDataSourceProtocol {
private(set) var nodes = [LocationNode]()
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/InMemoryCustomListRepository.swift b/ios/MullvadVPN/View controllers/SelectLocation/InMemoryCustomListRepository.swift
new file mode 100644
index 0000000000..fbc33ad071
--- /dev/null
+++ b/ios/MullvadVPN/View controllers/SelectLocation/InMemoryCustomListRepository.swift
@@ -0,0 +1,55 @@
+//
+// InMemoryCustomListRepository.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2024-01-31.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+import MullvadSettings
+import MullvadTypes
+
+class InMemoryCustomListRepository: CustomListRepositoryProtocol {
+ var publisher: AnyPublisher<[CustomList], Never> {
+ passthroughSubject.eraseToAnyPublisher()
+ }
+
+ private var customRelayLists: [CustomList] = [
+ CustomList(id: UUID(), name: "Netflix", locations: [.city("al", "tia")]),
+ CustomList(id: UUID(), name: "Streaming", locations: [
+ .city("us", "dal"),
+ .country("se"),
+ .city("de", "ber"),
+ ]),
+ ]
+
+ private let passthroughSubject = PassthroughSubject<[CustomList], Never>()
+
+ func update(_ list: CustomList) {
+ if let index = customRelayLists.firstIndex(where: { $0.id == list.id }) {
+ customRelayLists[index] = list
+ }
+ }
+
+ func delete(id: UUID) {
+ if let index = customRelayLists.firstIndex(where: { $0.id == id }) {
+ customRelayLists.remove(at: index)
+ }
+ }
+
+ func fetch(by id: UUID) -> CustomList? {
+ return customRelayLists.first(where: { $0.id == id })
+ }
+
+ func create(_ name: String, locations: [RelayLocation]) throws -> CustomList {
+ let item = CustomList(id: UUID(), name: name, locations: locations)
+ customRelayLists.append(item)
+ return item
+ }
+
+ func fetchAll() -> [CustomList] {
+ customRelayLists
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
index 16f4797d80..3c37884544 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
@@ -12,15 +12,21 @@ import MullvadSettings
import MullvadTypes
import UIKit
+protocol LocationViewControllerDelegate: AnyObject {
+ func didRequestRouteToCustomLists(_ controller: LocationViewController)
+}
+
final class LocationViewController: UIViewController {
private let searchBar = UISearchBar()
- private let tableView = UITableView()
+ private let tableView = UITableView(frame: .zero, style: .grouped)
private let topContentView = UIStackView()
private let filterView = RelayFilterView()
private var dataSource: LocationDataSource?
private var cachedRelays: CachedRelays?
private var filter = RelayFilter()
var relayLocations: RelayLocations?
+ weak var delegate: LocationViewControllerDelegate?
+ var customListRepository: CustomListRepositoryProtocol
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
@@ -35,6 +41,15 @@ final class LocationViewController: UIViewController {
var didUpdateFilter: ((RelayFilter) -> Void)?
var didFinish: (() -> Void)?
+ init(customListRepository: CustomListRepositoryProtocol) {
+ self.customListRepository = customListRepository
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
// MARK: - View lifecycle
override func viewDidLoad() {
@@ -108,7 +123,7 @@ final class LocationViewController: UIViewController {
dataSource = LocationDataSource(
tableView: tableView,
allLocations: AllLocationDataSource(),
- customLists: CustomListsDataSource(repository: CustomListRepository())
+ customLists: CustomListsDataSource(repository: customListRepository)
)
dataSource?.didSelectRelayLocations = { [weak self] locations in
@@ -128,6 +143,7 @@ final class LocationViewController: UIViewController {
tableView.indicatorStyle = .white
tableView.keyboardDismissMode = .onDrag
tableView.accessibilityIdentifier = .selectLocationTableView
+ tableView.sectionHeaderHeight = 56.0
}
private func setUpTopContent() {