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 Oct 16, 2023
1 parent f7b9c1b commit eb69f11
Show file tree
Hide file tree
Showing 38 changed files with 1,571 additions and 110 deletions.
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)
}
}
83 changes: 71 additions & 12 deletions ios/MullvadVPN/Coordinators/SelectLocationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,35 @@ 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 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 @@ -34,9 +54,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 +69,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 +96,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 }
}
}
52 changes: 52 additions & 0 deletions ios/MullvadVPN/Extensions/UIBarButtonItem+Blocks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// UIBarButtonItem+Blocks.swift
// MullvadVPN
//
// Created by pronebird on 19/04/2023.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import UIKit

private var actionHandlerAssociatedKey = 0

extension UIBarButtonItem {
typealias ActionHandler = () -> Void

/**
Block handler assigned to bar button item.
*/
var actionHandler: ActionHandler? {
get {
objc_getAssociatedObject(self, &actionHandlerAssociatedKey) as? ActionHandler
}
set {
objc_setAssociatedObject(self, &actionHandlerAssociatedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

target = newValue == nil ? nil : self
action = newValue == nil ? nil : #selector(handleAction)
}
}

/**
Initialize bar button item with block handler.
*/
convenience init(systemItem: UIBarButtonItem.SystemItem, actionHandler: @escaping ActionHandler) {
self.init(barButtonSystemItem: systemItem, target: nil, action: nil)

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

private var _tunnel: Tunnel?

private var _tunnelStatus = TunnelStatus()

/// Last processed device check.
Expand Down
Loading

0 comments on commit eb69f11

Please sign in to comment.