Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add filtering to location selection #4794

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Line wrap the file at 100 chars. Th
- Rotate public key from within packet tunnel when it detects that the key stored on backend does
not match the one stored on device.
- Add WireGuard port selection to settings.
- Add redeeming voucher code on account view.
- Add filtering on ownership and provider to location selection view.

## [2023.2 - 2023-04-03]
### Changed
Expand Down
6 changes: 5 additions & 1 deletion ios/MullvadTypes/RelayConstraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,20 @@ public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible

// Added in 2023.3
public var port: RelayConstraint<UInt16>
public var filter: RelayConstraint<RelayFilter>

public var debugDescription: String {
"RelayConstraints { location: \(location), port: \(port) }"
}

public init(
location: RelayConstraint<RelayLocation> = .only(.country("se")),
port: RelayConstraint<UInt16> = .any
port: RelayConstraint<UInt16> = .any,
filter: RelayConstraint<RelayFilter> = .any
) {
self.location = location
self.port = port
self.filter = filter
}

public init(from decoder: Decoder) throws {
Expand All @@ -44,5 +47,6 @@ public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible

// Added in 2023.3
port = try container.decodeIfPresent(RelayConstraint<UInt16>.self, forKey: .port) ?? .any
filter = try container.decodeIfPresent(RelayConstraint<RelayFilter>.self, forKey: .filter) ?? .any
}
}
25 changes: 25 additions & 0 deletions ios/MullvadTypes/RelayFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// RelayFilter.swift
// MullvadVPN
//
// Created by Jon Petersson on 2023-06-08.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation

public struct RelayFilter: Codable, Equatable {
public enum Ownership: Codable {
case any
case owned
case rented
}

public var ownership: Ownership
public var providers: RelayConstraint<[String]>

public init(ownership: Ownership = .any, providers: RelayConstraint<[String]> = .any) {
self.ownership = ownership
self.providers = providers
}
}
96 changes: 81 additions & 15 deletions ios/MullvadVPN.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions ios/MullvadVPN/Coordinators/RelayFilterCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// RelayFilterCoordinator.swift
// MullvadVPN
//
// Created by Jon Petersson on 2023-06-14.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import MullvadTypes
import RelayCache
import Routing
import UIKit

class RelayFilterCoordinator: Coordinator, Presentable, RelayCacheTrackerObserver {
private let tunnelManager: TunnelManager
private let relayCacheTracker: RelayCacheTracker
private var cachedRelays: CachedRelays?

let navigationController: UINavigationController

var presentedViewController: UIViewController {
return navigationController
}

var relayFilterViewController: RelayFilterViewController? {
return navigationController.viewControllers.first {
$0 is RelayFilterViewController
} as? RelayFilterViewController
}

var relayFilter: RelayFilter {
switch tunnelManager.settings.relayConstraints.filter {
case .any:
return RelayFilter()
case let .only(filter):
return filter
}
}

var didFinish: ((RelayFilterCoordinator, RelayFilter?) -> Void)?

init(
navigationController: UINavigationController,
tunnelManager: TunnelManager,
relayCacheTracker: RelayCacheTracker
) {
self.navigationController = navigationController
self.tunnelManager = tunnelManager
self.relayCacheTracker = relayCacheTracker
}

func start() {
let relayFilterViewController = RelayFilterViewController()

relayFilterViewController.onApplyFilter = { [weak self] filter in
guard let self else { return }

var relayConstraints = tunnelManager.settings.relayConstraints
relayConstraints.filter = .only(filter)

tunnelManager.setRelayConstraints(relayConstraints)

didFinish?(self, filter)
}

relayFilterViewController.didFinish = { [weak self] in
guard let self else { return }

didFinish?(self, nil)
}

relayCacheTracker.addObserver(self)

if let cachedRelays = try? relayCacheTracker.getCachedRelays() {
self.cachedRelays = cachedRelays
relayFilterViewController.setCachedRelays(cachedRelays, filter: relayFilter)
}

navigationController.pushViewController(relayFilterViewController, animated: false)
}

func relayCacheTracker(
_ tracker: RelayCacheTracker,
didUpdateCachedRelays cachedRelays: CachedRelays
) {
self.cachedRelays = cachedRelays
relayFilterViewController?.setCachedRelays(cachedRelays, filter: relayFilter)
}
}
79 changes: 67 additions & 12 deletions ios/MullvadVPN/Coordinators/SelectLocationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,31 @@ import RelayCache
import Routing
import UIKit

class SelectLocationCoordinator: Coordinator, Presentable, RelayCacheTrackerObserver {
class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrackerObserver {
private let tunnelManager: TunnelManager
private let relayCacheTracker: RelayCacheTracker
private var cachedRelays: CachedRelays?

let navigationController: UINavigationController

var presentedViewController: UIViewController {
navigationController
}

private let tunnelManager: TunnelManager
private let relayCacheTracker: RelayCacheTracker
var selectLocationViewController: SelectLocationViewController? {
return navigationController.viewControllers.first {
$0 is SelectLocationViewController
} as? SelectLocationViewController
}

var relayFilter: RelayFilter {
switch tunnelManager.settings.relayConstraints.filter {
case .any:
return RelayFilter()
case let .only(filter):
return filter
}
}

var didFinish: ((SelectLocationCoordinator, RelayLocation?) -> Void)?

Expand All @@ -34,9 +50,9 @@ class SelectLocationCoordinator: Coordinator, Presentable, RelayCacheTrackerObse
}

func start() {
let controller = SelectLocationViewController()
let selectLocationViewController = SelectLocationViewController()

controller.didSelectRelay = { [weak self] relay in
selectLocationViewController.didSelectRelay = { [weak self] relay in
guard let self else { return }

var relayConstraints = tunnelManager.settings.relayConstraints
Expand All @@ -49,7 +65,25 @@ class SelectLocationCoordinator: Coordinator, Presentable, RelayCacheTrackerObse
didFinish?(self, relay)
}

controller.didFinish = { [weak self] in
selectLocationViewController.navigateToFilter = { [weak self] in
guard let self else { return }

let coordinator = makeRelayFilterCoordinator(forModalPresentation: true)
coordinator.start()

presentChild(coordinator, animated: true)
}

selectLocationViewController.didUpdateFilter = { [weak self] filter in
guard let self else { return }

var relayConstraints = tunnelManager.settings.relayConstraints
relayConstraints.filter = .only(filter)

tunnelManager.setRelayConstraints(relayConstraints)
}

selectLocationViewController.didFinish = { [weak self] in
guard let self else { return }

didFinish?(self, nil)
Expand All @@ -58,21 +92,42 @@ class SelectLocationCoordinator: Coordinator, Presentable, RelayCacheTrackerObse
relayCacheTracker.addObserver(self)

if let cachedRelays = try? relayCacheTracker.getCachedRelays() {
controller.setCachedRelays(cachedRelays)
self.cachedRelays = cachedRelays
selectLocationViewController.setCachedRelays(cachedRelays, filter: relayFilter)
}

controller.relayLocation = tunnelManager.settings.relayConstraints.location.value
selectLocationViewController.relayLocation = tunnelManager.settings.relayConstraints.location.value

navigationController.pushViewController(selectLocationViewController, animated: false)
}

private func makeRelayFilterCoordinator(forModalPresentation isModalPresentation: Bool)
-> RelayFilterCoordinator {
let navigationController = CustomNavigationController()

let relayFilterCoordinator = RelayFilterCoordinator(
navigationController: navigationController,
tunnelManager: tunnelManager,
relayCacheTracker: relayCacheTracker
)

relayFilterCoordinator.didFinish = { [weak self] coordinator, filter in
if let cachedRelays = self?.cachedRelays, let filter {
self?.selectLocationViewController?.setCachedRelays(cachedRelays, filter: filter)
}

coordinator.dismiss(animated: true)
}

navigationController.pushViewController(controller, animated: false)
return relayFilterCoordinator
}

func relayCacheTracker(
_ tracker: RelayCacheTracker,
didUpdateCachedRelays cachedRelays: CachedRelays
) {
guard let controller = navigationController.viewControllers
.first as? SelectLocationViewController else { return }
self.cachedRelays = cachedRelays

controller.setCachedRelays(cachedRelays)
selectLocationViewController?.setCachedRelays(cachedRelays, filter: relayFilter)
}
}
21 changes: 21 additions & 0 deletions ios/MullvadVPN/Extensions/Collection+Sorting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Collection+Sorting.swift
// MullvadVPN
//
// Created by Jon Petersson on 2023-06-14.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation

extension Collection where Element: StringProtocol {
public func caseInsensitiveSorted() -> [Element] {
sorted { $0.caseInsensitiveCompare($1) == .orderedAscending }
}
}

extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
public mutating func caseInsensitiveSort() {
sort { $0.caseInsensitiveCompare($1) == .orderedAscending }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class FormSheetPresentationController: UIPresentationController {
let containerView,
!isInFullScreenPresentation else { return }
let frame = view.frame
let bottomMarginFromKeyboard = adjustment > 0 ? UIMetrics.sectionSpacing : 0
let bottomMarginFromKeyboard = adjustment > 0 ? UIMetrics.TableView.sectionSpacing : 0
view.frame = CGRect(
origin: CGPoint(
x: frame.origin.x,
Expand Down
Loading