Skip to content

Commit

Permalink
Add filtering to location selection
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Petersson committed Jul 3, 2023
1 parent 07c545e commit d405196
Show file tree
Hide file tree
Showing 37 changed files with 1,502 additions and 105 deletions.
6 changes: 5 additions & 1 deletion ios/MullvadTypes/RelayConstraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,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 @@ -32,5 +35,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
}
}
74 changes: 64 additions & 10 deletions ios/MullvadVPN.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

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

import MullvadTypes
import RelayCache
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)
}
}
83 changes: 71 additions & 12 deletions ios/MullvadVPN/Coordinators/App/SelectLocationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,35 @@ import MullvadTypes
import RelayCache
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 presentationContext: UIViewController {
return navigationController
}

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 @@ -33,9 +53,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 @@ -48,7 +68,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 @@ -57,21 +95,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 }
}
}
9 changes: 9 additions & 0 deletions ios/MullvadVPN/Extensions/UIBarButtonItem+Blocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ extension UIBarButtonItem {
self.actionHandler = actionHandler
}

/**
Initialize bar button item with title and block handler.
*/
convenience init(title: String, actionHandler: @escaping ActionHandler) {
self.init(title: title, style: .plain, target: nil, action: nil)

self.actionHandler = actionHandler
}

@objc private func handleAction() {
actionHandler?()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,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
1 change: 1 addition & 0 deletions ios/MullvadVPN/TunnelManager/TunnelManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ final class TunnelManager: StorePaymentObserver {
private var _tunnelSettings = TunnelSettingsV2()

private var _tunnel: Tunnel?

private var _tunnelStatus = TunnelStatus()

/// Last processed device check.
Expand Down
Loading

0 comments on commit d405196

Please sign in to comment.