diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-07-03 15:29:20 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-07-13 13:19:29 +0200 |
| commit | 9cdbb9c78f1682cc9b9c3c78bef314ba12dbac28 (patch) | |
| tree | a7f8f26af194304e1a3967f9d64db051e01a3e8e /android/app | |
| parent | 80a0705ab69dea7041fbc9176f2b7f2dd5c139e5 (diff) | |
| download | mullvadvpn-9cdbb9c78f1682cc9b9c3c78bef314ba12dbac28.tar.xz mullvadvpn-9cdbb9c78f1682cc9b9c3c78bef314ba12dbac28.zip | |
Add the ability to set a custom wireguard port
Diffstat (limited to 'android/app')
37 files changed, 637 insertions, 53 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 1b97a47914..47f7d8280c 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,8 @@ 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_CUSTOM_PORT_NUMBER_TEST_TAG +import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_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 @@ -623,6 +625,8 @@ class VpnSettingsScreenTest { toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } + + // Act composeTestRule .onNodeWithTag(LAZY_LIST_TEST_TAG) .performScrollToNode( @@ -653,19 +657,21 @@ class VpnSettingsScreenTest { toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } + + // Act composeTestRule .onNodeWithTag(LAZY_LIST_TEST_TAG) .performScrollToNode( hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53)) ) - - // Assert composeTestRule .onNodeWithTagAndText( testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820), text = "51820" ) .performClick() + + // Assert verify(exactly = 1) { mockSelectWireguardPortSelectionListener.invoke(Constraint.Only(Port(51820))) } @@ -692,6 +698,121 @@ class VpnSettingsScreenTest { .assertExists() } + @Test + fun testShowWireguardCustomPortDialog() { + // Arrange + composeTestRule.setContent { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.CustomPortDialogUiState( + availablePortRanges = listOf(PortRange(53, 53), PortRange(120, 121)) + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() + ) + } + + // Assert + composeTestRule.onNodeWithText("Valid ranges: 53, 120-121").assertExists() + } + + @Test + fun testShowWireguardCustomPort() { + // Arrange + composeTestRule.setContent { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.DefaultUiState( + selectedWireguardPort = Constraint.Only(Port(4000)) + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() + ) + } + + // Act + composeTestRule + .onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + + // Assert + composeTestRule.onNodeWithText("4000").assertExists() + } + + @Test + fun testClickWireguardCustomPortMainCell() { + // Arrange + val mockOnShowCustomPortDialog: () -> Unit = mockk(relaxed = true) + composeTestRule.setContent { + VpnSettingsScreen( + uiState = VpnSettingsUiState.DefaultUiState(), + onShowCustomPortDialog = mockOnShowCustomPortDialog, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() + ) + } + + // Act + composeTestRule + .onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + composeTestRule.onNodeWithTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG).performClick() + + // Assert + verify { mockOnShowCustomPortDialog.invoke() } + } + + @Test + fun testClickWireguardCustomPortNumberCell() { + // Arrange + val mockOnShowCustomPortDialog: () -> Unit = mockk(relaxed = true) + composeTestRule.setContent { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.DefaultUiState( + selectedWireguardPort = Constraint.Only(Port(4000)) + ), + onShowCustomPortDialog = mockOnShowCustomPortDialog, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() + ) + } + + // Act + composeTestRule + .onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + composeTestRule + .onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG) + .performClick() + + // Assert + verify { mockOnShowCustomPortDialog.invoke() } + } + + @Test + fun testSelectWireguardCustomPort() { + // Arrange + val onWireguardPortSelected: (Constraint<Port>) -> Unit = mockk(relaxed = true) + composeTestRule.setContent { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.DefaultUiState( + selectedWireguardPort = Constraint.Only(Port(4000)) + ), + onWireguardPortSelected = onWireguardPortSelected, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() + ) + } + + // Act + composeTestRule + .onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + composeTestRule + .onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG) + .performClick() + + // Assert + verify { onWireguardPortSelected.invoke(Constraint.Only(Port(4000))) } + } + 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/CustomPortCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CustomPortCell.kt new file mode 100644 index 0000000000..d15e8140d8 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CustomPortCell.kt @@ -0,0 +1,108 @@ +package net.mullvad.mullvadvpn.compose.cell + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.component.SpacedColumn +import net.mullvad.mullvadvpn.compose.theme.AlphaInvisible +import net.mullvad.mullvadvpn.compose.theme.AlphaVisible +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.Dimens + +@Preview +@Composable +fun PreviewCustomPortCell() { + AppTheme { + SpacedColumn(Modifier.background(MaterialTheme.colorScheme.background)) { + CustomPortCell(title = "Title", isSelected = true, port = "444") + CustomPortCell(title = "Title", isSelected = false, port = "") + } + } +} + +@Composable +fun CustomPortCell( + title: String, + isSelected: Boolean, + port: String, + mainTestTag: String = "", + numberTestTag: String = "", + onMainCellClicked: () -> Unit = {}, + onPortCellClicked: () -> Unit = {} +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + modifier = Modifier.height(Dimens.cellHeight).fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + modifier = + Modifier.clickable { onMainCellClicked() } + .height(Dimens.cellHeight) + .weight(1f) + .background( + if (isSelected) { + MaterialTheme.colorScheme.surface + } else { + MaterialTheme.colorScheme.secondaryContainer + } + ) + .padding(start = Dimens.cellStartPadding) + .testTag(mainTestTag) + ) { + Icon( + painter = painterResource(id = R.drawable.icon_tick), + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimary, + modifier = + Modifier.padding(end = Dimens.selectableCellTextMargin) + .alpha(if (isSelected) AlphaVisible else AlphaInvisible) + ) + BaseCellTitle( + title = title, + style = MaterialTheme.typography.labelLarge, + textAlign = TextAlign.Start + ) + } + Spacer(modifier = Modifier.width(Dimens.verticalSpacer)) + Box( + modifier = + Modifier.clickable { onPortCellClicked() } + .height(Dimens.cellHeight) + .wrapContentWidth() + .defaultMinSize(minWidth = Dimens.customPortBoxMinWidth) + .background(MaterialTheme.colorScheme.primary) + .testTag(numberTestTag) + ) { + Text( + text = port.ifEmpty { stringResource(id = R.string.port) }, + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.align(Alignment.Center) + ) + } + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Button.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Button.kt index e3885078fd..ef5e283c29 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Button.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Button.kt @@ -10,12 +10,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.theme.Dimens @Composable fun ActionButton( @@ -32,11 +31,8 @@ fun ActionButton( contentPadding = PaddingValues(0.dp), modifier = modifier - .height(dimensionResource(id = R.dimen.button_height)) - .defaultMinSize( - minWidth = 0.dp, - minHeight = dimensionResource(id = R.dimen.button_height) - ) + .height(Dimens.buttonHeight) + .defaultMinSize(minWidth = 0.dp, minHeight = Dimens.buttonHeight) .fillMaxWidth(), colors = colors, shape = MaterialTheme.shapes.small diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt new file mode 100644 index 0000000000..c53b91dfae --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt @@ -0,0 +1,136 @@ +package net.mullvad.mullvadvpn.compose.dialog + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.component.ActionButton +import net.mullvad.mullvadvpn.compose.textfield.CustomPortTextField +import net.mullvad.mullvadvpn.compose.theme.AlphaDescription +import net.mullvad.mullvadvpn.compose.theme.AlphaDisabled +import net.mullvad.mullvadvpn.compose.theme.AlphaInactive +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.Dimens +import net.mullvad.mullvadvpn.model.PortRange +import net.mullvad.mullvadvpn.util.asString +import net.mullvad.mullvadvpn.util.isPortInValidRanges + +@Preview +@Composable +fun PreviewCustomPortDialog() { + AppTheme { + CustomPortDialog( + onSave = {}, + onReset = {}, + customPort = "", + allowedPortRanges = listOf(PortRange(10, 10), PortRange(40, 50)), + showReset = true, + onDismissRequest = {} + ) + } +} + +@Composable +fun CustomPortDialog( + customPort: String, + allowedPortRanges: List<PortRange>, + showReset: Boolean, + onSave: (customPortString: String) -> Unit, + onReset: () -> Unit, + onDismissRequest: () -> Unit +) { + val port = remember { mutableStateOf(customPort) } + + AlertDialog( + title = { + Text( + text = stringResource(id = R.string.custom_port_dialog_title), + style = MaterialTheme.typography.headlineSmall + ) + }, + confirmButton = { + Column { + ActionButton( + text = stringResource(id = R.string.custom_port_dialog_submit), + onClick = { onSave(port.value) }, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + disabledContentColor = + MaterialTheme.colorScheme.onPrimary.copy(alpha = AlphaInactive), + disabledContainerColor = + MaterialTheme.colorScheme.onPrimary.copy(alpha = AlphaDisabled) + ), + isEnabled = + port.value.isNotEmpty() && + allowedPortRanges.isPortInValidRanges(port.value.toInt()) + ) + if (showReset) { + ActionButton( + text = stringResource(R.string.custom_port_dialog_remove), + onClick = { onReset() }, + modifier = Modifier.padding(top = Dimens.mediumPadding), + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + ) + ) + } + ActionButton( + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + text = stringResource(id = R.string.cancel), + modifier = Modifier.padding(top = Dimens.mediumPadding), + onClick = onDismissRequest + ) + } + }, + text = { + Column { + CustomPortTextField( + value = port.value, + onSubmit = { input -> + if (input.isNotEmpty()) { + onSave(input) + } else { + onReset() + } + }, + onValueChanged = { input -> port.value = input }, + isValidValue = + port.value.isNotEmpty() && + allowedPortRanges.isPortInValidRanges(port.value.toInt()) + ) + Spacer(modifier = Modifier.height(Dimens.smallPadding)) + Text( + text = + stringResource( + id = R.string.custom_port_dialog_valid_ranges, + allowedPortRanges.asString() + ), + color = MaterialTheme.colorScheme.onBackground.copy(alpha = AlphaDescription), + style = MaterialTheme.typography.bodySmall + ) + } + }, + containerColor = MaterialTheme.colorScheme.background, + titleContentColor = MaterialTheme.colorScheme.onBackground, + onDismissRequest = onDismissRequest + ) +} 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 index 680b395898..f8ab950ae4 100644 --- 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 @@ -4,25 +4,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.PortRange +import net.mullvad.mullvadvpn.util.asString @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}") - } - } - }, + stringResource(id = R.string.wireguard_port_info_port_range, portRanges.asString()), 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 dd7e4e22c6..a3d0e58f1e 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 @@ -29,7 +29,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -46,6 +45,7 @@ import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.cell.BaseCell import net.mullvad.mullvadvpn.compose.cell.ContentBlockersDisableModeCellSubtitle import net.mullvad.mullvadvpn.compose.cell.CustomDnsCellSubtitle +import net.mullvad.mullvadvpn.compose.cell.CustomPortCell import net.mullvad.mullvadvpn.compose.cell.DnsCell import net.mullvad.mullvadvpn.compose.cell.ExpandableComposeCell import net.mullvad.mullvadvpn.compose.cell.HeaderSwitchComposeCell @@ -60,6 +60,7 @@ import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar import net.mullvad.mullvadvpn.compose.dialog.ContentBlockersInfoDialog import net.mullvad.mullvadvpn.compose.dialog.CustomDnsInfoDialog +import net.mullvad.mullvadvpn.compose.dialog.CustomPortDialog import net.mullvad.mullvadvpn.compose.dialog.DnsDialog import net.mullvad.mullvadvpn.compose.dialog.LocalNetworkSharingInfoDialog import net.mullvad.mullvadvpn.compose.dialog.MalwareInfoDialog @@ -73,6 +74,8 @@ 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_CUSTOM_PORT_NUMBER_TEST_TAG +import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_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 @@ -82,6 +85,8 @@ 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.util.isCustom +import net.mullvad.mullvadvpn.util.toDisplayCustomPort import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem @Preview @@ -127,7 +132,10 @@ private fun PreviewVpnSettings() { onSelectQuantumResistanceSetting = {}, onQuantumResistanceInfoClicked = {}, onWireguardPortSelected = {}, - onWireguardPortInfoClicked = {} + onWireguardPortInfoClicked = {}, + onShowCustomPortDialog = {}, + onCancelCustomPortDialogClick = {}, + onCloseCustomPortDialog = {} ) } } @@ -168,10 +176,12 @@ fun VpnSettingsScreen( onSelectQuantumResistanceSetting: (quantumResistant: QuantumResistantState) -> Unit = {}, onQuantumResistanceInfoClicked: () -> Unit = {}, onWireguardPortSelected: (port: Constraint<Port>) -> Unit = {}, - onWireguardPortInfoClicked: () -> Unit = {} + onWireguardPortInfoClicked: () -> Unit = {}, + onShowCustomPortDialog: () -> Unit = {}, + onCancelCustomPortDialogClick: () -> Unit = {}, + onCloseCustomPortDialog: () -> Unit = {} ) { - val cellVerticalSpacing = dimensionResource(id = R.dimen.cell_label_vertical_padding) - val cellHorizontalSpacing = dimensionResource(id = R.dimen.cell_left_padding) + val savedCustomPort = rememberSaveable { mutableStateOf<Constraint<Port>>(Constraint.Any()) } when (uiState) { is VpnSettingsUiState.MtuDialogUiState -> { @@ -214,6 +224,24 @@ fun VpnSettingsScreen( is VpnSettingsUiState.WireguardPortInfoDialogUiState -> { WireguardPortInfoDialog(uiState.availablePortRanges, onDismissInfoClick) } + is VpnSettingsUiState.CustomPortDialogUiState -> { + CustomPortDialog( + customPort = savedCustomPort.value.toDisplayCustomPort(), + allowedPortRanges = uiState.availablePortRanges, + onSave = { customPortString -> + onWireguardPortSelected(Constraint.Only(Port(customPortString.toInt()))) + }, + onReset = { + if (uiState.selectedWireguardPort.isCustom()) { + onWireguardPortSelected(Constraint.Any()) + } + savedCustomPort.value = Constraint.Any() + onCloseCustomPortDialog() + }, + showReset = savedCustomPort.value is Constraint.Only, + onDismissRequest = { onCancelCustomPortDialogClick() } + ) + } else -> { // NOOP } @@ -226,6 +254,15 @@ fun VpnSettingsScreen( val state = rememberCollapsingToolbarScaffoldState() val progress = state.toolbarState.progress + LaunchedEffect(uiState.selectedWireguardPort) { + if ( + uiState.selectedWireguardPort.isCustom() && + uiState.selectedWireguardPort != savedCustomPort.value + ) { + savedCustomPort.value = uiState.selectedWireguardPort + } + } + CollapsingToolbarScaffold( backgroundColor = MaterialTheme.colorScheme.background, modifier = Modifier.fillMaxSize(), @@ -273,7 +310,7 @@ fun VpnSettingsScreen( state = lazyListState ) { item { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) HeaderSwitchComposeCell( title = stringResource(R.string.auto_connect), isToggled = uiState.isAutoConnectEnabled, @@ -285,7 +322,7 @@ fun VpnSettingsScreen( SwitchComposeSubtitleCell(text = stringResource(id = R.string.auto_connect_footer)) } item { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) HeaderSwitchComposeCell( title = stringResource(R.string.local_network_sharing), isToggled = uiState.isAllowLanEnabled, @@ -295,7 +332,7 @@ fun VpnSettingsScreen( ) } item { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) MtuComposeCell(mtuValue = uiState.mtu, onEditMtu = { onMtuCellClick() }) } item { MtuSubtitle() } @@ -368,10 +405,10 @@ fun VpnSettingsScreen( ContentBlockersDisableModeCellSubtitle( Modifier.background(MaterialTheme.colorScheme.secondary) .padding( - start = cellHorizontalSpacing, + start = Dimens.cellStartPadding, top = topPadding, - end = cellHorizontalSpacing, - bottom = cellVerticalSpacing + end = Dimens.cellEndPadding, + bottom = Dimens.cellLabelVerticalPadding ) ) } @@ -379,7 +416,7 @@ fun VpnSettingsScreen( } itemWithDivider { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) InformationComposeCell( title = stringResource(R.string.obfuscation_title), onInfoClicked = { onObfuscationInfoClick() } @@ -408,7 +445,7 @@ fun VpnSettingsScreen( } itemWithDivider { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) InformationComposeCell( title = stringResource(R.string.quantum_resistant_title), onInfoClicked = { onQuantumResistanceInfoClicked() } @@ -439,7 +476,7 @@ fun VpnSettingsScreen( } itemWithDivider { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) InformationComposeCell( title = stringResource(id = R.string.wireguard_port_title), onInfoClicked = onWireguardPortInfoClicked @@ -465,8 +502,31 @@ fun VpnSettingsScreen( } } + itemWithDivider { + CustomPortCell( + title = stringResource(id = R.string.wireguard_custon_port_title), + isSelected = uiState.selectedWireguardPort.isCustom(), + port = + if (uiState.selectedWireguardPort.isCustom()) { + uiState.selectedWireguardPort.toDisplayCustomPort() + } else { + savedCustomPort.value.toDisplayCustomPort() + }, + onMainCellClicked = { + if (savedCustomPort.value is Constraint.Only) { + onWireguardPortSelected(savedCustomPort.value) + } else { + onShowCustomPortDialog() + } + }, + onPortCellClicked = { onShowCustomPortDialog() }, + mainTestTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG, + numberTestTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG + ) + } + item { - Spacer(modifier = Modifier.height(cellVerticalSpacing)) + Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) HeaderSwitchComposeCell( title = stringResource(R.string.enable_custom_dns), isToggled = uiState.isCustomDnsEnabled, @@ -511,10 +571,10 @@ fun VpnSettingsScreen( Modifier.background(MaterialTheme.colorScheme.secondary) .testTag(LAZY_LIST_LAST_ITEM_TEST_TAG) .padding( - start = cellHorizontalSpacing, + start = Dimens.cellStartPadding, top = topPadding, - end = cellHorizontalSpacing, - bottom = cellVerticalSpacing, + end = Dimens.cellEndPadding, + bottom = Dimens.cellLabelVerticalPadding, ) ) } 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 0413e52ed1..d6c9625668 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 @@ -124,7 +124,7 @@ sealed interface VpnSettingsUiState { override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(), override val selectedObfuscation: SelectedObfuscation = SelectedObfuscation.Off, override val quantumResistant: QuantumResistantState = QuantumResistantState.Off, - override val selectedWireguardPort: Constraint<Port> = Constraint.Any(), + override val selectedWireguardPort: Constraint<Port> = Constraint.Any() ) : VpnSettingsUiState data class QuantumResistanceInfoDialogUiState( @@ -153,4 +153,18 @@ sealed interface VpnSettingsUiState { override val selectedWireguardPort: Constraint<Port> = Constraint.Any(), val availablePortRanges: List<PortRange> = emptyList() ) : VpnSettingsUiState + + data class CustomPortDialogUiState( + 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 84b9e32afe..536a343594 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 @@ -6,6 +6,10 @@ 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" +const val LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG = + "lazy_list_wireguard_custom_port_text_test_tag" +const val LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG = + "lazy_list_wireguard_custom_port_number_test_tag" // SelectLocationScreen const val CIRCULAR_PROGRESS_INDICATOR = "circular_progress_indicator" diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomPortTextField.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomPortTextField.kt new file mode 100644 index 0000000000..956c65eedf --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomPortTextField.kt @@ -0,0 +1,29 @@ +package net.mullvad.mullvadvpn.compose.textfield + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import net.mullvad.mullvadvpn.R + +@Composable +fun CustomPortTextField( + value: String, + modifier: Modifier = Modifier, + onSubmit: (String) -> Unit, + onValueChanged: (String) -> Unit, + isValidValue: Boolean +) { + CustomTextField( + value = value, + keyboardType = KeyboardType.Number, + modifier = modifier, + placeholderText = stringResource(id = R.string.custom_port_dialog_placeholder), + onValueChanged = onValueChanged, + onFocusChange = {}, + onSubmit = onSubmit, + isDigitsOnlyAllowed = true, + isEnabled = true, + isValidValue = isValidValue + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt index 0efd386e50..d089a467ca 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt @@ -22,5 +22,7 @@ val MullvadWhite60 = Color(0x99FFFFFF) val MullvadWhite80 = Color(0xCCFFFFFF) const val AlphaVisible = 1f +const val AlphaDisabled = 0.2f const val AlphaInactive = 0.4f +const val AlphaDescription = 0.6f const val AlphaInvisible = 0f diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt index 3a0980e922..0d038fe10e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp data class Dimensions( + val buttonHeight: Dp = 44.dp, val cellEndPadding: Dp = 16.dp, val cellFooterTopPadding: Dp = 6.dp, val cellHeight: Dp = 52.dp, @@ -11,6 +12,7 @@ data class Dimensions( val cellStartPadding: Dp = 22.dp, val cityRowPadding: Dp = 34.dp, val countryRowPadding: Dp = 18.dp, + val customPortBoxMinWidth: Dp = 80.dp, val expandableCellChevronSize: Dp = 30.dp, val indentedCellStartPadding: Dp = 38.dp, val infoButtonVerticalPadding: Dp = 13.dp, @@ -33,7 +35,8 @@ data class Dimensions( val sideMargin: Dp = 22.dp, val smallPadding: Dp = 8.dp, val titleIconSize: Dp = 24.dp, - val verticalSpace: Dp = 20.dp + val verticalSpace: Dp = 20.dp, + val verticalSpacer: Dp = 1.dp ) val defaultDimensions = Dimensions() 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 45c7f4b14e..c8a2a4e914 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 @@ -57,7 +57,10 @@ class VpnSettingsFragment : BaseFragment() { onSelectQuantumResistanceSetting = vm::onSelectQuantumResistanceSetting, onQuantumResistanceInfoClicked = vm::onQuantumResistanceInfoClicked, onWireguardPortSelected = vm::onWireguardPortSelected, - onWireguardPortInfoClicked = vm::onWireguardPortInfoClicked + onWireguardPortInfoClicked = vm::onWireguardPortInfoClicked, + onShowCustomPortDialog = vm::onShowCustomPortDialog, + onCancelCustomPortDialogClick = vm::onCancelDialogClick, + onCloseCustomPortDialog = vm::onCancelDialogClick ) } } 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 deleted file mode 100644 index 9be01b2952..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ConstraintExtensions.kt +++ /dev/null @@ -1,10 +0,0 @@ -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/util/PortConstraintExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PortConstraintExtensions.kt new file mode 100644 index 0000000000..0a5167da2e --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PortConstraintExtensions.kt @@ -0,0 +1,23 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.constant.WIREGUARD_PRESET_PORTS +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 + } + +fun Constraint<Port>.isCustom() = + when (this) { + is Constraint.Any -> false + is Constraint.Only -> !WIREGUARD_PRESET_PORTS.contains(this.value.value) + } + +fun Constraint<Port>.toDisplayCustomPort() = + when (this) { + is Constraint.Any -> "" + is Constraint.Only -> this.value.value.toString() + } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PortRangeExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PortRangeExtensions.kt new file mode 100644 index 0000000000..7b7fa8b104 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PortRangeExtensions.kt @@ -0,0 +1,19 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.model.PortRange + +fun List<PortRange>.isPortInValidRanges(port: Int) = + this.any { portRange -> portRange.from <= port && portRange.to >= port } + +fun List<PortRange>.asString() = buildString { + this@asString.forEachIndexed { index, range -> + if (index != 0) { + append(", ") + } + if (range.from == range.to) { + append(range.from) + } else { + append("${range.from}-${range.to}") + } + } +} 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 a5fa4721bd..084e525d77 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 @@ -352,12 +352,17 @@ class VpnSettingsViewModel( serviceConnectionManager.relayListListener()?.selectedWireguardConstraints = WireguardConstraints(port = port) } + hideDialog() } fun onWireguardPortInfoClicked() { dialogState.update { VpnSettingsDialogState.WireguardPortInfoDialog } } + fun onShowCustomPortDialog() { + dialogState.update { VpnSettingsDialogState.CustomPortDialog } + } + private fun updateDefaultDnsOptionsViaRepository(contentBlockersOption: DefaultDnsOptions) = viewModelScope.launch(dispatcher) { repository.setDnsOptions( 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 b77b107037..ab749e02d9 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 @@ -146,6 +146,21 @@ data class VpnSettingsViewModelState( availablePortRanges = availablePortRanges ) } + is VpnSettingsDialogState.CustomPortDialog -> { + VpnSettingsUiState.CustomPortDialogUiState( + mtu = mtuValue, + isAutoConnectEnabled = isAutoConnectEnabled, + isLocalNetworkSharingEnabled = isLocalNetworkSharingEnabled, + isCustomDnsEnabled = isCustomDnsEnabled, + isAllowLanEnabled = isAllowLanEnabled, + customDnsItems = customDnsList, + contentBlockersOptions = contentBlockersOptions, + selectedObfuscation = selectedObfuscation, + quantumResistant = quantumResistant, + selectedWireguardPort = selectedWireguardPort, + availablePortRanges = availablePortRanges + ) + } else -> VpnSettingsUiState.DefaultUiState( mtu = mtuValue, @@ -203,6 +218,8 @@ sealed class VpnSettingsDialogState { object QuantumResistanceInfoDialog : VpnSettingsDialogState() object WireguardPortInfoDialog : VpnSettingsDialogState() + + object CustomPortDialog : 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 f82406aa23..bcfaa59ac0 100644 --- a/android/app/src/main/res/values-da/strings.xml +++ b/android/app/src/main/res/values-da/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Tid udløbet</string> <string name="paid_until">Betalt indtil</string> <string name="pay_to_start_using">For at begynde at bruge appen skal du først føje tid til din konto.</string> + <string name="port">Port</string> <string name="port_removal_notice">Dette vil slette alle videresendte porte. Lokale indstillinger vil blive gemt.</string> <string name="privacy_disclaimer_title">Privatliv</string> <string name="privacy_policy_label">Fortrolighedspolitik</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Altid-til VPN er måske aktiveret for en anden app</string> <string name="vpn_permission_error_notification_title">VPN-tilladelsesfejl</string> <string name="we_will_look_into_this">Vi vil undersøge dette.</string> + <string name="wireguard_custon_port_title">Brugerdefineret</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> + <string name="wireguard_port_info_port_range">Den brugerdefinerede port kan være en hvilken som helst værdi inden for de gyldige intervaller: %1$s.</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 fc423b7c63..911b145b3c 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Zeit abgelaufen</string> <string name="paid_until">Bezahlt bis</string> <string name="pay_to_start_using">Um mit der Nutzung dieser App zu beginnen, müssen Sie erst einmal Zeit zu Ihrem Konto hinzufügen.</string> + <string name="port">Port</string> <string name="port_removal_notice">Dadurch werden alle weitergeleiteten Ports gelöscht. Die lokalen Einstellungen werden gespeichert.</string> <string name="privacy_disclaimer_title">Datenschutz</string> <string name="privacy_policy_label">Datenschutzrichtlinie</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Always-on VPN könnte für eine andere App aktiviert sein</string> <string name="vpn_permission_error_notification_title">VPN-Berechtigungsfehler</string> <string name="we_will_look_into_this">Wir werden uns das anschauen.</string> + <string name="wireguard_custon_port_title">Benutzerdefiniert</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> + <string name="wireguard_port_info_port_range">Der benutzerdefinierte Port kann ein beliebiger Wert innerhalb dieser gültigen Bereiche sein: %1$s.</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 f365d518ae..49c9ee6fce 100644 --- a/android/app/src/main/res/values-es/strings.xml +++ b/android/app/src/main/res/values-es/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Tiempo agotado</string> <string name="paid_until">Pagado hasta</string> <string name="pay_to_start_using">Para empezar a usar la aplicación, primero necesita agregar tiempo a su cuenta.</string> + <string name="port">Puerto</string> <string name="port_removal_notice">Se eliminarán todos los puertos reenviados. La configuración local se guardará.</string> <string name="privacy_disclaimer_title">Privacidad</string> <string name="privacy_policy_label">Política de privacidad</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">La VPN siempre activa podría estar habilitada en otra aplicación</string> <string name="vpn_permission_error_notification_title">Error en la autorización de la VPN</string> <string name="we_will_look_into_this">Revisaremos esto.</string> + <string name="wireguard_custon_port_title">Personalizado</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> + <string name="wireguard_port_info_port_range">El puerto personalizado pueder ser cualquier valor dentro de los rangos válidos: %1$s.</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 f34f511072..a681bd4311 100644 --- a/android/app/src/main/res/values-fi/strings.xml +++ b/android/app/src/main/res/values-fi/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Ei käyttöaikaa</string> <string name="paid_until">Maksu ennen</string> <string name="pay_to_start_using">Voit aloittaa sovelluksen käyttämisen lisäämällä ensin aikaa tilillesi.</string> + <string name="port">Portti</string> <string name="port_removal_notice">Toiminto poistaa kaikki välitetyt portit. Paikalliset asetukset tallennetaan.</string> <string name="privacy_disclaimer_title">Tietosuoja</string> <string name="privacy_policy_label">Tietosuojakäytäntö</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Aina päällä oleva VPN on mahdollisesti otettu käyttöön toiselle sovellukselle</string> <string name="vpn_permission_error_notification_title">VPN-lupavirhe</string> <string name="we_will_look_into_this">Tutkimme asiaa.</string> + <string name="wireguard_custon_port_title">Mukautettu</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> + <string name="wireguard_port_info_port_range">Mukautettu portti voi olla mikä tahansa sallittu arvo: %1$s.</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 e5434473c4..0a6a848969 100644 --- a/android/app/src/main/res/values-fr/strings.xml +++ b/android/app/src/main/res/values-fr/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Plus de temps</string> <string name="paid_until">Payé jusqu\'au</string> <string name="pay_to_start_using">Pour commencer à utiliser l\'application, vous devez d\'abord ajouter du temps à votre compte.</string> + <string name="port">Port</string> <string name="port_removal_notice">Ceci supprimera tous les ports transférés. Les paramètres locaux seront enregistrés.</string> <string name="privacy_disclaimer_title">Confidentialité</string> <string name="privacy_policy_label">Politique de confidentialité</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">« Toujours exiger un VPN » est peut-être activé pour une autre application</string> <string name="vpn_permission_error_notification_title">Erreur de permission VPN</string> <string name="we_will_look_into_this">Nous allons nous pencher dessus.</string> + <string name="wireguard_custon_port_title">Personnalisé</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> + <string name="wireguard_port_info_port_range">Le port personnalisé peut prendre n\'importe quelle valeur dans les plages valides : %1$s.</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 f005c90a47..4040af912e 100644 --- a/android/app/src/main/res/values-it/strings.xml +++ b/android/app/src/main/res/values-it/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Scaduto</string> <string name="paid_until">Pagato fino al</string> <string name="pay_to_start_using">Per iniziare a utilizzare l\'app, devi prima aggiungere tempo al tuo account.</string> + <string name="port">Porta</string> <string name="port_removal_notice">Questa operazione eliminerà tutte le porte inoltrate. Le impostazioni locali verranno salvate.</string> <string name="privacy_disclaimer_title">Privacy</string> <string name="privacy_policy_label">Informativa sulla privacy</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">La VPN sempre attiva potrebbe essere abilitata per un\'altra app</string> <string name="vpn_permission_error_notification_title">Errore di autorizzazione VPN</string> <string name="we_will_look_into_this">Verificheremo.</string> + <string name="wireguard_custon_port_title">Personalizzato</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> + <string name="wireguard_port_info_port_range">La porta personalizzata può essere qualsiasi valore all\'interno degli intervalli validi: %1$s.</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 e50604b756..0b261161fc 100644 --- a/android/app/src/main/res/values-ja/strings.xml +++ b/android/app/src/main/res/values-ja/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">時間切れ</string> <string name="paid_until">次の日時まで支払い済み</string> <string name="pay_to_start_using">アプリを使い始めるには、まずはアカウントに時間を追加する必要があります。</string> + <string name="port">ポート</string> <string name="port_removal_notice">これにより、転送されたポートがすべて削除されます。ローカル設定は保存されます。</string> <string name="privacy_disclaimer_title">プライバシー</string> <string name="privacy_policy_label">プライバシーポリシー</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Always-on VPNが別のアプリで有効になっている可能性があります</string> <string name="vpn_permission_error_notification_title">VPN許可エラー</string> <string name="we_will_look_into_this">この問題を調査いたします。</string> + <string name="wireguard_custon_port_title">カスタム</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> + <string name="wireguard_port_info_port_range">カスタムポートは次の有効範囲内の任意の値に設定できます: %1$s。</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 72206d1f2d..90f3d04b38 100644 --- a/android/app/src/main/res/values-ko/strings.xml +++ b/android/app/src/main/res/values-ko/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">시간 초과</string> <string name="paid_until">유효 기간</string> <string name="pay_to_start_using">앱 사용을 시작하려면, 먼저 계정에 시간을 추가해야 합니다.</string> + <string name="port">포트</string> <string name="port_removal_notice">전달된 모든 포트가 삭제됩니다. 로컬 설정이 저장됩니다.</string> <string name="privacy_disclaimer_title">개인 정보 보호</string> <string name="privacy_policy_label">개인정보 보호정책</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">상시 접속 VPN이 다른 앱에 활성화되었을 수 있습니다.</string> <string name="vpn_permission_error_notification_title">VPN 권한 오류</string> <string name="we_will_look_into_this">조사해보겠습니다.</string> + <string name="wireguard_custon_port_title">사용자 지정</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> + <string name="wireguard_port_info_port_range">사용자 지정 포트는 유효한 범위 내의 모든 값이 될 수 있습니다: %1$s</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 ea41d88978..bc4728751f 100644 --- a/android/app/src/main/res/values-my/strings.xml +++ b/android/app/src/main/res/values-my/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">အချိန်စေ့သွားပါပြီ</string> <string name="paid_until">ဖော်ပြပါအထိ ပေးချေထားပြီး</string> <string name="pay_to_start_using">အက်ပ်ကို စသုံးရန်အတွက် ဦးစွာ သင့်အကောင့်တွင် အချိန်ပေါင်းထည့်ပေးရန် လိုအပ်ပါသည်။</string> + <string name="port">ပေါ့တ်</string> <string name="port_removal_notice">၎င်းသည် ပေးပို့ထားသော ports ကို ဖျက်ပါမည်။ စက်တွင်းဆက်တင်ကို သိမ်းထားပါမည်။</string> <string name="privacy_disclaimer_title">ကိုယ်ရေးအချက်အလက် လုံခြုံရေး</string> <string name="privacy_policy_label">ကိုယ်ပိုင်အချက်အလက် မူဝါဒ</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">အမြဲဖွင့် VPN ကို နောက်ထပ်အက်ပ်အတွက် ဖွင့်ထားနိုင်ပါသည်</string> <string name="vpn_permission_error_notification_title">VPN ခွင့်ပြုချက် ချို့ယွင်းချက်</string> <string name="we_will_look_into_this">ဤသည်ကို စစ်ဆေးလိုက်ပါမည်။</string> + <string name="wireguard_custon_port_title">စိတ်ကြိုက်</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> + <string name="wireguard_port_info_port_range">စိတ်ကြိုက်ပေါ့တ်သည် အကျုံးဝင် အပိုင်းအခြားများထဲမှ မည်သည့်တန်ဖိုးမဆို ဖြစ်နိုင်ပါသည်- %1$s ။</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 847c9c8978..375d7c012c 100644 --- a/android/app/src/main/res/values-nb/strings.xml +++ b/android/app/src/main/res/values-nb/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Tiden har utløpt</string> <string name="paid_until">Betalt fram til</string> <string name="pay_to_start_using">For å starte bruken av appen, må du først legge til tid til kontoen.</string> + <string name="port">Port</string> <string name="port_removal_notice">Dette vil slette alle formidlede porter. Lokale innstillinger blir lagret.</string> <string name="privacy_disclaimer_title">Personvern</string> <string name="privacy_policy_label">Retningslinjer for personvern</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">VPN som alltid er på, kan være aktivert for en annen app</string> <string name="vpn_permission_error_notification_title">Feil med VPN-tillatelse</string> <string name="we_will_look_into_this">Dette skal vi følge opp.</string> + <string name="wireguard_custon_port_title">Egendefinert</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> + <string name="wireguard_port_info_port_range">Den egendefinerte porten kan ha en hvilken som helst verdi innen det gyldige utvalget: %1$s.</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 64a93d45d1..5d8124537f 100644 --- a/android/app/src/main/res/values-nl/strings.xml +++ b/android/app/src/main/res/values-nl/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Geen tijd meer</string> <string name="paid_until">Betaald tot</string> <string name="pay_to_start_using">Om de app te gebruiken, moet u eerst tijd toevoegen aan uw account.</string> + <string name="port">Poort</string> <string name="port_removal_notice">Hiermee worden alle doorgestuurde poorten verwijderd. De lokale instellingen worden opgeslagen.</string> <string name="privacy_disclaimer_title">Privacy</string> <string name="privacy_policy_label">Privacybeleid</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Altijd-aan VPN is mogelijk ingeschakeld voor een andere app</string> <string name="vpn_permission_error_notification_title">VPN-machtigingsfout</string> <string name="we_will_look_into_this">We gaan het bekijken.</string> + <string name="wireguard_custon_port_title">Aangepast</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> + <string name="wireguard_port_info_port_range">De aangepaste poort kan elke waarde zijn binnen de geldige bereiken: %1$s.</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 90d87491ab..d38f5a7c39 100644 --- a/android/app/src/main/res/values-pl/strings.xml +++ b/android/app/src/main/res/values-pl/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Koniec czasu</string> <string name="paid_until">Płatne do</string> <string name="pay_to_start_using">Aby rozpocząć korzystanie z aplikacji, musisz najpierw dodać czas do swojego konta.</string> + <string name="port">Port</string> <string name="port_removal_notice">Spowoduje to usunięcie wszystkich przekierowanych portów. Ustawienia lokalne zostaną zapisane.</string> <string name="privacy_disclaimer_title">Prywatność</string> <string name="privacy_policy_label">Polityka prywatności</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Opcja „Zawsze włączony VPN” może być włączona dla innej aplikacji</string> <string name="vpn_permission_error_notification_title">Błąd uprawnienia VPN</string> <string name="we_will_look_into_this">Sprawdzimy to.</string> + <string name="wireguard_custon_port_title">Niestandardowy</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> + <string name="wireguard_port_info_port_range">Port niestandardowy może mieć dowolną wartość z następujących prawidłowych zakresów: %1$s.</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 1cf2bbb289..29e3e5df25 100644 --- a/android/app/src/main/res/values-pt/strings.xml +++ b/android/app/src/main/res/values-pt/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Sem tempo</string> <string name="paid_until">Pago até</string> <string name="pay_to_start_using">Para começar a utilizar a aplicação, primeiro tem de adicionar tempo à sua conta.</string> + <string name="port">Porta</string> <string name="port_removal_notice">Esta ação irá apagar todas as portas reencaminhadas. As definições locais serão guardadas.</string> <string name="privacy_disclaimer_title">Privacidade</string> <string name="privacy_policy_label">Política de privacidade</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">A VPN sempre ligada pode estar ativada para outra app</string> <string name="vpn_permission_error_notification_title">Erro de permissão da VPN</string> <string name="we_will_look_into_this">Vamos analisar esta situação.</string> + <string name="wireguard_custon_port_title">Personalizado</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> + <string name="wireguard_port_info_port_range">A porta personalizada pode ser qualquer valor dentro dos intervalos válidos: %1$s.</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 d394aeb07d..3fdc285af6 100644 --- a/android/app/src/main/res/values-ru/strings.xml +++ b/android/app/src/main/res/values-ru/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Закончилось время</string> <string name="paid_until">Оплачено до</string> <string name="pay_to_start_using">Чтобы пользоваться приложением, нужно добавить время на учетную запись.</string> + <string name="port">Порт</string> <string name="port_removal_notice">Будут удалены все перенаправленные порты. Локальные настройки сохранятся.</string> <string name="privacy_disclaimer_title">Конфиденциальность</string> <string name="privacy_policy_label">Политика конфиденциальности</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Опцию «VPN всегда вкл.» может быть включена для другого приложения</string> <string name="vpn_permission_error_notification_title">Ошибка разрешения для VPN</string> <string name="we_will_look_into_this">Мы рассмотрим эту проблему.</string> + <string name="wireguard_custon_port_title">Пользовательский</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> + <string name="wireguard_port_info_port_range">Пользовательский порт может принимать любое значение внутри допустимых диапазонов: %1$s.</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 1da6d39668..e85a4d2a20 100644 --- a/android/app/src/main/res/values-sv/strings.xml +++ b/android/app/src/main/res/values-sv/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Ingen tid kvar</string> <string name="paid_until">Betalat till</string> <string name="pay_to_start_using">Om du vill börja använda appen måste du först lägga till tid i ditt konto.</string> + <string name="port">Port</string> <string name="port_removal_notice">Detta tar bort alla vidarebefordrade portar. Lokala inställningar sparas.</string> <string name="privacy_disclaimer_title">Sekretess</string> <string name="privacy_policy_label">Sekretesspolicy</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">VPN som alltid är på kan ha aktiverats för annan app</string> <string name="vpn_permission_error_notification_title">Behörighetsfel med VPN</string> <string name="we_will_look_into_this">Vi kommer att undersöka detta.</string> + <string name="wireguard_custon_port_title">Anpassad</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> + <string name="wireguard_port_info_port_range">Den anpassade porten kan vara ett värde inom de giltiga intervallen: %1$s.</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 a85c2b8620..fefb659b84 100644 --- a/android/app/src/main/res/values-th/strings.xml +++ b/android/app/src/main/res/values-th/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">หมดเวลา</string> <string name="paid_until">ชำระเงินแล้วจนถึง</string> <string name="pay_to_start_using">คุณจำเป็นต้องเพิ่มเวลาไปยังบัญชีของคุณก่อน เพื่อที่จะเริ่มใช้งานแอป</string> + <string name="port">พอร์ต</string> <string name="port_removal_notice">นี่จะเป็นการลบพอร์ตที่ส่งต่อทั้งหมด และการตั้งค่าบนอุปกรณ์จะได้รับการบันทึกไว้</string> <string name="privacy_disclaimer_title">ความเป็นส่วนตัว</string> <string name="privacy_policy_label">นโยบายความเป็นส่วนตัว</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Always-on VPN อาจได้รับการเปิดใช้งานสำหรับแอปอื่น</string> <string name="vpn_permission_error_notification_title">เกิดข้อผิดพลาดในการอนุญาต VPN</string> <string name="we_will_look_into_this">เราจะตรวจสอบปัญหานี้</string> + <string name="wireguard_custon_port_title">กำหนดเอง</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> + <string name="wireguard_port_info_port_range">พอร์ตแบบกำหนดเองอาจมีค่าใดๆ ก็ได้ ภายในช่วงที่ใช้งานได้: %1$s</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 71672834ee..387db9c990 100644 --- a/android/app/src/main/res/values-tr/strings.xml +++ b/android/app/src/main/res/values-tr/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">Süre doldu</string> <string name="paid_until">Şu tarihe kadar ödendi:</string> <string name="pay_to_start_using">Uygulamayı kullanmaya başlamak için önce hesabınıza süre eklemeniz gerekir.</string> + <string name="port">Port</string> <string name="port_removal_notice">Bu işlem, yönlendirilen tüm portları silecek. Yerel ayarlar kaydedilecektir.</string> <string name="privacy_disclaimer_title">Gizlilik</string> <string name="privacy_policy_label">Gizlilik politikası</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">Her zaman açık VPN başka bir uygulama için etkinleştirilebilir</string> <string name="vpn_permission_error_notification_title">VPN izin hatası</string> <string name="we_will_look_into_this">Bunu araştıracağız.</string> + <string name="wireguard_custon_port_title">Özel</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> + <string name="wireguard_port_info_port_range">Özel port, geçerli aralıklar içindeki herhangi bir değer olabilir: %1$s.</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 48099250e7..2362f35b97 100644 --- a/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/android/app/src/main/res/values-zh-rCN/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">已没有时间</string> <string name="paid_until">到期时间</string> <string name="pay_to_start_using">要开始使用本应用,您首先需要向帐户中充入时间。</string> + <string name="port">端口</string> <string name="port_removal_notice">这将删除所有转发的端口。将保存本地设置。</string> <string name="privacy_disclaimer_title">隐私</string> <string name="privacy_policy_label">隐私政策</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">可能为另一个应用启用了“始终启用 VPN”</string> <string name="vpn_permission_error_notification_title">VPN 权限错误</string> <string name="we_will_look_into_this">我们将对此进行调查。</string> + <string name="wireguard_custon_port_title">自定义</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> + <string name="wireguard_port_info_port_range">自定义端口可以是有效范围内的任何值:%1$s。</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 8d7fcd491d..7b3b65a5fd 100644 --- a/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/android/app/src/main/res/values-zh-rTW/strings.xml @@ -122,6 +122,7 @@ <string name="out_of_time">逾時</string> <string name="paid_until">支付至</string> <string name="pay_to_start_using">需先在帳戶中加時,才能開始使用本應用程式。</string> + <string name="port">連接埠</string> <string name="port_removal_notice">這將刪除所有轉送的連接埠。將儲存本機設定。</string> <string name="privacy_disclaimer_title">隱私權</string> <string name="privacy_policy_label">隱私權政策</string> @@ -185,7 +186,9 @@ <string name="vpn_permission_error_notification_message">可能已為另一個應用程式啟用了「始終啟用 VPN」</string> <string name="vpn_permission_error_notification_title">VPN 權限錯誤</string> <string name="we_will_look_into_this">我們會對此進行調查。</string> + <string name="wireguard_custon_port_title">自訂</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> + <string name="wireguard_port_info_port_range">自訂連接埠可以是有效範圍內的任何值:%1$s。</string> </resources> diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index dfed235066..05e516c362 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -209,4 +209,12 @@ <![CDATA[No result for <b>%s</b>.]]> </string> <string name="select_location_empty_text_second_row">Try a different search.</string> + <string name="wireguard_port_info_port_range">The custom port can be any value inside the valid ranges: %s.</string> + <string name="wireguard_custon_port_title">Custom</string> + <string name="port">Port</string> + <string name="custom_port_dialog_title">WireGuard custom port</string> + <string name="custom_port_dialog_submit">Set port</string> + <string name="custom_port_dialog_remove">Remove custom port</string> + <string name="custom_port_dialog_valid_ranges">Valid ranges: %s</string> + <string name="custom_port_dialog_placeholder">Enter port</string> </resources> |
