Skip to content

Commit c61b7d4

Browse files
committed
Add state to new connection view
1 parent 9573f3e commit c61b7d4

15 files changed

+661
-117
lines changed

ios/MullvadVPN.xcodeproj/project.pbxproj

+29
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,9 @@
464464
7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */; };
465465
7A0EAE9A2D01B41500D3EB8B /* MainButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */; };
466466
7A0EAE9E2D01BCBF00D3EB8B /* View+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAE9D2D01BCBF00D3EB8B /* View+Size.swift */; };
467+
7A0EAEA02D0333CE00D3EB8B /* Color+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAE9F2D0333CB00D3EB8B /* Color+Helpers.swift */; };
468+
7A0EAEA22D033D5D00D3EB8B /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAEA12D033D5A00D3EB8B /* BlurView.swift */; };
469+
7A0EAEA42D06DF8C00D3EB8B /* ConnectionViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAEA32D06DF8200D3EB8B /* ConnectionViewViewModel.swift */; };
467470
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */; };
468471
7A12D0762B062D5C00E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */; };
469472
7A12D0772B062D6500E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */; };
@@ -658,6 +661,8 @@
658661
7AF9BE972A41C71F00DBFEDB /* ChipViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */; };
659662
7AFBE38B2D09AAFF002335FC /* SinglehopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */; };
660663
7AFBE38D2D09AB2E002335FC /* MultihopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */; };
664+
7AFBE3872D084C9D002335FC /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */; };
665+
7AFBE3892D089163002335FC /* FI_TunnelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */; };
661666
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DA2B503D7700EF8C96 /* RelayTests.swift */; };
662667
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */; };
663668
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */; };
@@ -1843,6 +1848,9 @@
18431848
7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Coordinator+Router.swift"; sourceTree = "<group>"; };
18441849
7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainButtonStyle.swift; sourceTree = "<group>"; };
18451850
7A0EAE9D2D01BCBF00D3EB8B /* View+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Size.swift"; sourceTree = "<group>"; };
1851+
7A0EAE9F2D0333CB00D3EB8B /* Color+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Helpers.swift"; sourceTree = "<group>"; };
1852+
7A0EAEA12D033D5A00D3EB8B /* BlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = "<group>"; };
1853+
7A0EAEA32D06DF8200D3EB8B /* ConnectionViewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionViewViewModel.swift; sourceTree = "<group>"; };
18461854
7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = "<group>"; };
18471855
7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlertPresenter.swift; sourceTree = "<group>"; };
18481856
7A1A26442A29CEF700B978AA /* RelayFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterViewController.swift; sourceTree = "<group>"; };
@@ -2014,6 +2022,8 @@
20142022
7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewCell.swift; sourceTree = "<group>"; };
20152023
7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = "<group>"; };
20162024
7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = "<group>"; };
2025+
7AFBE3862D084C96002335FC /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
2026+
7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FI_TunnelViewController.swift; sourceTree = "<group>"; };
20172027
85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsSmoke.xctestplan; sourceTree = "<group>"; };
20182028
850201DA2B503D7700EF8C96 /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = "<group>"; };
20192029
850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationPage.swift; sourceTree = "<group>"; };
@@ -3015,6 +3025,7 @@
30153025
isa = PBXGroup;
30163026
children = (
30173027
7A5869962B32EA4500640D27 /* AppButton.swift */,
3028+
7A0EAEA12D033D5A00D3EB8B /* BlurView.swift */,
30183029
7A9FA1412A2E3306000B728D /* CheckboxView.swift */,
30193030
5868585424054096000B8131 /* CustomButton.swift */,
30203031
58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */,
@@ -3085,6 +3096,7 @@
30853096
587EB669270EFACB00123C75 /* CharacterSet+IPAddress.swift */,
30863097
58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */,
30873098
7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */,
3099+
7A0EAE9F2D0333CB00D3EB8B /* Color+Helpers.swift */,
30883100
7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */,
30893101
5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */,
30903102
58DFF7CF2B02560400F864E0 /* NSAttributedString+Extensions.swift */,
@@ -3612,6 +3624,7 @@
36123624
A9D9A4C12C36D53C004088DD /* MullvadRustRuntimeTests */,
36133625
58CE5E61224146200008646E /* Products */,
36143626
584F991F2902CBDD001F858D /* Frameworks */,
3627+
7A0EAE982D01B29E00D3EB8B /* Recovered References */,
36153628
);
36163629
sourceTree = "<group>";
36173630
};
@@ -3912,6 +3925,14 @@
39123925
path = Edit;
39133926
sourceTree = "<group>";
39143927
};
3928+
7A0EAE982D01B29E00D3EB8B /* Recovered References */ = {
3929+
isa = PBXGroup;
3930+
children = (
3931+
7AA1309C2D0072F900640DF9 /* View+Size.swift */,
3932+
);
3933+
name = "Recovered References";
3934+
sourceTree = "<group>";
3935+
};
39153936
7A2960F72A964A3500389B82 /* Alert */ = {
39163937
isa = PBXGroup;
39173938
children = (
@@ -4046,7 +4067,10 @@
40464067
7AA130972CFF364F00640DF9 /* FeatureIndicators */ = {
40474068
isa = PBXGroup;
40484069
children = (
4070+
7AFBE3862D084C96002335FC /* ActivityIndicator.swift */,
40494071
7AA130982CFF365A00640DF9 /* ConnectionView.swift */,
4072+
7A0EAEA32D06DF8200D3EB8B /* ConnectionViewViewModel.swift */,
4073+
7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */,
40504074
);
40514075
path = FeatureIndicators;
40524076
sourceTree = "<group>";
@@ -5817,6 +5841,7 @@
58175841
F0ADC3742CD3C47400A1AD97 /* ChipFlowLayout.swift in Sources */,
58185842
587B75412668FD7800DEF7E9 /* AccountExpirySystemNotificationProvider.swift in Sources */,
58195843
587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */,
5844+
7AFBE3872D084C9D002335FC /* ActivityIndicator.swift in Sources */,
58205845
F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */,
58215846
58DFF7D82B02774C00F864E0 /* ListItemPickerViewController.swift in Sources */,
58225847
5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */,
@@ -5838,6 +5863,7 @@
58385863
7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */,
58395864
7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */,
58405865
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */,
5866+
7AFBE3892D089163002335FC /* FI_TunnelViewController.swift in Sources */,
58415867
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */,
58425868
5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */,
58435869
5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */,
@@ -5872,6 +5898,7 @@
58725898
7AA1309B2D0048D800640DF9 /* MainButton.swift in Sources */,
58735899
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
58745900
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */,
5901+
7A0EAEA02D0333CE00D3EB8B /* Color+Helpers.swift in Sources */,
58755902
7A8A19052CE4E9A9000BCB5B /* SwitchRowView.swift in Sources */,
58765903
A91614D62B10B26B00F416EB /* TunnelControlViewModel.swift in Sources */,
58775904
7A5869972B32EA4500640D27 /* AppButton.swift in Sources */,
@@ -6061,6 +6088,8 @@
60616088
7A6F2FA92AFD0842006D0856 /* CustomDNSDataSource.swift in Sources */,
60626089
58EF580B25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift in Sources */,
60636090
5892A45E265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift in Sources */,
6091+
7A0EAEA42D06DF8C00D3EB8B /* ConnectionViewViewModel.swift in Sources */,
6092+
7A0EAEA22D033D5D00D3EB8B /* BlurView.swift in Sources */,
60646093
580909D32876D09A0078138D /* RevokedDeviceViewController.swift in Sources */,
60656094
58FF9FF02B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift in Sources */,
60666095
7A8A19162CEF269E000BCB5B /* MultihopTunnelSettingsViewModel.swift in Sources */,

ios/MullvadVPN/Coordinators/TunnelCoordinator.swift

+10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import UIKit
1111

1212
class TunnelCoordinator: Coordinator, Presenting {
1313
private let tunnelManager: TunnelManager
14+
15+
#if DEBUG
16+
private let controller: FI_TunnelViewController
17+
#else
1418
private let controller: TunnelViewController
19+
#endif
1520

1621
private var tunnelObserver: TunnelObserver?
1722

@@ -35,7 +40,12 @@ class TunnelCoordinator: Coordinator, Presenting {
3540
tunnelManager: tunnelManager,
3641
outgoingConnectionService: outgoingConnectionService
3742
)
43+
44+
#if DEBUG
45+
controller = FI_TunnelViewController(interactor: interactor)
46+
#else
3847
controller = TunnelViewController(interactor: interactor)
48+
#endif
3949

4050
super.init()
4151

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// Color.swift
3+
// MullvadVPN
4+
//
5+
// Created by Jon Petersson on 2024-12-06.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
extension Color {
12+
/// Returns the color darker by the given percent (in range from 0..1)
13+
func darkened(by percent: CGFloat) -> Color? {
14+
UIColor(self).darkened(by: percent)?.color
15+
}
16+
}

ios/MullvadVPN/TunnelManager/TunnelState+UI.swift

+75-22
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
import UIKit
1010

1111
extension TunnelState {
12+
enum TunnelControlActionButton {
13+
case connect
14+
case disconnect
15+
case cancel
16+
}
17+
1218
var textColorForSecureLabel: UIColor {
1319
switch self {
1420
case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingEphemeralPeer:
1521
.white
16-
1722
case .connected:
1823
.successColor
19-
2024
case .disconnecting, .disconnected, .pendingReconnect, .waitingForConnectivity(.noNetwork), .error:
2125
.dangerColor
2226
}
@@ -65,6 +69,7 @@ extension TunnelState {
6569
comment: ""
6670
)
6771
}
72+
6873
case let .connected(_, isPostQuantum, _):
6974
if isPostQuantum {
7075
NSLocalizedString(
@@ -77,7 +82,7 @@ extension TunnelState {
7782
NSLocalizedString(
7883
"TUNNEL_STATE_CONNECTED",
7984
tableName: "Main",
80-
value: "Secure connection",
85+
value: "Connected",
8186
comment: ""
8287
)
8388
}
@@ -89,6 +94,7 @@ extension TunnelState {
8994
value: "Disconnecting",
9095
comment: ""
9196
)
97+
9298
case .disconnecting(.reconnect), .pendingReconnect:
9399
NSLocalizedString(
94100
"TUNNEL_STATE_PENDING_RECONNECT",
@@ -123,7 +129,7 @@ extension TunnelState {
123129
}
124130
}
125131

126-
var localizedTitleForSelectLocationButton: String? {
132+
var localizedTitleForSelectLocationButton: String {
127133
switch self {
128134
case .disconnecting(.reconnect), .pendingReconnect:
129135
NSLocalizedString(
@@ -159,24 +165,6 @@ extension TunnelState {
159165
}
160166
}
161167

162-
func secureConnectionLabel(isPostQuantum: Bool) -> String {
163-
if isPostQuantum {
164-
NSLocalizedString(
165-
"TUNNEL_STATE_PQ_CONNECTING_ACCESSIBILITY_LABEL",
166-
tableName: "Main",
167-
value: "Creating quantum secure connection",
168-
comment: ""
169-
)
170-
} else {
171-
NSLocalizedString(
172-
"TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
173-
tableName: "Main",
174-
value: "Creating secure connection",
175-
comment: ""
176-
)
177-
}
178-
}
179-
180168
var localizedAccessibilityLabel: String {
181169
switch self {
182170
case let .connecting(_, isPostQuantum, _):
@@ -263,4 +251,69 @@ extension TunnelState {
263251
)
264252
}
265253
}
254+
255+
var actionButton: TunnelControlActionButton {
256+
switch self {
257+
case .disconnected, .disconnecting(.nothing), .waitingForConnectivity(.noNetwork):
258+
.connect
259+
case .connecting, .pendingReconnect, .disconnecting(.reconnect), .waitingForConnectivity(.noConnection):
260+
.cancel
261+
case .negotiatingEphemeralPeer:
262+
.cancel
263+
case .connected, .reconnecting, .error:
264+
.disconnect
265+
}
266+
}
267+
268+
var titleForCountryAndCity: String? {
269+
guard isSecured, let tunnelRelays = relays else {
270+
return nil
271+
}
272+
273+
return "\(tunnelRelays.exit.location.country), \(tunnelRelays.exit.location.city)"
274+
}
275+
276+
func titleForServer(daitaEnabled: Bool) -> String? {
277+
guard isSecured, let tunnelRelays = relays else {
278+
return nil
279+
}
280+
281+
let exitName = tunnelRelays.exit.hostname
282+
let entryName = tunnelRelays.entry?.hostname
283+
let usingDaita = daitaEnabled == true
284+
285+
return if let entryName {
286+
String(format: NSLocalizedString(
287+
"CONNECT_PANEL_TITLE",
288+
tableName: "Main",
289+
value: "%@ via %@\(usingDaita ? " using DAITA" : "")",
290+
comment: ""
291+
), exitName, entryName)
292+
} else {
293+
String(format: NSLocalizedString(
294+
"CONNECT_PANEL_TITLE",
295+
tableName: "Main",
296+
value: "%@\(usingDaita ? " using DAITA" : "")",
297+
comment: ""
298+
), exitName)
299+
}
300+
}
301+
302+
func secureConnectionLabel(isPostQuantum: Bool) -> String {
303+
if isPostQuantum {
304+
NSLocalizedString(
305+
"TUNNEL_STATE_PQ_CONNECTING_ACCESSIBILITY_LABEL",
306+
tableName: "Main",
307+
value: "Creating quantum secure connection",
308+
comment: ""
309+
)
310+
} else {
311+
NSLocalizedString(
312+
"TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
313+
tableName: "Main",
314+
value: "Creating secure connection",
315+
comment: ""
316+
)
317+
}
318+
}
266319
}

ios/MullvadVPN/TunnelManager/TunnelState.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ enum TunnelState: Equatable, CustomStringConvertible {
8484
case let .connecting(tunnelRelays, isPostQuantum, isDaita):
8585
if let tunnelRelays {
8686
"""
87-
connecting \(isPostQuantum ? "(PQ) " : "")\
88-
daita: \(isDaita) \
87+
connecting \(isPostQuantum ? "(PQ) " : ""), \
88+
daita: \(isDaita), \
8989
to \(tunnelRelays.exit.hostname)\
9090
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
9191
"""
@@ -94,8 +94,8 @@ enum TunnelState: Equatable, CustomStringConvertible {
9494
}
9595
case let .connected(tunnelRelays, isPostQuantum, isDaita):
9696
"""
97-
connected \(isPostQuantum ? "(PQ) " : "")\
98-
daita: \(isDaita) \
97+
connected \(isPostQuantum ? "(PQ) " : ""), \
98+
daita: \(isDaita), \
9999
to \(tunnelRelays.exit.hostname)\
100100
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
101101
"""
@@ -105,8 +105,8 @@ enum TunnelState: Equatable, CustomStringConvertible {
105105
"disconnected"
106106
case let .reconnecting(tunnelRelays, isPostQuantum, isDaita):
107107
"""
108-
reconnecting \(isPostQuantum ? "(PQ) " : "")\
109-
daita: \(isDaita) \
108+
reconnecting \(isPostQuantum ? "(PQ) " : ""), \
109+
daita: \(isDaita), \
110110
to \(tunnelRelays.exit.hostname)\
111111
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
112112
"""
@@ -117,8 +117,8 @@ enum TunnelState: Equatable, CustomStringConvertible {
117117
case let .negotiatingEphemeralPeer(tunnelRelays, _, isPostQuantum, isDaita):
118118
"""
119119
negotiating key with exit relay: \(tunnelRelays.exit.hostname)\
120-
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")\
121-
, isPostQuantum: \(isPostQuantum), isDaita: \(isDaita)
120+
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? ""), \
121+
isPostQuantum: \(isPostQuantum), isDaita: \(isDaita)
122122
"""
123123
}
124124
}

0 commit comments

Comments
 (0)