From 2ad27f50a678ca06da7cb55c637890bd25692b34 Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Wed, 24 Apr 2024 09:14:19 +0200 Subject: [PATCH] Add a new container view to wrap the whole location selection --- ios/MullvadVPN.xcodeproj/project.pbxproj | 4 + .../Classes/AccessbilityIdentifier.swift | 1 + .../Coordinators/LocationCoordinator.swift | 113 ++++++------- .../UI appearance/UIColor+Palette.swift | 5 + .../RelayFilter/RelayFilterView.swift | 4 +- .../LocationViewController.swift | 57 ++----- .../LocationViewControllerWrapper.swift | 156 ++++++++++++++++++ 7 files changed, 229 insertions(+), 111 deletions(-) create mode 100644 ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 27fc3db0b0da..b01bed21d732 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -587,6 +587,7 @@ 7AA513862BC91C6B00D081A4 /* LogRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */; }; 7AB2B6702BA1EB8C00B03E3B /* ListCustomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */; }; 7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */; }; + 7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */; }; 7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */; }; 7AB4CCBB2B691BBB006037F5 /* IPOverrideInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */; }; 7ABCA5B32A9349F20044A708 /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; }; @@ -1855,6 +1856,7 @@ 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRotationTests.swift; sourceTree = ""; }; 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListViewController.swift; sourceTree = ""; }; 7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListCoordinator.swift; sourceTree = ""; }; + 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewControllerWrapper.swift; sourceTree = ""; }; 7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideTests.swift; sourceTree = ""; }; 7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideInteractor.swift; sourceTree = ""; }; 7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = ""; }; @@ -2646,6 +2648,7 @@ F050AE512B70DFC0003F4EDB /* LocationSection.swift */, F0BE65362B9F136A005CC385 /* LocationSectionHeaderView.swift */, 5888AD86227B17950051EB06 /* LocationViewController.swift */, + 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */, ); path = SelectLocation; sourceTree = ""; @@ -5582,6 +5585,7 @@ 063687BA28EB234F00BE7161 /* PacketTunnelTransport.swift in Sources */, A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */, 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */, + 7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */, F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */, 58FF9FF42B07C61B00E4C97D /* AccessMethodValidationError.swift in Sources */, 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */, diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index 9f738fabcc2b..9fab241c13b2 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -102,6 +102,7 @@ public enum AccessibilityIdentifier: String { case outOfTimeView case termsOfServiceView case selectLocationView + case selectLocationViewWrapper case selectLocationTableView case settingsTableView case vpnSettingsTableView diff --git a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift index 3ccfbd1c70b9..58e828ef1289 100644 --- a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift @@ -24,10 +24,10 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { navigationController } - var locationViewController: LocationViewController? { + var locationViewControllerWrapper: LocationViewControllerWrapper? { return navigationController.viewControllers.first { - $0 is LocationViewController - } as? LocationViewController + $0 is LocationViewControllerWrapper + } as? LocationViewControllerWrapper } var relayFilter: RelayFilter { @@ -54,43 +54,14 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { } func start() { - let locationViewController = LocationViewController(customListRepository: customListRepository) - locationViewController.delegate = self - - locationViewController.didSelectRelays = { [weak self] locations in - guard let self else { return } - - var relayConstraints = tunnelManager.settings.relayConstraints - relayConstraints.locations = .only(locations) - - tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) { - self.tunnelManager.startTunnel() - } - - didFinish?(self) - } - - locationViewController.navigateToFilter = { [weak self] in - guard let self else { return } - - let coordinator = makeRelayFilterCoordinator(forModalPresentation: true) - coordinator.start() - - presentChild(coordinator, animated: true) - } - - locationViewController.didUpdateFilter = { [weak self] filter in - guard let self else { return } - - var relayConstraints = tunnelManager.settings.relayConstraints - relayConstraints.filter = .only(filter) - - tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) - } + let locationViewControllerWrapper = LocationViewControllerWrapper( + customListRepository: customListRepository, + selectedRelays: tunnelManager.settings.relayConstraints.locations.value + ) + locationViewControllerWrapper.delegate = self - locationViewController.didFinish = { [weak self] in + locationViewControllerWrapper.didFinish = { [weak self] in guard let self else { return } - didFinish?(self) } @@ -98,12 +69,10 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { if let cachedRelays = try? relayCacheTracker.getCachedRelays() { self.cachedRelays = cachedRelays - locationViewController.setCachedRelays(cachedRelays, filter: relayFilter) + locationViewControllerWrapper.setCachedRelays(cachedRelays, filter: relayFilter) } - locationViewController.relayLocations = tunnelManager.settings.relayConstraints.locations.value - - navigationController.pushViewController(locationViewController, animated: false) + navigationController.pushViewController(locationViewControllerWrapper, animated: false) } private func makeRelayFilterCoordinator(forModalPresentation isModalPresentation: Bool) @@ -118,7 +87,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { relayFilterCoordinator.didFinish = { [weak self] coordinator, filter in if let cachedRelays = self?.cachedRelays, let filter { - self?.locationViewController?.setCachedRelays(cachedRelays, filter: filter) + self?.locationViewControllerWrapper?.setCachedRelays(cachedRelays, filter: filter) } coordinator.dismiss(animated: true) @@ -138,7 +107,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { coordinator.didFinish = { [weak self] addCustomListCoordinator in addCustomListCoordinator.dismiss(animated: true) - self?.locationViewController?.refreshCustomLists() + self?.locationViewControllerWrapper?.refreshCustomLists() } coordinator.start() @@ -155,7 +124,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { coordinator.didFinish = { [weak self] listCustomListCoordinator in listCustomListCoordinator.dismiss(animated: true) - self?.locationViewController?.refreshCustomLists() + self?.locationViewControllerWrapper?.refreshCustomLists() } coordinator.start() @@ -169,7 +138,7 @@ class LocationCoordinator: Coordinator, Presentable, Presenting { // See showEditCustomLists() above. extension LocationCoordinator: UIAdaptivePresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - locationViewController?.refreshCustomLists() + locationViewControllerWrapper?.refreshCustomLists() } } @@ -180,32 +149,50 @@ extension LocationCoordinator: RelayCacheTrackerObserver { ) { self.cachedRelays = cachedRelays - locationViewController?.setCachedRelays(cachedRelays, filter: relayFilter) + locationViewControllerWrapper?.setCachedRelays(cachedRelays, filter: relayFilter) } } -extension LocationCoordinator: LocationViewControllerDelegate { - func didRequestRouteToCustomLists(_ controller: LocationViewController, nodes: [LocationNode]) { +extension LocationCoordinator: LocationViewControllerWrapperDelegate { + func didSelectRelays(relays: UserSelectedRelays) { + var relayConstraints = tunnelManager.settings.relayConstraints + relayConstraints.locations = .only(relays) + + tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) { + self.tunnelManager.startTunnel() + } + + didFinish?(self) + } + + func didUpdateFilter(filter: RelayFilter) { + var relayConstraints = tunnelManager.settings.relayConstraints + relayConstraints.filter = .only(filter) + + tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) + } + + func navigateToFilter() { + let coordinator = makeRelayFilterCoordinator(forModalPresentation: true) + coordinator.start() + + presentChild(coordinator, animated: true) + } + + func navigateToCustomLists(nodes: [LocationNode]) { let actionSheet = UIAlertController( title: NSLocalizedString( - "CUSTOM_LIST_ACTION_SHEET_TITLE", - tableName: "CustomLists", - value: "Custom lists", - comment: "" + "ACTION_SHEET_TITLE", tableName: "CustomLists", value: "Custom lists", comment: "" ), message: nil, preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet ) - actionSheet.overrideUserInterfaceStyle = .dark actionSheet.view.tintColor = UIColor(red: 0.0, green: 0.59, blue: 1.0, alpha: 1) let addCustomListAction = UIAlertAction( title: NSLocalizedString( - "CUSTOM_LIST_ACTION_SHEET_ADD_LIST_BUTTON", - tableName: "CustomLists", - value: "Add new list", - comment: "" + "ACTION_SHEET_ADD_LIST_BUTTON", tableName: "CustomLists", value: "Add new list", comment: "" ), style: .default, handler: { [weak self] _ in @@ -214,12 +201,10 @@ extension LocationCoordinator: LocationViewControllerDelegate { ) addCustomListAction.accessibilityIdentifier = AccessibilityIdentifier.addNewCustomListButton actionSheet.addAction(addCustomListAction) + let editAction = UIAlertAction( title: NSLocalizedString( - "CUSTOM_LIST_ACTION_SHEET_EDIT_LISTS_BUTTON", - tableName: "CustomLists", - value: "Edit lists", - comment: "" + "ACTION_SHEET_EDIT_LISTS_BUTTON", tableName: "CustomLists", value: "Edit lists", comment: "" ), style: .default, handler: { [weak self] _ in @@ -228,7 +213,6 @@ extension LocationCoordinator: LocationViewControllerDelegate { ) editAction.isEnabled = !customListRepository.fetchAll().isEmpty editAction.accessibilityIdentifier = AccessibilityIdentifier.editCustomListButton - actionSheet.addAction(editAction) actionSheet.addAction(UIAlertAction( @@ -238,8 +222,7 @@ extension LocationCoordinator: LocationViewControllerDelegate { value: "Cancel", comment: "" ), - style: .cancel, - handler: nil + style: .cancel )) presentationContext.present(actionSheet, animated: true) diff --git a/ios/MullvadVPN/UI appearance/UIColor+Palette.swift b/ios/MullvadVPN/UI appearance/UIColor+Palette.swift index 5cc9a562a111..bc53819d1030 100644 --- a/ios/MullvadVPN/UI appearance/UIColor+Palette.swift +++ b/ios/MullvadVPN/UI appearance/UIColor+Palette.swift @@ -136,6 +136,11 @@ extension UIColor { static let actionButtonColor = UIColor(white: 1.0, alpha: 0.8) } + enum SegmentedControl { + static let backgroundColor = UIColor(red: 0.18, green: 0.33, blue: 0.49, alpha: 1.0) + static let selectedColor = successColor + } + // Common colors static let primaryColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 1.0) static let secondaryColor = UIColor(red: 0.10, green: 0.18, blue: 0.27, alpha: 1.0) diff --git a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift index ab66f9b3ff00..5a952b146b43 100644 --- a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift +++ b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift @@ -94,8 +94,8 @@ class RelayFilterView: UIView { contentContainer.spacing = UIMetrics.FilterView.labelSpacing addConstrainedSubviews([contentContainer]) { - contentContainer.pinEdges(.init([.top(0), .bottom(0)]), to: self) - contentContainer.pinEdges(.init([.leading(0), .trailing(0)]), to: layoutMarginsGuide) + contentContainer.pinEdges(.init([.top(4), .bottom(0)]), to: self) + contentContainer.pinEdges(.init([.leading(4), .trailing(4)]), to: layoutMarginsGuide) } } diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift index fa787f86c683..38188122ffa6 100644 --- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift +++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift @@ -6,14 +6,15 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import MullvadLogging import MullvadREST import MullvadSettings import MullvadTypes import UIKit protocol LocationViewControllerDelegate: AnyObject { - func didRequestRouteToCustomLists(_ controller: LocationViewController, nodes: [LocationNode]) + func navigateToCustomLists(nodes: [LocationNode]) + func didSelectRelays(relays: UserSelectedRelays) + func didUpdateFilter(filter: RelayFilter) } final class LocationViewController: UIViewController { @@ -24,7 +25,7 @@ final class LocationViewController: UIViewController { private var dataSource: LocationDataSource? private var cachedRelays: CachedRelays? private var filter = RelayFilter() - var relayLocations: UserSelectedRelays? + private var selectedRelays: UserSelectedRelays? weak var delegate: LocationViewControllerDelegate? var customListRepository: CustomListRepositoryProtocol @@ -36,13 +37,9 @@ final class LocationViewController: UIViewController { return (filter.ownership == .any) && (filter.providers == .any) } - var navigateToFilter: (() -> Void)? - var didSelectRelays: ((UserSelectedRelays) -> Void)? - var didUpdateFilter: ((RelayFilter) -> Void)? - var didFinish: (() -> Void)? - - init(customListRepository: CustomListRepositoryProtocol) { + init(customListRepository: CustomListRepositoryProtocol, selectedRelays: UserSelectedRelays?) { self.customListRepository = customListRepository + self.selectedRelays = selectedRelays super.init(nibName: nil, bundle: nil) } @@ -58,34 +55,6 @@ final class LocationViewController: UIViewController { view.accessibilityIdentifier = .selectLocationView view.backgroundColor = .secondaryColor - navigationItem.title = NSLocalizedString( - "NAVIGATION_TITLE", - tableName: "SelectLocation", - value: "Select location", - comment: "" - ) - - navigationItem.leftBarButtonItem = UIBarButtonItem( - title: NSLocalizedString( - "NAVIGATION_TITLE", - tableName: "SelectLocation", - value: "Filter", - comment: "" - ), - primaryAction: UIAction(handler: { [weak self] _ in - self?.navigateToFilter?() - }) - ) - navigationItem.leftBarButtonItem?.accessibilityIdentifier = .selectLocationFilterButton - - navigationItem.rightBarButtonItem = UIBarButtonItem( - systemItem: .done, - primaryAction: UIAction(handler: { [weak self] _ in - self?.didFinish?() - }) - ) - navigationItem.rightBarButtonItem?.accessibilityIdentifier = .closeSelectLocationButton - setUpDataSources() setUpTableView() setUpTopContent() @@ -94,7 +63,7 @@ final class LocationViewController: UIViewController { topContentView.pinEdgesToSuperviewMargins(.all().excluding(.bottom)) tableView.pinEdgesToSuperview(.all().excluding(.top)) - tableView.topAnchor.constraint(equalTo: topContentView.bottomAnchor) + tableView.topAnchor.constraint(equalTo: topContentView.bottomAnchor, constant: 8) } } @@ -121,11 +90,11 @@ final class LocationViewController: UIViewController { filterView.setFilter(filter) } - dataSource?.setRelays(cachedRelays.relays, selectedRelays: relayLocations, filter: filter) + dataSource?.setRelays(cachedRelays.relays, selectedRelays: selectedRelays, filter: filter) } func refreshCustomLists() { - dataSource?.refreshCustomLists(selectedRelays: relayLocations) + dataSource?.refreshCustomLists(selectedRelays: selectedRelays) } // MARK: - Private @@ -138,7 +107,7 @@ final class LocationViewController: UIViewController { ) dataSource?.didSelectRelayLocations = { [weak self] locations in - self?.didSelectRelays?(locations) + self?.delegate?.didSelectRelays(relays: locations) } dataSource?.didTapEditCustomLists = { [weak self] in @@ -147,12 +116,12 @@ final class LocationViewController: UIViewController { if let cachedRelays { let allLocationDataSource = AllLocationDataSource() allLocationDataSource.reload(cachedRelays.relays, relays: cachedRelays.relays.wireguard.relays) - delegate?.didRequestRouteToCustomLists(self, nodes: allLocationDataSource.nodes) + delegate?.navigateToCustomLists(nodes: allLocationDataSource.nodes) } } if let cachedRelays { - dataSource?.setRelays(cachedRelays.relays, selectedRelays: relayLocations, filter: filter) + dataSource?.setRelays(cachedRelays.relays, selectedRelays: selectedRelays, filter: filter) } } @@ -178,7 +147,7 @@ final class LocationViewController: UIViewController { guard let self else { return } filter = $0 - didUpdateFilter?($0) + delegate?.didUpdateFilter(filter: $0) if let cachedRelays { setCachedRelays(cachedRelays, filter: filter) diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift new file mode 100644 index 000000000000..333d965f8734 --- /dev/null +++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift @@ -0,0 +1,156 @@ +// +// LocationViewControllerWrapper.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-04-23. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import MullvadREST +import MullvadSettings +import MullvadTypes +import UIKit + +protocol LocationViewControllerWrapperDelegate: AnyObject { + func navigateToCustomLists(nodes: [LocationNode]) + func navigateToFilter() + func didSelectRelays(relays: UserSelectedRelays) + func didUpdateFilter(filter: RelayFilter) +} + +final class LocationViewControllerWrapper: UIViewController { + private let locationViewController: LocationViewController + private let segmentedControl = UISegmentedControl() + + weak var delegate: LocationViewControllerWrapperDelegate? + + init(customListRepository: CustomListRepositoryProtocol, selectedRelays: UserSelectedRelays?) { + locationViewController = LocationViewController( + customListRepository: customListRepository, + selectedRelays: selectedRelays + ) + + super.init(nibName: nil, bundle: nil) + + locationViewController.delegate = self + } + + var didFinish: (() -> Void)? + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.accessibilityIdentifier = .selectLocationViewWrapper + view.backgroundColor = .secondaryColor + + setUpNavigation() + setUpSegmentedControl() + addSubviews() + } + + func setCachedRelays(_ cachedRelays: CachedRelays, filter: RelayFilter) { + locationViewController.setCachedRelays(cachedRelays, filter: filter) + } + + func refreshCustomLists() { + locationViewController.refreshCustomLists() + } + + private func setUpNavigation() { + navigationItem.largeTitleDisplayMode = .never + + navigationItem.title = NSLocalizedString( + "NAVIGATION_TITLE", + tableName: "SelectLocation", + value: "Select location", + comment: "" + ) + + navigationItem.leftBarButtonItem = UIBarButtonItem( + title: NSLocalizedString( + "NAVIGATION_FILTER", + tableName: "SelectLocation", + value: "Filter", + comment: "" + ), + primaryAction: UIAction(handler: { [weak self] _ in + self?.delegate?.navigateToFilter() + }) + ) + navigationItem.leftBarButtonItem?.accessibilityIdentifier = .selectLocationFilterButton + + navigationItem.rightBarButtonItem = UIBarButtonItem( + systemItem: .done, + primaryAction: UIAction(handler: { [weak self] _ in + self?.didFinish?() + }) + ) + navigationItem.rightBarButtonItem?.accessibilityIdentifier = .closeSelectLocationButton + } + + private func setUpSegmentedControl() { + segmentedControl.backgroundColor = .SegmentedControl.backgroundColor + segmentedControl.selectedSegmentTintColor = .SegmentedControl.selectedColor + segmentedControl.setTitleTextAttributes([ + .foregroundColor: UIColor.white, + .font: UIFont.systemFont(ofSize: 17, weight: .medium), + ], for: .normal) + + segmentedControl.insertSegment(withTitle: NSLocalizedString( + "MULTIHOP_TAB_ENTRY", + tableName: "SelectLocation", + value: "Entry", + comment: "" + ), at: 0, animated: false) + segmentedControl.insertSegment(withTitle: NSLocalizedString( + "MULTIHOP_TAB_EXIT", + tableName: "SelectLocation", + value: "Exit", + comment: "" + ), at: 1, animated: false) + + segmentedControl.selectedSegmentIndex = 0 + segmentedControl.addTarget(self, action: #selector(segmentedControlDidChange), for: .valueChanged) + } + + private func addSubviews() { + addChild(locationViewController) + locationViewController.didMove(toParent: self) + + view.addConstrainedSubviews([segmentedControl, locationViewController.view]) { + segmentedControl.heightAnchor.constraint(equalToConstant: 44) + segmentedControl.pinEdgesToSuperviewMargins(PinnableEdges([.top(0), .leading(8), .trailing(8)])) + + locationViewController.view.pinEdgesToSuperview(.all().excluding(.top)) + + #if DEBUG + locationViewController.view.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 4) + #else + locationViewController.view.pinEdgeToSuperviewMargin(.top(0)) + #endif + } + } + + @objc + private func segmentedControlDidChange(sender: UISegmentedControl) { + refreshCustomLists() + } +} + +extension LocationViewControllerWrapper: LocationViewControllerDelegate { + func navigateToCustomLists(nodes: [LocationNode]) { + delegate?.navigateToCustomLists(nodes: nodes) + } + + func didSelectRelays(relays: UserSelectedRelays) { + delegate?.didSelectRelays(relays: relays) + } + + func didUpdateFilter(filter: RelayFilter) { + delegate?.didUpdateFilter(filter: filter) + } +}