Skip to content

Commit d715098

Browse files
author
Jon Petersson
committed
Split select location view into two sections
1 parent 75c0537 commit d715098

31 files changed

+1094
-513
lines changed

ios/.swiftlint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ disabled_rules:
77
- type_body_length
88
- opening_brace # Differs from Google swift guidelines enforced by swiftformat
99
- trailing_comma
10+
- switch_case_alignment # Enables expressions such as [return switch location {}]
1011
opt_in_rules:
1112
- empty_count
1213

ios/MullvadVPN.xcodeproj/project.pbxproj

+53-25
Large diffs are not rendered by default.

ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
6565
}()
6666

6767
private var splitTunnelCoordinator: TunnelCoordinator?
68-
private var splitLocationCoordinator: SelectLocationCoordinator?
68+
private var splitLocationCoordinator: LocationCoordinator?
6969

7070
private let tunnelManager: TunnelManager
7171
private let storePaymentManager: StorePaymentManager
@@ -488,17 +488,17 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
488488

489489
private func setupSplitView() {
490490
let tunnelCoordinator = makeTunnelCoordinator()
491-
let selectLocationCoordinator = makeSelectLocationCoordinator(forModalPresentation: false)
491+
let locationCoordinator = makeLocationCoordinator(forModalPresentation: false)
492492

493493
addChild(tunnelCoordinator)
494-
addChild(selectLocationCoordinator)
494+
addChild(locationCoordinator)
495495

496496
splitTunnelCoordinator = tunnelCoordinator
497-
splitLocationCoordinator = selectLocationCoordinator
497+
splitLocationCoordinator = locationCoordinator
498498

499499
splitViewController.delegate = self
500500
splitViewController.viewControllers = [
501-
selectLocationCoordinator.navigationController,
501+
locationCoordinator.navigationController,
502502
tunnelCoordinator.rootViewController,
503503
]
504504

@@ -508,7 +508,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
508508
.safeAreaLayoutGuide
509509

510510
tunnelCoordinator.start()
511-
selectLocationCoordinator.start()
511+
locationCoordinator.start()
512512
}
513513

514514
private func presentTOS(animated: Bool, completion: @escaping (Coordinator) -> Void) {
@@ -634,7 +634,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
634634
}
635635

636636
private func presentSelectLocation(animated: Bool, completion: @escaping (Coordinator) -> Void) {
637-
let coordinator = makeSelectLocationCoordinator(forModalPresentation: true)
637+
let coordinator = makeLocationCoordinator(forModalPresentation: true)
638638
coordinator.start()
639639

640640
presentChild(coordinator, animated: animated) {
@@ -702,24 +702,24 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
702702
return tunnelCoordinator
703703
}
704704

705-
private func makeSelectLocationCoordinator(forModalPresentation isModalPresentation: Bool)
706-
-> SelectLocationCoordinator {
705+
private func makeLocationCoordinator(forModalPresentation isModalPresentation: Bool)
706+
-> LocationCoordinator {
707707
let navigationController = CustomNavigationController()
708708
navigationController.isNavigationBarHidden = !isModalPresentation
709709

710-
let selectLocationCoordinator = SelectLocationCoordinator(
710+
let locationCoordinator = LocationCoordinator(
711711
navigationController: navigationController,
712712
tunnelManager: tunnelManager,
713713
relayCacheTracker: relayCacheTracker
714714
)
715715

716-
selectLocationCoordinator.didFinish = { [weak self] _, _ in
716+
locationCoordinator.didFinish = { [weak self] _, _ in
717717
if isModalPresentation {
718718
self?.router.dismiss(.selectLocation, animated: true)
719719
}
720720
}
721721

722-
return selectLocationCoordinator
722+
return locationCoordinator
723723
}
724724

725725
private func presentAccount(animated: Bool, completion: @escaping (Coordinator) -> Void) {

ios/MullvadVPN/Coordinators/SelectLocationCoordinator.swift ios/MullvadVPN/Coordinators/LocationCoordinator.swift

+13-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// SelectLocationCoordinator.swift
2+
// LocationCoordinator.swift
33
// MullvadVPN
44
//
55
// Created by pronebird on 29/01/2023.
@@ -11,9 +11,7 @@ import MullvadTypes
1111
import Routing
1212
import UIKit
1313

14-
import MullvadSettings
15-
16-
class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrackerObserver {
14+
class LocationCoordinator: Coordinator, Presentable, Presenting, RelayCacheTrackerObserver {
1715
private let tunnelManager: TunnelManager
1816
private let relayCacheTracker: RelayCacheTracker
1917
private var cachedRelays: CachedRelays?
@@ -24,10 +22,10 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
2422
navigationController
2523
}
2624

27-
var selectLocationViewController: SelectLocationViewController? {
25+
var selectLocationViewController: LocationViewController? {
2826
return navigationController.viewControllers.first {
29-
$0 is SelectLocationViewController
30-
} as? SelectLocationViewController
27+
$0 is LocationViewController
28+
} as? LocationViewController
3129
}
3230

3331
var relayFilter: RelayFilter {
@@ -39,7 +37,7 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
3937
}
4038
}
4139

42-
var didFinish: ((SelectLocationCoordinator, RelayLocation?) -> Void)?
40+
var didFinish: ((LocationCoordinator, [RelayLocation]) -> Void)?
4341

4442
init(
4543
navigationController: UINavigationController,
@@ -52,22 +50,22 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
5250
}
5351

5452
func start() {
55-
let selectLocationViewController = SelectLocationViewController()
53+
let selectLocationViewController = LocationViewController()
5654

57-
selectLocationViewController.didSelectRelay = { [weak self] relay in
55+
selectLocationViewController.didSelectRelays = { [weak self] locations, customListId in
5856
guard let self else { return }
5957

6058
var relayConstraints = tunnelManager.settings.relayConstraints
6159
relayConstraints.locations = .only(RelayLocations(
62-
locations: [relay],
63-
customListId: nil
60+
locations: locations,
61+
customListId: customListId
6462
))
6563

6664
tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) {
6765
self.tunnelManager.startTunnel()
6866
}
6967

70-
didFinish?(self, relay)
68+
didFinish?(self, locations)
7169
}
7270

7371
selectLocationViewController.navigateToFilter = { [weak self] in
@@ -91,7 +89,7 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
9189
selectLocationViewController.didFinish = { [weak self] in
9290
guard let self else { return }
9391

94-
didFinish?(self, nil)
92+
didFinish?(self, [])
9593
}
9694

9795
relayCacheTracker.addObserver(self)
@@ -101,8 +99,7 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
10199
selectLocationViewController.setCachedRelays(cachedRelays, filter: relayFilter)
102100
}
103101

104-
selectLocationViewController.relayLocation =
105-
tunnelManager.settings.relayConstraints.locations.value?.locations.first
102+
selectLocationViewController.relayLocations = tunnelManager.settings.relayConstraints.locations.value
106103

107104
navigationController.pushViewController(selectLocationViewController, animated: false)
108105
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// SettingsValidationErrorConfiguration.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2024-02-16.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
struct SettingsValidationErrorConfiguration: UIContentConfiguration, Equatable {
12+
var errors: [CustomListFieldValidationError] = []
13+
var directionalLayoutMargins: NSDirectionalEdgeInsets = UIMetrics.SettingsCell.settingsValidationErrorLayoutMargins
14+
15+
func makeContentView() -> UIView & UIContentView {
16+
return SettingsValidationErrorContentView(configuration: self)
17+
}
18+
19+
func updated(for state: UIConfigurationState) -> Self {
20+
return self
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//
2+
// SettingsValidationErrorContentView.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2024-02-16.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
class SettingsValidationErrorContentView: UIView, UIContentView {
12+
let contentView = UIStackView()
13+
14+
var icon: UIImageView {
15+
let view = UIImageView(image: UIImage(resource: .iconAlert).withTintColor(.dangerColor))
16+
view.heightAnchor.constraint(equalToConstant: 14).isActive = true
17+
view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1).isActive = true
18+
return view
19+
}
20+
21+
var configuration: UIContentConfiguration {
22+
get {
23+
actualConfiguration
24+
}
25+
set {
26+
guard let newConfiguration = newValue as? SettingsValidationErrorConfiguration else { return }
27+
28+
let previousConfiguration = actualConfiguration
29+
actualConfiguration = newConfiguration
30+
31+
configureSubviews(previousConfiguration: previousConfiguration)
32+
}
33+
}
34+
35+
private var actualConfiguration: SettingsValidationErrorConfiguration
36+
37+
func supports(_ configuration: UIContentConfiguration) -> Bool {
38+
configuration is SettingsValidationErrorConfiguration
39+
}
40+
41+
init(configuration: SettingsValidationErrorConfiguration) {
42+
actualConfiguration = configuration
43+
44+
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
45+
46+
addSubviews()
47+
configureSubviews()
48+
}
49+
50+
required init?(coder: NSCoder) {
51+
fatalError("init(coder:) has not been implemented")
52+
}
53+
54+
private func addSubviews() {
55+
contentView.axis = .vertical
56+
contentView.spacing = 6
57+
58+
addConstrainedSubviews([contentView]) {
59+
contentView.pinEdgesToSuperviewMargins()
60+
}
61+
}
62+
63+
private func configureSubviews(previousConfiguration: SettingsValidationErrorConfiguration? = nil) {
64+
guard actualConfiguration != previousConfiguration else { return }
65+
66+
configureLayoutMargins()
67+
68+
contentView.arrangedSubviews.forEach { view in
69+
view.removeFromSuperview()
70+
}
71+
72+
actualConfiguration.errors.forEach { error in
73+
let label = UILabel()
74+
label.text = error.errorDescription
75+
label.numberOfLines = 0
76+
label.font = .systemFont(ofSize: 13)
77+
label.textColor = .white.withAlphaComponent(0.6)
78+
79+
let stackView = UIStackView(arrangedSubviews: [icon, label])
80+
stackView.alignment = .top
81+
stackView.spacing = 6
82+
83+
contentView.addArrangedSubview(stackView)
84+
}
85+
}
86+
87+
private func configureLayoutMargins() {
88+
directionalLayoutMargins = actualConfiguration.directionalLayoutMargins
89+
}
90+
}

ios/MullvadVPN/Extensions/UIBackgroundConfiguration+Extensions.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ extension UIBackgroundConfiguration {
2121
/// - Returns: a background configuration
2222
static func mullvadListPlainCell() -> UIBackgroundConfiguration {
2323
var config = listPlainCell()
24-
config.backgroundColor = UIColor.Cell.backgroundColor
24+
config.backgroundColor = UIColor.Cell.Background.normal
2525
return config
2626
}
2727

2828
/// Returns the corresponding grouped cell background configuration adapted for Mullvad UI.
2929
/// - Returns: a background configuration
3030
static func mullvadListGroupedCell() -> UIBackgroundConfiguration {
3131
var config = listGroupedCell()
32-
config.backgroundColor = UIColor.Cell.backgroundColor
32+
config.backgroundColor = UIColor.Cell.Background.normal
3333
return config
3434
}
3535

@@ -58,20 +58,20 @@ extension UICellConfigurationState {
5858
switch selectionType {
5959
case .dimmed:
6060
if isSelected || isHighlighted {
61-
UIColor.Cell.selectedAltBackgroundColor
61+
UIColor.Cell.Background.selectedAlt
6262
} else if isDisabled {
63-
UIColor.Cell.disabledBackgroundColor
63+
UIColor.Cell.Background.disabled
6464
} else {
65-
UIColor.Cell.backgroundColor
65+
UIColor.Cell.Background.normal
6666
}
6767

6868
case .green:
6969
if isSelected || isHighlighted {
70-
UIColor.Cell.selectedBackgroundColor
70+
UIColor.Cell.Background.selected
7171
} else if isDisabled {
72-
UIColor.Cell.disabledBackgroundColor
72+
UIColor.Cell.Background.disabled
7373
} else {
74-
UIColor.Cell.backgroundColor
74+
UIColor.Cell.Background.normal
7575
}
7676
}
7777
}

ios/MullvadVPN/UI appearance/UIColor+Palette.swift

+13-14
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,18 @@ extension UIColor {
8686

8787
// Cells
8888
enum Cell {
89-
static let backgroundColor = primaryColor
90-
static let disabledBackgroundColor = backgroundColor.darkened(by: 0.3)!
91-
92-
static let selectedBackgroundColor = successColor
93-
static let disabledSelectedBackgroundColor = selectedBackgroundColor.darkened(by: 0.3)!
94-
95-
static let selectedAltBackgroundColor = backgroundColor.darkened(by: 0.2)!
89+
enum Background {
90+
static let indentationLevelZero = primaryColor
91+
static let indentationLevelOne = UIColor(red: 0.15, green: 0.23, blue: 0.33, alpha: 1.0)
92+
static let indentationLevelTwo = UIColor(red: 0.13, green: 0.20, blue: 0.30, alpha: 1.0)
93+
static let indentationLevelThree = UIColor(red: 0.11, green: 0.17, blue: 0.27, alpha: 1.0)
94+
95+
static let normal = indentationLevelZero
96+
static let disabled = normal.darkened(by: 0.3)!
97+
static let selected = successColor
98+
static let disabledSelected = selected.darkened(by: 0.3)!
99+
static let selectedAlt = normal.darkened(by: 0.2)!
100+
}
96101

97102
static let titleTextColor = UIColor.white
98103
static let detailTextColor = UIColor(white: 1.0, alpha: 0.8)
@@ -109,13 +114,7 @@ extension UIColor {
109114
static let footerTextColor = UIColor(white: 1.0, alpha: 0.6)
110115
}
111116

112-
enum SubCell {
113-
static let backgroundColor = UIColor(red: 0.15, green: 0.23, blue: 0.33, alpha: 1.0)
114-
}
115-
116-
enum SubSubCell {
117-
static let backgroundColor = UIColor(red: 0.13, green: 0.20, blue: 0.30, alpha: 1.0)
118-
}
117+
enum SettingsCellBackground {}
119118

120119
enum HeaderBar {
121120
static let defaultBackgroundColor = primaryColor

ios/MullvadVPN/UI appearance/UIMetrics.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ extension UIMetrics {
173173
static let contentInsets = UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24)
174174

175175
/// Common layout margins for location cell presentation
176-
static let selectLocationCellLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 28, bottom: 16, trailing: 12)
176+
static let locationCellLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 28, bottom: 16, trailing: 12)
177177

178178
/// Layout margins used by content heading displayed below the large navigation title.
179179
static let contentHeadingLayoutMargins = NSDirectionalEdgeInsets(top: 8, leading: 24, bottom: 24, trailing: 24)

0 commit comments

Comments
 (0)