summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-06-21 14:15:56 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-07-07 11:12:15 +0200
commit9f313d321dd0361545e71688bde320987eed8d48 (patch)
tree944fd328f24a5072da6a4512508c90991bdef24b
parentafb24305d9da874b0f20802484411a7879a3d7ca (diff)
downloadmullvadvpn-9f313d321dd0361545e71688bde320987eed8d48.tar.xz
mullvadvpn-9f313d321dd0361545e71688bde320987eed8d48.zip
Add option to change wireguard port to vpn settings
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt93
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardPortInfoDialog.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt44
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt45
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/WireguardConstant.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/VpnSettingsFragment.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt12
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ConstraintExtensions.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt82
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt55
-rw-r--r--android/app/src/main/res/values-da/strings.xml1
-rw-r--r--android/app/src/main/res/values-de/strings.xml1
-rw-r--r--android/app/src/main/res/values-es/strings.xml1
-rw-r--r--android/app/src/main/res/values-fi/strings.xml1
-rw-r--r--android/app/src/main/res/values-fr/strings.xml1
-rw-r--r--android/app/src/main/res/values-it/strings.xml1
-rw-r--r--android/app/src/main/res/values-ja/strings.xml1
-rw-r--r--android/app/src/main/res/values-ko/strings.xml1
-rw-r--r--android/app/src/main/res/values-my/strings.xml1
-rw-r--r--android/app/src/main/res/values-nb/strings.xml1
-rw-r--r--android/app/src/main/res/values-nl/strings.xml1
-rw-r--r--android/app/src/main/res/values-pl/strings.xml1
-rw-r--r--android/app/src/main/res/values-pt/strings.xml1
-rw-r--r--android/app/src/main/res/values-ru/strings.xml1
-rw-r--r--android/app/src/main/res/values-sv/strings.xml1
-rw-r--r--android/app/src/main/res/values-th/strings.xml1
-rw-r--r--android/app/src/main/res/values-tr/strings.xml1
-rw-r--r--android/app/src/main/res/values-zh-rCN/strings.xml1
-rw-r--r--android/app/src/main/res/values-zh-rTW/strings.xml1
-rw-r--r--android/app/src/main/res/values/strings.xml2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt100
-rw-r--r--gui/locales/messages.pot3
35 files changed, 468 insertions, 50 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt
index e66e779313..fd3be6092f 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt
@@ -21,6 +21,10 @@ import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_LAST_ITEM_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Port
+import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.model.QuantumResistantState
import net.mullvad.mullvadvpn.onNodeWithTagAndChildrenText
import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem
@@ -46,14 +50,18 @@ class VpnSettingsScreenTest {
toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow()
)
}
+
+ composeTestRule.apply {
+ onNodeWithText("WireGuard MTU").assertExists()
+ onNodeWithText("Default").assertExists()
+ }
+
composeTestRule
.onNodeWithTag(LAZY_LIST_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG))
// Assert
composeTestRule.apply {
- onNodeWithText("WireGuard MTU").assertExists()
- onNodeWithText("Default").assertExists()
onNodeWithText("Use custom DNS server").assertExists()
onNodeWithText("Add a server").assertDoesNotExist()
}
@@ -603,6 +611,87 @@ class VpnSettingsScreenTest {
composeTestRule.onNodeWithText("Got it!").assertExists()
}
+ @Test
+ fun testShowWireguardPortOptions() {
+ // Arrange
+ composeTestRule.setContent {
+ VpnSettingsScreen(
+ uiState =
+ VpnSettingsUiState.DefaultUiState(
+ selectedWireguardPort = Constraint.Only(Port(53))
+ ),
+ toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow()
+ )
+ }
+ composeTestRule
+ .onNodeWithTag(LAZY_LIST_TEST_TAG)
+ .performScrollToNode(
+ hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53))
+ )
+
+ // Assert
+ composeTestRule
+ .onNodeWithTagAndChildrenText(
+ testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820),
+ text = "51820"
+ )
+ .assertExists()
+ }
+
+ @Test
+ fun testSelectWireguardPortOption() {
+ // Arrange
+ val mockSelectWireguardPortSelectionListener: (Constraint<Port>) -> Unit =
+ mockk(relaxed = true)
+ composeTestRule.setContent {
+ VpnSettingsScreen(
+ uiState =
+ VpnSettingsUiState.DefaultUiState(
+ selectedWireguardPort = Constraint.Only(Port(53))
+ ),
+ onWireguardPortSelected = mockSelectWireguardPortSelectionListener,
+ toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow()
+ )
+ }
+ composeTestRule
+ .onNodeWithTag(LAZY_LIST_TEST_TAG)
+ .performScrollToNode(
+ hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53))
+ )
+
+ // Assert
+ composeTestRule
+ .onNodeWithTagAndChildrenText(
+ testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820),
+ text = "51820"
+ )
+ .performClick()
+ verify(exactly = 1) {
+ mockSelectWireguardPortSelectionListener.invoke(Constraint.Only(Port(51820)))
+ }
+ }
+
+ @Test
+ fun testShowWireguardPortInfo() {
+ // Arrange
+ composeTestRule.setContent {
+ VpnSettingsScreen(
+ uiState =
+ VpnSettingsUiState.WireguardPortInfoDialogUiState(
+ availablePortRanges = listOf(PortRange(53, 53), PortRange(120, 121))
+ ),
+ toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule
+ .onNodeWithText(
+ "The automatic setting will randomly choose from the valid port ranges shown below."
+ )
+ .assertExists()
+ }
+
companion object {
private const val LOCAL_DNS_SERVER_WARNING =
"The local DNS server will not work unless you enable " +
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt
index 6025abb281..e8c0518233 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt
@@ -115,10 +115,15 @@ internal fun BaseCell(
}
@Composable
-internal fun BaseCellTitle(title: String, style: TextStyle, modifier: Modifier = Modifier) {
+internal fun BaseCellTitle(
+ title: String,
+ style: TextStyle,
+ modifier: Modifier = Modifier,
+ textAlign: TextAlign = TextAlign.Center
+) {
Text(
text = title,
- textAlign = TextAlign.Center,
+ textAlign = textAlign,
style = style,
color = MaterialTheme.colorScheme.onPrimary,
modifier = modifier.wrapContentWidth(align = Alignment.End).wrapContentHeight()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardPortInfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardPortInfoDialog.kt
new file mode 100644
index 0000000000..680b395898
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardPortInfoDialog.kt
@@ -0,0 +1,28 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.model.PortRange
+
+@Composable
+fun WireguardPortInfoDialog(portRanges: List<PortRange>, onDismiss: () -> Unit) {
+ InfoDialog(
+ message = stringResource(id = R.string.wireguard_port_info_description),
+ additionalInfo =
+ buildString {
+ portRanges.forEachIndexed { index, range ->
+ if (index != 0) {
+ append(",")
+ append(" ")
+ }
+ if (range.from == range.to) {
+ append(range.from)
+ } else {
+ append("${range.from}-${range.to}")
+ }
+ }
+ },
+ onDismiss = onDismiss
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
index bc44670940..f5c8d39cd4 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
@@ -64,16 +64,22 @@ import net.mullvad.mullvadvpn.compose.dialog.MalwareInfoDialog
import net.mullvad.mullvadvpn.compose.dialog.MtuDialog
import net.mullvad.mullvadvpn.compose.dialog.ObfuscationInfoDialog
import net.mullvad.mullvadvpn.compose.dialog.QuantumResistanceInfoDialog
+import net.mullvad.mullvadvpn.compose.dialog.WireguardPortInfoDialog
import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider
import net.mullvad.mullvadvpn.compose.state.VpnSettingsUiState
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_LAST_ITEM_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.compose.theme.Dimens
+import net.mullvad.mullvadvpn.constant.WIREGUARD_PRESET_PORTS
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Port
import net.mullvad.mullvadvpn.model.QuantumResistantState
import net.mullvad.mullvadvpn.model.SelectedObfuscation
+import net.mullvad.mullvadvpn.util.hasValue
import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem
@Preview
@@ -117,7 +123,9 @@ private fun PreviewVpnSettings() {
onSelectObfuscationSetting = {},
onObfuscationInfoClick = {},
onSelectQuantumResistanceSetting = {},
- onQuantumResistanceInfoClicked = {}
+ onQuantumResistanceInfoClicked = {},
+ onWireguardPortSelected = {},
+ onWireguardPortInfoClicked = {}
)
}
}
@@ -156,7 +164,9 @@ fun VpnSettingsScreen(
onSelectObfuscationSetting: (selectedObfuscation: SelectedObfuscation) -> Unit = {},
onObfuscationInfoClick: () -> Unit = {},
onSelectQuantumResistanceSetting: (quantumResistant: QuantumResistantState) -> Unit = {},
- onQuantumResistanceInfoClicked: () -> Unit = {}
+ onQuantumResistanceInfoClicked: () -> Unit = {},
+ onWireguardPortSelected: (port: Constraint<Port>) -> Unit = {},
+ onWireguardPortInfoClicked: () -> Unit = {}
) {
val cellVerticalSpacing = dimensionResource(id = R.dimen.cell_label_vertical_padding)
val cellHorizontalSpacing = dimensionResource(id = R.dimen.cell_left_padding)
@@ -199,6 +209,9 @@ fun VpnSettingsScreen(
is VpnSettingsUiState.QuantumResistanceInfoDialogUiState -> {
QuantumResistanceInfoDialog(onDismissInfoClick)
}
+ is VpnSettingsUiState.WireguardPortInfoDialogUiState -> {
+ WireguardPortInfoDialog(uiState.availablePortRanges, onDismissInfoClick)
+ }
else -> {
// NOOP
}
@@ -420,6 +433,33 @@ fun VpnSettingsScreen(
)
}
+ itemWithDivider {
+ Spacer(modifier = Modifier.height(cellVerticalSpacing))
+ InformationComposeCell(
+ title = stringResource(id = R.string.wireguard_port_title),
+ onInfoClicked = onWireguardPortInfoClicked
+ )
+ }
+
+ itemWithDivider {
+ SelectableCell(
+ title = stringResource(id = R.string.automatic),
+ isSelected = uiState.selectedWireguardPort is Constraint.Any,
+ onCellClicked = { onWireguardPortSelected(Constraint.Any()) }
+ )
+ }
+
+ WIREGUARD_PRESET_PORTS.forEach { port ->
+ itemWithDivider {
+ SelectableCell(
+ title = port.toString(),
+ testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, port),
+ isSelected = uiState.selectedWireguardPort.hasValue(port),
+ onCellClicked = { onWireguardPortSelected(Constraint.Only(Port(port))) }
+ )
+ }
+ }
+
item {
Spacer(modifier = Modifier.height(cellVerticalSpacing))
HeaderSwitchComposeCell(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
index 1515b2d19a..0413e52ed1 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
@@ -1,6 +1,9 @@
package net.mullvad.mullvadvpn.compose.state
+import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.DefaultDnsOptions
+import net.mullvad.mullvadvpn.model.Port
+import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.model.QuantumResistantState
import net.mullvad.mullvadvpn.model.SelectedObfuscation
import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem
@@ -16,6 +19,7 @@ sealed interface VpnSettingsUiState {
val isAllowLanEnabled: Boolean
val selectedObfuscation: SelectedObfuscation
val quantumResistant: QuantumResistantState
+ val selectedWireguardPort: Constraint<Port>
data class DefaultUiState(
override val mtu: String = "",
@@ -26,7 +30,8 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class MtuDialogUiState(
@@ -39,7 +44,8 @@ sealed interface VpnSettingsUiState {
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
val mtuEditValue: String,
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class DnsDialogUiState(
@@ -52,7 +58,8 @@ sealed interface VpnSettingsUiState {
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
val stagedDns: StagedDns,
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class LocalNetworkSharingInfoDialogUiState(
@@ -64,7 +71,8 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class ContentBlockersInfoDialogUiState(
@@ -76,7 +84,8 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class CustomDnsInfoDialogUiState(
@@ -88,7 +97,8 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class MalwareInfoDialogUiState(
@@ -100,7 +110,8 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
) : VpnSettingsUiState
data class ObfuscationInfoDialogUiState(
@@ -112,7 +123,8 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any(),
) : VpnSettingsUiState
data class QuantumResistanceInfoDialogUiState(
@@ -124,6 +136,21 @@ sealed interface VpnSettingsUiState {
override val customDnsItems: List<CustomDnsItem> = listOf(),
override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
- override val quantumResistant: QuantumResistantState = QuantumResistantState.Off
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any()
+ ) : VpnSettingsUiState
+
+ data class WireguardPortInfoDialogUiState(
+ override val mtu: String = "",
+ override val isAutoConnectEnabled: Boolean = false,
+ override val isLocalNetworkSharingEnabled: Boolean = false,
+ override val isCustomDnsEnabled: Boolean = false,
+ override val isAllowLanEnabled: Boolean = false,
+ override val customDnsItems: List<CustomDnsItem> = listOf(),
+ override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
+ override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off,
+ override val quantumResistant: QuantumResistantState = QuantumResistantState.Off,
+ override val selectedWireguardPort: Constraint<Port> = Constraint.Any(),
+ val availablePortRanges: List<PortRange> = emptyList()
) : VpnSettingsUiState
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
index 326908667c..84b9e32afe 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
@@ -5,6 +5,7 @@ const val LAZY_LIST_TEST_TAG = "lazy_list_test_tag"
const val LAZY_LIST_LAST_ITEM_TEST_TAG = "lazy_list_last_item_test_tag"
const val LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG = "lazy_list_quantum_item_off_test_tag"
const val LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG = "lazy_list_quantum_item_on_test_tag"
+const val LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG = "lazy_list_quantum_item_%d_test_tag"
// SelectLocationScreen
const val CIRCULAR_PROGRESS_INDICATOR = "circular_progress_indicator"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/WireguardConstant.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/WireguardConstant.kt
new file mode 100644
index 0000000000..b4ed1e29a9
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/WireguardConstant.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.constant
+
+val WIREGUARD_PRESET_PORTS = listOf(51820, 53)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index bbe632f9d5..2244438ef0 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -85,13 +85,7 @@ val uiModule = module {
ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG)
}
viewModel { PrivacyDisclaimerViewModel(get()) }
- viewModel {
- VpnSettingsViewModel(
- get(),
- get(),
- get(),
- )
- }
+ viewModel { VpnSettingsViewModel(get(), get(), get(), get()) }
viewModel { SelectLocationViewModel(get()) }
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/VpnSettingsFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/VpnSettingsFragment.kt
index f90186e548..45c7f4b14e 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/VpnSettingsFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/VpnSettingsFragment.kt
@@ -55,7 +55,9 @@ class VpnSettingsFragment : BaseFragment() {
onSelectObfuscationSetting = vm::onSelectObfuscationSetting,
onObfuscationInfoClick = vm::onObfuscationInfoClick,
onSelectQuantumResistanceSetting = vm::onSelectQuantumResistanceSetting,
- onQuantumResistanceInfoClicked = vm::onQuantumResistanceInfoClicked
+ onQuantumResistanceInfoClicked = vm::onQuantumResistanceInfoClicked,
+ onWireguardPortSelected = vm::onWireguardPortSelected,
+ onWireguardPortInfoClicked = vm::onWireguardPortInfoClicked
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt
index c6142694b9..eb31a264ac 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt
@@ -73,7 +73,7 @@ class RelayListListener(
eventDispatcher.registerHandler(Event.NewRelayList::class) { event ->
event.relayList?.let { relayLocations ->
relayListChanged(RelayList(relayLocations))
- onPortRangesChange?.invoke(relayLocations.wireguardEndpointData.portRanges)
+ portRangesChanged(relayLocations.wireguardEndpointData.portRanges)
}
}
@@ -90,6 +90,7 @@ class RelayListListener(
private fun relaySettingsChanged(newRelaySettings: RelaySettings?) {
synchronized(this) {
val relayList = this.relayList
+ val portRanges = this.portRanges
relaySettings =
newRelaySettings
@@ -100,6 +101,7 @@ class RelayListListener(
if (relayList != null) {
relayListChanged(relayList)
}
+ portRangesChanged(portRanges)
}
}
@@ -112,6 +114,14 @@ class RelayListListener(
}
}
+ private fun portRangesChanged(newPortRanges: List<PortRange>) {
+ synchronized(this) {
+ portRanges = newPortRanges
+
+ onPortRangesChange?.invoke(portRanges)
+ }
+ }
+
private fun findSelectedRelayItem(): RelayItem? {
val relaySettings = this.relaySettings
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ConstraintExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ConstraintExtensions.kt
new file mode 100644
index 0000000000..9be01b2952
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ConstraintExtensions.kt
@@ -0,0 +1,10 @@
+package net.mullvad.mullvadvpn.util
+
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Port
+
+fun Constraint<Port>.hasValue(value: Int) =
+ when (this) {
+ is Constraint.Any -> false
+ is Constraint.Only -> this.value.value == value
+ }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
index 81811722c7..a5fa4721bd 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
@@ -7,11 +7,16 @@ import androidx.lifecycle.viewModelScope
import java.net.InetAddress
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
@@ -22,11 +27,18 @@ import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.DefaultDnsOptions
import net.mullvad.mullvadvpn.model.DnsState
import net.mullvad.mullvadvpn.model.ObfuscationSettings
+import net.mullvad.mullvadvpn.model.Port
import net.mullvad.mullvadvpn.model.QuantumResistantState
+import net.mullvad.mullvadvpn.model.RelaySettings
import net.mullvad.mullvadvpn.model.SelectedObfuscation
import net.mullvad.mullvadvpn.model.Settings
import net.mullvad.mullvadvpn.model.Udp2TcpObfuscationSettings
+import net.mullvad.mullvadvpn.model.WireguardConstraints
import net.mullvad.mullvadvpn.repository.SettingsRepository
+import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
+import net.mullvad.mullvadvpn.ui.serviceconnection.relayListListener
import net.mullvad.mullvadvpn.util.isValidMtu
import org.apache.commons.validator.routines.InetAddressValidator
@@ -34,6 +46,7 @@ class VpnSettingsViewModel(
private val repository: SettingsRepository,
private val inetAddressValidator: InetAddressValidator,
private val resources: Resources,
+ private val serviceConnectionManager: ServiceConnectionManager,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ViewModel() {
@@ -44,21 +57,38 @@ class VpnSettingsViewModel(
MutableStateFlow<VpnSettingsDialogState>(VpnSettingsDialogState.NoDialog)
private val vmState =
- combine(repository.settingsUpdates, dialogState) { settings, dialogState ->
- VpnSettingsViewModelState(
- mtuValue = settings?.mtuString() ?: "",
- isAutoConnectEnabled = settings?.autoConnect ?: false,
- isLocalNetworkSharingEnabled = settings?.allowLan ?: false,
- isCustomDnsEnabled = settings?.isCustomDnsEnabled() ?: false,
- customDnsList = settings?.addresses()?.asStringAddressList() ?: listOf(),
- contentBlockersOptions = settings?.contentBlockersSettings()
- ?: DefaultDnsOptions(),
- isAllowLanEnabled = settings?.allowLan ?: false,
- selectedObfuscation = settings?.selectedObfuscationSettings()
- ?: SelectedObfuscation.Off,
- dialogState = dialogState,
- quantumResistant = settings?.quantumResistant() ?: QuantumResistantState.Off
- )
+ serviceConnectionManager.connectionState
+ .flatMapLatest { state ->
+ if (state is ServiceConnectionState.ConnectedReady) {
+ flowOf(state.container)
+ } else {
+ emptyFlow()
+ }
+ }
+ .flatMapLatest { serviceConnection ->
+ combine(
+ repository.settingsUpdates,
+ serviceConnection.relayListListener.portRangesCallbackFlow(),
+ dialogState
+ ) { settings, portRanges, dialogState ->
+ VpnSettingsViewModelState(
+ mtuValue = settings?.mtuString() ?: "",
+ isAutoConnectEnabled = settings?.autoConnect ?: false,
+ isLocalNetworkSharingEnabled = settings?.allowLan ?: false,
+ isCustomDnsEnabled = settings?.isCustomDnsEnabled() ?: false,
+ customDnsList = settings?.addresses()?.asStringAddressList() ?: listOf(),
+ contentBlockersOptions = settings?.contentBlockersSettings()
+ ?: DefaultDnsOptions(),
+ isAllowLanEnabled = settings?.allowLan ?: false,
+ selectedObfuscation = settings?.selectedObfuscationSettings()
+ ?: SelectedObfuscation.Off,
+ dialogState = dialogState,
+ quantumResistant = settings?.quantumResistant()
+ ?: QuantumResistantState.Off,
+ selectedWireguardPort = settings?.getWireguardPort() ?: Constraint.Any(),
+ availablePortRanges = portRanges
+ )
+ }
}
.stateIn(
viewModelScope,
@@ -317,6 +347,17 @@ class VpnSettingsViewModel(
dialogState.update { VpnSettingsDialogState.QuantumResistanceInfoDialog }
}
+ fun onWireguardPortSelected(port: Constraint<Port>) {
+ viewModelScope.launch(dispatcher) {
+ serviceConnectionManager.relayListListener()?.selectedWireguardConstraints =
+ WireguardConstraints(port = port)
+ }
+ }
+
+ fun onWireguardPortInfoClicked() {
+ dialogState.update { VpnSettingsDialogState.WireguardPortInfoDialog }
+ }
+
private fun updateDefaultDnsOptionsViaRepository(contentBlockersOption: DefaultDnsOptions) =
viewModelScope.launch(dispatcher) {
repository.setDnsOptions(
@@ -363,6 +404,17 @@ class VpnSettingsViewModel(
private fun Settings.selectedObfuscationSettings() = obfuscationSettings.selectedObfuscation
+ private fun RelayListListener.portRangesCallbackFlow() = callbackFlow {
+ onPortRangesChange = { portRanges -> this.trySend(portRanges) }
+ awaitClose { onPortRangesChange = null }
+ }
+
+ private fun Settings.getWireguardPort() =
+ when (relaySettings) {
+ RelaySettings.CustomTunnelEndpoint -> Constraint.Any()
+ is RelaySettings.Normal -> relaySettings.relayConstraints.wireguardConstraints.port
+ }
+
private fun String.isValidIp(): Boolean {
return inetAddressValidator.isValid(this)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
index 8cf675b786..b77b107037 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
@@ -1,7 +1,10 @@
package net.mullvad.mullvadvpn.viewmodel
import net.mullvad.mullvadvpn.compose.state.VpnSettingsUiState
+import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.DefaultDnsOptions
+import net.mullvad.mullvadvpn.model.Port
+import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.model.QuantumResistantState
import net.mullvad.mullvadvpn.model.SelectedObfuscation
@@ -15,7 +18,9 @@ data class VpnSettingsViewModelState(
val contentBlockersOptions: DefaultDnsOptions,
val selectedObfuscation: SelectedObfuscation,
val dialogState: VpnSettingsDialogState,
- val quantumResistant: QuantumResistantState
+ val quantumResistant: QuantumResistantState,
+ val selectedWireguardPort: Constraint<Port>,
+ val availablePortRanges: List<PortRange>
) {
fun toUiState(): VpnSettingsUiState {
return when (dialogState) {
@@ -30,7 +35,8 @@ data class VpnSettingsViewModelState(
contentBlockersOptions = contentBlockersOptions,
mtuEditValue = dialogState.mtuEditValue,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.DnsDialog ->
VpnSettingsUiState.DnsDialogUiState(
@@ -43,7 +49,8 @@ data class VpnSettingsViewModelState(
contentBlockersOptions = contentBlockersOptions,
stagedDns = dialogState.stagedDns,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.LocalNetworkSharingInfoDialog ->
VpnSettingsUiState.LocalNetworkSharingInfoDialogUiState(
@@ -55,7 +62,8 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.ContentBlockersInfoDialog ->
VpnSettingsUiState.ContentBlockersInfoDialogUiState(
@@ -67,7 +75,8 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.CustomDnsInfoDialog ->
VpnSettingsUiState.CustomDnsInfoDialogUiState(
@@ -79,7 +88,8 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.MalwareInfoDialog ->
VpnSettingsUiState.MalwareInfoDialogUiState(
@@ -91,7 +101,8 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.ObfuscationInfoDialog ->
VpnSettingsUiState.ObfuscationInfoDialogUiState(
@@ -103,7 +114,8 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
is VpnSettingsDialogState.QuantumResistanceInfoDialog -> {
VpnSettingsUiState.QuantumResistanceInfoDialogUiState(
@@ -115,7 +127,23 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
+ )
+ }
+ is VpnSettingsDialogState.WireguardPortInfoDialog -> {
+ VpnSettingsUiState.WireguardPortInfoDialogUiState(
+ mtu = mtuValue,
+ isAutoConnectEnabled = isAutoConnectEnabled,
+ isLocalNetworkSharingEnabled = isLocalNetworkSharingEnabled,
+ isCustomDnsEnabled = isCustomDnsEnabled,
+ isAllowLanEnabled = isAllowLanEnabled,
+ customDnsItems = customDnsList,
+ contentBlockersOptions = contentBlockersOptions,
+ selectedObfuscation = selectedObfuscation,
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort,
+ availablePortRanges = availablePortRanges
)
}
else ->
@@ -128,7 +156,8 @@ data class VpnSettingsViewModelState(
customDnsItems = customDnsList,
contentBlockersOptions = contentBlockersOptions,
selectedObfuscation = selectedObfuscation,
- quantumResistant = quantumResistant
+ quantumResistant = quantumResistant,
+ selectedWireguardPort = selectedWireguardPort
)
}
}
@@ -147,7 +176,9 @@ data class VpnSettingsViewModelState(
isAllowLanEnabled = false,
dialogState = VpnSettingsDialogState.NoDialog,
selectedObfuscation = SelectedObfuscation.Auto,
- quantumResistant = QuantumResistantState.Off
+ quantumResistant = QuantumResistantState.Off,
+ selectedWireguardPort = Constraint.Any(),
+ availablePortRanges = emptyList()
)
}
}
@@ -170,6 +201,8 @@ sealed class VpnSettingsDialogState {
object ObfuscationInfoDialog : VpnSettingsDialogState()
object QuantumResistanceInfoDialog : VpnSettingsDialogState()
+
+ object WireguardPortInfoDialog : VpnSettingsDialogState()
}
sealed interface StagedDns {
diff --git a/android/app/src/main/res/values-da/strings.xml b/android/app/src/main/res/values-da/strings.xml
index 38d8f0159c..8379e714a5 100644
--- a/android/app/src/main/res/values-da/strings.xml
+++ b/android/app/src/main/res/values-da/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Vi vil undersøge dette.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">Indstil WireGuard MTU-værdi. Gyldigt område: %1$d - %2$d.</string>
+ <string name="wireguard_port_info_description">Den automatiske indstilling vælger tilfældigt fra de gyldige rækker af porte nedenfor.</string>
</resources>
diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml
index 4b0af82856..996bf50648 100644
--- a/android/app/src/main/res/values-de/strings.xml
+++ b/android/app/src/main/res/values-de/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Wir werden uns das anschauen.</string>
<string name="wireguard_mtu">WireGuard-MTU</string>
<string name="wireguard_mtu_footer">WireGuard-MTU-Wert einstellen. Gültiger Bereich: %1$d – %2$d.</string>
+ <string name="wireguard_port_info_description">Die automatische Einstellung wählt zufällig aus den unten gezeigten gültigen Portbereichen.</string>
</resources>
diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml
index 2a2b536073..14d04fab32 100644
--- a/android/app/src/main/res/values-es/strings.xml
+++ b/android/app/src/main/res/values-es/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Revisaremos esto.</string>
<string name="wireguard_mtu">MTU de WireGuard</string>
<string name="wireguard_mtu_footer">Establezca el valor de MTU de WireGuard. Intervalo válido: %1$d-%2$d.</string>
+ <string name="wireguard_port_info_description">El ajuste automático se elegirá al azar entre los rangos de puertos válidos que se muestran a continuación.</string>
</resources>
diff --git a/android/app/src/main/res/values-fi/strings.xml b/android/app/src/main/res/values-fi/strings.xml
index 1682783eb2..6e03fc7cc5 100644
--- a/android/app/src/main/res/values-fi/strings.xml
+++ b/android/app/src/main/res/values-fi/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Tutkimme asiaa.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">Aseta WireGuardin MTU-arvo väliltä %1$d–%2$d.</string>
+ <string name="wireguard_port_info_description">Automaattinen asetus valitsee satunnaisesti käytettävissä olevista, alla luetelluista porteista.</string>
</resources>
diff --git a/android/app/src/main/res/values-fr/strings.xml b/android/app/src/main/res/values-fr/strings.xml
index fd0312e947..a312b828f3 100644
--- a/android/app/src/main/res/values-fr/strings.xml
+++ b/android/app/src/main/res/values-fr/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Nous allons nous pencher dessus.</string>
<string name="wireguard_mtu">MTU WireGuard</string>
<string name="wireguard_mtu_footer">Définir la valeur MTU WireGuard. Plage valide : %1$d - %2$d.</string>
+ <string name="wireguard_port_info_description">Le réglage automatique choisira au hasard parmi la plage de ports valide affichée ci-dessous.</string>
</resources>
diff --git a/android/app/src/main/res/values-it/strings.xml b/android/app/src/main/res/values-it/strings.xml
index 7b15b5a504..f9d5eafc01 100644
--- a/android/app/src/main/res/values-it/strings.xml
+++ b/android/app/src/main/res/values-it/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Verificheremo.</string>
<string name="wireguard_mtu">MTU WireGuard</string>
<string name="wireguard_mtu_footer">Imposta il valore MTU WireGuard. Intervallo valido: %1$d - %2$d.</string>
+ <string name="wireguard_port_info_description">L\'impostazione automatica sceglierà in modo casuale una porta valida negli intervalli mostrati di seguito.</string>
</resources>
diff --git a/android/app/src/main/res/values-ja/strings.xml b/android/app/src/main/res/values-ja/strings.xml
index e66be1edf0..f9bc8ac000 100644
--- a/android/app/src/main/res/values-ja/strings.xml
+++ b/android/app/src/main/res/values-ja/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">この問題を調査いたします。</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">WireGuard MTUの値を設定します。有効範囲: %1$d ~ %2$d</string>
+ <string name="wireguard_port_info_description">自動設定では、以下の有効なポート範囲からランダムに選択されます。</string>
</resources>
diff --git a/android/app/src/main/res/values-ko/strings.xml b/android/app/src/main/res/values-ko/strings.xml
index 2d4104a9b8..d0f3d31ca4 100644
--- a/android/app/src/main/res/values-ko/strings.xml
+++ b/android/app/src/main/res/values-ko/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">조사해보겠습니다.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">WireGuard MTU 값을 설정하세요. 유효 범위: %1$d ~ %2$d</string>
+ <string name="wireguard_port_info_description">자동 설정은 아래 표시된 유효한 포트 범위에서 임의로 선택합니다.</string>
</resources>
diff --git a/android/app/src/main/res/values-my/strings.xml b/android/app/src/main/res/values-my/strings.xml
index b9c0107722..d7e9372363 100644
--- a/android/app/src/main/res/values-my/strings.xml
+++ b/android/app/src/main/res/values-my/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">ဤသည်ကို စစ်ဆေးလိုက်ပါမည်။</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">WireGuard MTU တန်ဖိုးကို သတ်မှတ်ပါ။ အကျုံးဝင်သည့် အပိုင်းအခြား- %1$d - %2$d ။</string>
+ <string name="wireguard_port_info_description">အော်တိုဆက်တင်သည် အောက်တွင် ဖော်ပြထားသည့် အကျုံးဝင် ပေါ့တ် အပိုင်းအခြားများထဲမှ ကျပန်းရွေးချယ်ပါမည်။</string>
</resources>
diff --git a/android/app/src/main/res/values-nb/strings.xml b/android/app/src/main/res/values-nb/strings.xml
index 4e2db90074..03492e41c6 100644
--- a/android/app/src/main/res/values-nb/strings.xml
+++ b/android/app/src/main/res/values-nb/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Dette skal vi følge opp.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">Angi WireGuard MTU-verdi. Verdiområde: %1$d–%2$d.</string>
+ <string name="wireguard_port_info_description">Den automatiske innstillingen vil tilfeldig velge fra utvalget av gyldige porter vist under.</string>
</resources>
diff --git a/android/app/src/main/res/values-nl/strings.xml b/android/app/src/main/res/values-nl/strings.xml
index ffa697d76a..e6f5a6fff6 100644
--- a/android/app/src/main/res/values-nl/strings.xml
+++ b/android/app/src/main/res/values-nl/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">We gaan het bekijken.</string>
<string name="wireguard_mtu">WireGuard-MTU</string>
<string name="wireguard_mtu_footer">Stel de MTU-waarde voor WireGuard in. Geldig bereik: %1$d - %2$d.</string>
+ <string name="wireguard_port_info_description">Bij de automatische instelling wordt willekeurig gekozen uit de hieronder weergegeven geldige poortbereiken.</string>
</resources>
diff --git a/android/app/src/main/res/values-pl/strings.xml b/android/app/src/main/res/values-pl/strings.xml
index c0f15a2c02..4ff503ec6d 100644
--- a/android/app/src/main/res/values-pl/strings.xml
+++ b/android/app/src/main/res/values-pl/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Sprawdzimy to.</string>
<string name="wireguard_mtu">MTU WireGuard</string>
<string name="wireguard_mtu_footer">Ustaw wartość MTU WireGuard. Prawidłowy zakres: %1$d–%2$d.</string>
+ <string name="wireguard_port_info_description">Ustawienie automatyczne skutkuje wyborem losowym prawidłowego zakresu portów spośród zakresów przedstawionych poniżej.</string>
</resources>
diff --git a/android/app/src/main/res/values-pt/strings.xml b/android/app/src/main/res/values-pt/strings.xml
index e578fdd98c..3037dd4eed 100644
--- a/android/app/src/main/res/values-pt/strings.xml
+++ b/android/app/src/main/res/values-pt/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Vamos analisar esta situação.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">Definir o valor WireGuard MTU. Intervalo válido: %1$d - %2$d.</string>
+ <string name="wireguard_port_info_description">A definição automática escolherá aleatoriamente a partir do intervalo de portas válido apresentado abaixo.</string>
</resources>
diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml
index e0f937f5e3..33ec82869c 100644
--- a/android/app/src/main/res/values-ru/strings.xml
+++ b/android/app/src/main/res/values-ru/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Мы рассмотрим эту проблему.</string>
<string name="wireguard_mtu">MTU для WireGuard</string>
<string name="wireguard_mtu_footer">Установите значение MTU для WireGuard. Диапазон значений: %1$d–%2$d.</string>
+ <string name="wireguard_port_info_description">При автоматической настройке порт будет выбираться случайным образом из допустимого диапазона, показанного ниже.</string>
</resources>
diff --git a/android/app/src/main/res/values-sv/strings.xml b/android/app/src/main/res/values-sv/strings.xml
index b2d1298f51..dcd91bb97b 100644
--- a/android/app/src/main/res/values-sv/strings.xml
+++ b/android/app/src/main/res/values-sv/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Vi kommer att undersöka detta.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">Ange WireGuard MTU-värde. Giltigt intervall: %1$d–%2$d.</string>
+ <string name="wireguard_port_info_description">Den automatiska inställningen väljer slumpmässigt från giltiga portintervall som visas nedan.</string>
</resources>
diff --git a/android/app/src/main/res/values-th/strings.xml b/android/app/src/main/res/values-th/strings.xml
index ba7e1e2124..4ef7f78671 100644
--- a/android/app/src/main/res/values-th/strings.xml
+++ b/android/app/src/main/res/values-th/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">เราจะตรวจสอบปัญหานี้</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">ตั้งค่า WireGuard MTU ช่วงที่ใช้ได้: %1$d - %2$d</string>
+ <string name="wireguard_port_info_description">การตั้งค่าอัตโนมัติจะเป็นการสุ่มเลือกจากช่วงพอร์ตที่ใช้งานได้ต่างๆ ซึ่งแสดงอยู่ด้านล่าง</string>
</resources>
diff --git a/android/app/src/main/res/values-tr/strings.xml b/android/app/src/main/res/values-tr/strings.xml
index 655e0c45bd..252b74da9b 100644
--- a/android/app/src/main/res/values-tr/strings.xml
+++ b/android/app/src/main/res/values-tr/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">Bunu araştıracağız.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">WireGuard MTU değerini ayarlayın. Geçerli aralık: %1$d - %2$d.</string>
+ <string name="wireguard_port_info_description">Otomatik ayar, aşağıda gösterilen geçerli port aralıklarından rastgele seçim yapar.</string>
</resources>
diff --git a/android/app/src/main/res/values-zh-rCN/strings.xml b/android/app/src/main/res/values-zh-rCN/strings.xml
index 369dfdd73a..77930489f9 100644
--- a/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">我们将对此进行调查。</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">设置 WireGuard MTU 值。有效范围:%1$d - %2$d。</string>
+ <string name="wireguard_port_info_description">自动设置将从下方显示的有效端口范围中随机选择。</string>
</resources>
diff --git a/android/app/src/main/res/values-zh-rTW/strings.xml b/android/app/src/main/res/values-zh-rTW/strings.xml
index 2b408557ea..3d297f6e9f 100644
--- a/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -184,4 +184,5 @@
<string name="we_will_look_into_this">我們會對此進行調查。</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">設定 WireGuard MTU 值。有效範圍:%1$d - %2$d。</string>
+ <string name="wireguard_port_info_description">自動設定將會隨機從下方顯示的有效連接埠範圍中進行選擇。</string>
</resources>
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index d4d506173c..0c3ba602a1 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -202,4 +202,6 @@
<string name="on">On</string>
<string name="quantum_creating_secure_connection">CREATING QUANTUM SECURE CONNECTION</string>
<string name="quantum_secure_connection">QUANTUM SECURE CONNECTION</string>
+ <string name="wireguard_port_title">WireGuard Port</string>
+ <string name="wireguard_port_info_description">The automatic setting will randomly choose from the valid port ranges shown below.</string>
</resources>
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
index a0a2e8ae91..ab784263fa 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
@@ -5,19 +5,33 @@ import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
+import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify
import kotlin.test.assertEquals
+import kotlin.test.assertIs
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.TestCoroutineRule
+import net.mullvad.mullvadvpn.assertLists
+import net.mullvad.mullvadvpn.compose.state.VpnSettingsUiState
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Port
+import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.model.QuantumResistantState
+import net.mullvad.mullvadvpn.model.RelayConstraints
+import net.mullvad.mullvadvpn.model.RelaySettings
import net.mullvad.mullvadvpn.model.Settings
import net.mullvad.mullvadvpn.model.TunnelOptions
+import net.mullvad.mullvadvpn.model.WireguardConstraints
import net.mullvad.mullvadvpn.model.WireguardTunnelOptions
import net.mullvad.mullvadvpn.repository.SettingsRepository
+import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import org.apache.commons.validator.routines.InetAddressValidator
import org.junit.After
import org.junit.Before
@@ -30,20 +44,34 @@ class VpnSettingsViewModelTest {
private val mockSettingsRepository: SettingsRepository = mockk()
private val mockInetAddressValidator: InetAddressValidator = mockk()
private val mockResources: Resources = mockk()
+ private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
+
+ private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+ private val mockRelayListListener: RelayListListener = mockk()
+ private val portRangeSlot = slot<(List<PortRange>) -> Unit>()
private val mockSettingsUpdate = MutableStateFlow<Settings?>(null)
+ private val mockConnectionState =
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
private lateinit var viewModel: VpnSettingsViewModel
@Before
fun setUp() {
every { mockSettingsRepository.settingsUpdates } returns mockSettingsUpdate
+ every { mockServiceConnectionManager.connectionState } returns mockConnectionState
+
+ every { mockServiceConnectionContainer.relayListListener } returns mockRelayListListener
+
+ every { mockRelayListListener.onPortRangesChange = capture(portRangeSlot) } answers {}
+ every { mockRelayListListener.onPortRangesChange = null } answers {}
viewModel =
VpnSettingsViewModel(
repository = mockSettingsRepository,
inetAddressValidator = mockInetAddressValidator,
resources = mockResources,
+ serviceConnectionManager = mockServiceConnectionManager,
dispatcher = UnconfinedTestDispatcher()
)
}
@@ -67,7 +95,10 @@ class VpnSettingsViewModelTest {
@Test
fun test_update_quantum_resistant_default_state() = runTest {
+ // Arrange
val expectedResistantState = QuantumResistantState.Off
+
+ // Act, Assert
viewModel.uiState.test {
assertEquals(expectedResistantState, awaitItem().quantumResistant)
}
@@ -84,11 +115,80 @@ class VpnSettingsViewModelTest {
every { mockSettings.tunnelOptions } returns mockTunnelOptions
every { mockTunnelOptions.wireguard } returns mockWireguardTunnelOptions
every { mockWireguardTunnelOptions.quantumResistant } returns expectedResistantState
+ every { mockSettings.relaySettings } returns mockk<RelaySettings.Normal>(relaxed = true)
viewModel.uiState.test {
assertEquals(defaultResistantState, awaitItem().quantumResistant)
mockSettingsUpdate.value = mockSettings
+ mockConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ portRangeSlot.captured.invoke(emptyList())
assertEquals(expectedResistantState, awaitItem().quantumResistant)
}
}
+
+ @Test
+ fun test_update_wireguard_port_state() = runTest {
+ // Arrange
+ val expectedPort: Constraint<Port> = Constraint.Only(Port(99))
+ val mockSettings: Settings = mockk(relaxed = true)
+ val mockRelaySettings: RelaySettings.Normal = mockk()
+ val mockRelayConstraints: RelayConstraints = mockk()
+ val mockWireguardConstraints: WireguardConstraints = mockk()
+
+ every { mockSettings.relaySettings } returns mockRelaySettings
+ every { mockRelaySettings.relayConstraints } returns mockRelayConstraints
+ every { mockRelayConstraints.wireguardConstraints } returns mockWireguardConstraints
+ every { mockWireguardConstraints.port } returns expectedPort
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertIs<Constraint.Any<Port>>(awaitItem().selectedWireguardPort)
+ mockSettingsUpdate.value = mockSettings
+ mockConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ portRangeSlot.captured.invoke(emptyList())
+ assertEquals(expectedPort, awaitItem().selectedWireguardPort)
+ }
+ }
+
+ @Test
+ fun test_select_wireguard_port() = runTest {
+ // Arrange
+ val wireguardPort: Constraint<Port> = Constraint.Only(Port(99))
+ val wireguardConstraints = WireguardConstraints(port = wireguardPort)
+ every { mockRelayListListener.selectedWireguardConstraints = any() } returns Unit
+
+ // Act
+ mockConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ viewModel.onWireguardPortSelected(wireguardPort)
+
+ // Assert
+ verify(exactly = 1) {
+ mockRelayListListener.selectedWireguardConstraints = wireguardConstraints
+ }
+ }
+
+ @Test
+ fun test_update_port_range_state() = runTest {
+ // Arrange
+ val expectedPortRange = listOf<PortRange>(mockk(), mockk())
+ val mockSettings: Settings = mockk(relaxed = true)
+
+ every { mockSettings.relaySettings } returns mockk<RelaySettings.Normal>(relaxed = true)
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertIs<VpnSettingsUiState.DefaultUiState>(awaitItem())
+ mockSettingsUpdate.value = mockSettings
+ viewModel.onWireguardPortInfoClicked()
+ mockConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ portRangeSlot.captured.invoke(expectedPortRange)
+ val state = awaitItem()
+ assertIs<VpnSettingsUiState.WireguardPortInfoDialogUiState>(state)
+ assertLists(expectedPortRange, state.availablePortRanges)
+ }
+ }
}
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot
index db4ff05229..f0ce1f78ea 100644
--- a/gui/locales/messages.pot
+++ b/gui/locales/messages.pot
@@ -1779,6 +1779,9 @@ msgstr ""
msgid "WireGuard MTU"
msgstr ""
+msgid "WireGuard Port"
+msgstr ""
+
msgid "WireGuard obfuscation"
msgstr ""