diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 37562f411940..0b0ba6a4e90e 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -27,6 +27,9 @@ Line wrap the file at 100 chars. Th proxies. The access method is enabled by default. - Add multihop which allows the routing of traffic through an entry and exit server, making it harder to trace. +- Enable DAITA to route traffic through servers with DAITA support to enable the use + of all servers together with DAITA. This behaviour can be disabled with the use of the + "Direct only" setting. ### Changed - Animation has been changed to look better with predictive back. diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/constant/ContentType.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/constant/ContentType.kt index 04b29268a52b..47b6ba7923e9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/constant/ContentType.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/constant/ContentType.kt @@ -9,4 +9,5 @@ object ContentType { const val SPACER = 5 const val PROGRESS = 6 const val EMPTY_TEXT = 7 + const val BUTTON = 8 } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaDirectOnlyConfirmationDialog.kt similarity index 60% rename from android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt rename to android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaDirectOnlyConfirmationDialog.kt index b79814746c0a..a1b6e7bba09f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaDirectOnlyConfirmationDialog.kt @@ -1,8 +1,6 @@ package net.mullvad.mullvadvpn.compose.dialog -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -18,34 +16,24 @@ import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.dialog.info.InfoConfirmationDialog import net.mullvad.mullvadvpn.compose.dialog.info.InfoConfirmationDialogTitleType import net.mullvad.mullvadvpn.lib.theme.AppTheme -import net.mullvad.mullvadvpn.lib.theme.Dimens @Preview @Composable -private fun PreviewDaitaConfirmationDialog() { - AppTheme { DaitaConfirmation(EmptyResultBackNavigator()) } +private fun PreviewDaitaDirectOnlyConfirmationDialog() { + AppTheme { DaitaDirectOnlyConfirmation(EmptyResultBackNavigator()) } } @Destination(style = DestinationStyle.Dialog::class) @Composable -fun DaitaConfirmation(navigator: ResultBackNavigator) { +fun DaitaDirectOnlyConfirmation(navigator: ResultBackNavigator) { InfoConfirmationDialog( navigator = navigator, titleType = InfoConfirmationDialogTitleType.IconOnly, - confirmButtonTitle = stringResource(R.string.enable_anyway), - cancelButtonTitle = stringResource(R.string.back), + confirmButtonTitle = stringResource(R.string.enable_direct_only), + cancelButtonTitle = stringResource(R.string.cancel), ) { Text( - text = stringResource(id = R.string.daita_relay_subset_warning), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodySmall, - modifier = Modifier.fillMaxWidth(), - ) - - Spacer(modifier = Modifier.height(Dimens.verticalSpace)) - - Text( - text = stringResource(id = R.string.daita_warning, stringResource(id = R.string.daita)), + text = stringResource(id = R.string.direct_only_description), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodySmall, modifier = Modifier.fillMaxWidth(), diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaInfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaDirectOnlyInfoDialog.kt similarity index 64% rename from android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaInfoDialog.kt rename to android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaDirectOnlyInfoDialog.kt index 4cfbbb087e29..64e5cd46de33 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaInfoDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaDirectOnlyInfoDialog.kt @@ -14,22 +14,15 @@ import net.mullvad.mullvadvpn.lib.theme.AppTheme @Preview @Composable -private fun PreviewDaitaInfoDialog() { - AppTheme { DaitaInfo(EmptyDestinationsNavigator) } +private fun PreviewDaitaDirectOnlyInfoDialog() { + AppTheme { DaitaDirectOnlyInfo(EmptyDestinationsNavigator) } } @Destination(style = DestinationStyle.Dialog::class) @Composable -fun DaitaInfo(navigator: DestinationsNavigator) { +fun DaitaDirectOnlyInfo(navigator: DestinationsNavigator) { InfoDialog( - message = - stringResource( - id = R.string.daita_info, - stringResource(id = R.string.daita), - stringResource(id = R.string.daita_full), - ), - additionalInfo = - stringResource(id = R.string.daita_warning, stringResource(id = R.string.daita)), + message = stringResource(id = R.string.daita_info), onDismiss = dropUnlessResumed { navigator.navigateUp() }, ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SettingsUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SettingsUiStatePreviewParameterProvider.kt index 18f422a988af..5a7a6b276a3f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SettingsUiStatePreviewParameterProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SettingsUiStatePreviewParameterProvider.kt @@ -10,6 +10,7 @@ class SettingsUiStatePreviewParameterProvider : PreviewParameterProvider(style = SlideInFromRightTransition::class) +@Composable +fun Daita( + navigator: DestinationsNavigator, + daitaConfirmationDialogResult: ResultRecipient, +) { + val viewModel = koinViewModel() + val state by viewModel.uiState.collectAsStateWithLifecycle() + + daitaConfirmationDialogResult.OnNavResultValue { + if (it) { + viewModel.setDirectOnly(true) + } + } + + DaitaScreen( + state = state, + onDaitaEnabled = viewModel::setDaita, + onDirectOnly = { enable -> + if (enable) { + navigator.navigate(DaitaDirectOnlyConfirmationDestination) + } else { + viewModel.setDirectOnly(false) + } + }, + onDirectOnlyInfoClick = + dropUnlessResumed { navigator.navigate(DaitaDirectOnlyInfoDestination) }, + onBackClick = dropUnlessResumed { navigator.navigateUp() }, + ) +} + +@Composable +fun DaitaScreen( + state: DaitaUiState, + onDaitaEnabled: (enable: Boolean) -> Unit = {}, + onDirectOnly: (enable: Boolean) -> Unit = {}, + onDirectOnlyInfoClick: () -> Unit = {}, + onBackClick: () -> Unit = {}, +) { + ScaffoldWithMediumTopBar( + appBarTitle = stringResource(id = R.string.daita), + navigationIcon = { NavigateBackIconButton { onBackClick() } }, + ) { modifier -> + Column(modifier = modifier) { + val pagerState = rememberPagerState(pageCount = { DaitaPages.entries.size }) + DescriptionPager(pagerState = pagerState) + PageIndicator(pagerState = pagerState) + HeaderSwitchComposeCell( + title = stringResource(R.string.enable), + isToggled = state.daitaEnabled, + onCellClicked = onDaitaEnabled, + ) + HorizontalDivider() + HeaderSwitchComposeCell( + title = stringResource(R.string.direct_only), + isToggled = state.directOnly, + isEnabled = state.daitaEnabled, + onCellClicked = onDirectOnly, + onInfoClicked = onDirectOnlyInfoClick, + ) + } + } +} + +@Composable +private fun DescriptionPager(pagerState: PagerState) { + HorizontalPager( + state = pagerState, + verticalAlignment = Alignment.Top, + beyondViewportPageCount = DaitaPages.entries.size, + ) { page -> + Column(modifier = Modifier.fillMaxWidth()) { + val page = DaitaPages.entries[page] + // Scale image to fit width up to certain width + Image( + contentScale = ContentScale.FillWidth, + modifier = + Modifier.widthIn(max = Dimens.settingsDetailsImageMaxWidth) + .fillMaxWidth() + .padding(horizontal = Dimens.mediumPadding) + .align(Alignment.CenterHorizontally), + painter = painterResource(id = page.image), + contentDescription = stringResource(R.string.daita), + ) + DescriptionText( + firstParagraph = page.textFirstParagraph, + secondParagraph = page.textSecondParagraph, + thirdParagraph = page.textThirdParagraph, + ) + } + } +} + +@Composable +private fun DescriptionText(firstParagraph: Int, secondParagraph: Int, thirdParagraph: Int) { + SwitchComposeSubtitleCell( + modifier = Modifier.padding(vertical = Dimens.smallPadding), + text = + buildString { + appendLine(stringResource(firstParagraph)) + appendLine() + appendLine(stringResource(secondParagraph)) + appendLine() + append(stringResource(thirdParagraph)) + }, + ) +} + +@Composable +private fun PageIndicator(pagerState: PagerState) { + Row( + Modifier.wrapContentHeight().fillMaxWidth().padding(bottom = Dimens.mediumPadding), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.Bottom, + ) { + repeat(pagerState.pageCount) { iteration -> + val color = + if (pagerState.currentPage == iteration) MaterialTheme.colorScheme.onPrimary + else MaterialTheme.colorScheme.primary + Box( + modifier = + Modifier.padding(Dimens.indicatorPadding) + .clip(CircleShape) + .background(color) + .size(Dimens.indicatorSize) + ) + } + } +} + +private enum class DaitaPages( + val image: Int, + val textFirstParagraph: Int, + val textSecondParagraph: Int, + val textThirdParagraph: Int, +) { + FIRST( + image = R.drawable.daita_illustration_1, + textFirstParagraph = R.string.daita_description_slide_1_first_paragraph, + textSecondParagraph = R.string.daita_description_slide_1_second_paragraph, + textThirdParagraph = R.string.daita_description_slide_1_third_paragraph, + ), + SECOND( + image = R.drawable.daita_illustration_2, + textFirstParagraph = R.string.daita_description_slide_2_first_paragraph, + textSecondParagraph = R.string.daita_description_slide_2_second_paragraph, + textThirdParagraph = R.string.daita_description_slide_2_third_paragraph, + ), +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt index b8c418cd0681..75ba5abdd842 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt @@ -26,6 +26,7 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.ApiAccessListDestination import com.ramcosta.composedestinations.generated.destinations.AppInfoDestination +import com.ramcosta.composedestinations.generated.destinations.DaitaDestination import com.ramcosta.composedestinations.generated.destinations.MultihopDestination import com.ramcosta.composedestinations.generated.destinations.ReportProblemDestination import com.ramcosta.composedestinations.generated.destinations.SplitTunnelingDestination @@ -74,6 +75,7 @@ fun Settings(navigator: DestinationsNavigator) { onReportProblemCellClick = dropUnlessResumed { navigator.navigate(ReportProblemDestination) }, onMultihopClick = dropUnlessResumed { navigator.navigate(MultihopDestination) }, + onDaitaClick = dropUnlessResumed { navigator.navigate(DaitaDestination) }, onBackClick = dropUnlessResumed { navigator.navigateUp() }, ) } @@ -88,6 +90,7 @@ fun SettingsScreen( onReportProblemCellClick: () -> Unit = {}, onApiAccessClick: () -> Unit = {}, onMultihopClick: () -> Unit = {}, + onDaitaClick: () -> Unit = {}, onBackClick: () -> Unit = {}, ) { ScaffoldWithMediumTopBar( @@ -99,6 +102,9 @@ fun SettingsScreen( state = lazyListState, ) { if (state.isLoggedIn) { + itemWithDivider { + DaitaCell(isDaitaEnabled = state.isDaitaEnabled, onDaitaClick = onDaitaClick) + } itemWithDivider { MultihopCell( isMultihopEnabled = state.multihopEnabled, @@ -220,6 +226,23 @@ private fun PrivacyPolicy(state: SettingsUiState) { ) } +@Composable +private fun DaitaCell(isDaitaEnabled: Boolean, onDaitaClick: () -> Unit) { + val title = stringResource(id = R.string.daita) + TwoRowCell( + titleText = title, + subtitleText = + stringResource( + if (isDaitaEnabled) { + R.string.on + } else { + R.string.off + } + ), + onCellClicked = onDaitaClick, + ) +} + @Composable private fun MultihopCell(isMultihopEnabled: Boolean, onMultihopClick: () -> Unit) { val title = stringResource(id = R.string.multihop) 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 24b19cfae6f3..f76327243884 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 @@ -37,8 +37,6 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AutoConnectAndLockdownModeDestination import com.ramcosta.composedestinations.generated.destinations.ContentBlockersInfoDestination import com.ramcosta.composedestinations.generated.destinations.CustomDnsInfoDestination -import com.ramcosta.composedestinations.generated.destinations.DaitaConfirmationDestination -import com.ramcosta.composedestinations.generated.destinations.DaitaInfoDestination import com.ramcosta.composedestinations.generated.destinations.DnsDestination import com.ramcosta.composedestinations.generated.destinations.LocalNetworkSharingInfoDestination import com.ramcosta.composedestinations.generated.destinations.MalwareInfoDestination @@ -141,7 +139,6 @@ fun VpnSettings( dnsDialogResult: ResultRecipient, customWgPortResult: ResultRecipient, mtuDialogResult: ResultRecipient, - daitaConfirmationDialogResult: ResultRecipient, ) { val vm = koinViewModel() val state by vm.uiState.collectAsStateWithLifecycle() @@ -171,12 +168,6 @@ fun VpnSettings( } } - daitaConfirmationDialogResult.OnNavResultValue { doEnableDaita -> - if (doEnableDaita) { - vm.onToggleDaita(true) - } - } - val snackbarHostState = remember { SnackbarHostState() } val context = LocalContext.current CollectSideEffectWithLifecycle(vm.uiSideEffect) { @@ -223,16 +214,12 @@ fun VpnSettings( }, navigateToLocalNetworkSharingInfo = dropUnlessResumed { navigator.navigate(LocalNetworkSharingInfoDestination) }, - navigateToDaitaInfo = dropUnlessResumed { navigator.navigate(DaitaInfoDestination) }, - navigateToDaitaConfirmation = - dropUnlessResumed { navigator.navigate(DaitaConfirmationDestination) }, navigateToServerIpOverrides = dropUnlessResumed { navigator.navigate(ServerIpOverridesDestination) }, onToggleBlockTrackers = vm::onToggleBlockTrackers, onToggleBlockAds = vm::onToggleBlockAds, onToggleBlockMalware = vm::onToggleBlockMalware, onToggleLocalNetworkSharing = vm::onToggleLocalNetworkSharing, - onDisableDaita = { vm.onToggleDaita(false) }, onToggleBlockAdultContent = vm::onToggleBlockAdultContent, onToggleBlockGambling = vm::onToggleBlockGambling, onToggleBlockSocialMedia = vm::onToggleBlockSocialMedia, @@ -280,15 +267,12 @@ fun VpnSettingsScreen( navigateToQuantumResistanceInfo: () -> Unit = {}, navigateToWireguardPortInfo: (availablePortRanges: List) -> Unit = {}, navigateToLocalNetworkSharingInfo: () -> Unit = {}, - navigateToDaitaInfo: () -> Unit = {}, - navigateToDaitaConfirmation: () -> Unit = {}, navigateToWireguardPortDialog: () -> Unit = {}, navigateToServerIpOverrides: () -> Unit = {}, onToggleBlockTrackers: (Boolean) -> Unit = {}, onToggleBlockAds: (Boolean) -> Unit = {}, onToggleBlockMalware: (Boolean) -> Unit = {}, onToggleLocalNetworkSharing: (Boolean) -> Unit = {}, - onDisableDaita: () -> Unit = {}, onToggleBlockAdultContent: (Boolean) -> Unit = {}, onToggleBlockGambling: (Boolean) -> Unit = {}, onToggleBlockSocialMedia: (Boolean) -> Unit = {}, @@ -502,23 +486,6 @@ fun VpnSettingsScreen( ) } - item { - Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) - HeaderSwitchComposeCell( - title = stringResource(id = R.string.daita), - isToggled = state.isDaitaEnabled, - onCellClicked = { enable -> - if (enable) { - navigateToDaitaConfirmation() - } else { - onDisableDaita() - } - }, - onInfoClicked = navigateToDaitaInfo, - ) - Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) - } - itemWithDivider { InformationComposeCell( title = stringResource(id = R.string.wireguard_port_title), diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationList.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationList.kt index 8f07ab180e7b..3538aacff1b9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationList.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationList.kt @@ -1,19 +1,28 @@ package net.mullvad.mullvadvpn.compose.screen.location import androidx.compose.foundation.gestures.animateScrollBy +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.lifecycle.compose.collectAsStateWithLifecycle +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.button.PrimaryButton import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar import net.mullvad.mullvadvpn.compose.constant.ContentType @@ -23,6 +32,7 @@ import net.mullvad.mullvadvpn.compose.state.SelectLocationListUiState import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR import net.mullvad.mullvadvpn.compose.util.RunOnKeyChange import net.mullvad.mullvadvpn.lib.model.RelayItem +import net.mullvad.mullvadvpn.lib.theme.Dimens import net.mullvad.mullvadvpn.lib.theme.color.AlphaScrollbar import net.mullvad.mullvadvpn.viewmodel.location.SelectLocationListViewModel import org.koin.androidx.compose.koinViewModel @@ -33,6 +43,7 @@ fun SelectLocationList( backgroundColor: Color, relayListType: RelayListType, onSelectRelay: (RelayItem) -> Unit, + openDaitaSettings: () -> Unit, onUpdateBottomSheetState: (LocationBottomSheetState) -> Unit, ) { val viewModel = @@ -58,11 +69,20 @@ fun SelectLocationList( ), state = lazyListState, horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = + if (state is SelectLocationListUiState.EntryBlocked) { + Arrangement.Center + } else { + Arrangement.Top + }, ) { when (stateActual) { SelectLocationListUiState.Loading -> { loading() } + SelectLocationListUiState.EntryBlocked -> { + entryBlocked(openDaitaSettings = openDaitaSettings) + } is SelectLocationListUiState.Content -> { relayListContent( backgroundColor = backgroundColor, @@ -83,6 +103,28 @@ private fun LazyListScope.loading() { } } +private fun LazyListScope.entryBlocked(openDaitaSettings: () -> Unit) { + item(contentType = ContentType.DESCRIPTION) { + Text( + text = stringResource(R.string.multihop_entry_disabled_description), + style = MaterialTheme.typography.labelMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = Dimens.mediumPadding), + ) + } + item(contentType = ContentType.SPACER) { + Spacer(modifier = Modifier.height(Dimens.mediumPadding)) + } + item(contentType = ContentType.BUTTON) { + PrimaryButton( + text = stringResource(R.string.open_daita_settings), + onClick = openDaitaSettings, + modifier = Modifier.padding(horizontal = Dimens.mediumPadding), + ) + } +} + private fun SelectLocationListUiState.indexOfSelectedRelayItem(): Int? = if (this is SelectLocationListUiState.Content) { val index = diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreen.kt index 5ad5eb1507b2..2f37ce10bcc6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreen.kt @@ -39,6 +39,7 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.CreateCustomListDestination import com.ramcosta.composedestinations.generated.destinations.CustomListLocationsDestination import com.ramcosta.composedestinations.generated.destinations.CustomListsDestination +import com.ramcosta.composedestinations.generated.destinations.DaitaDestination import com.ramcosta.composedestinations.generated.destinations.DeleteCustomListDestination import com.ramcosta.composedestinations.generated.destinations.EditCustomListNameDestination import com.ramcosta.composedestinations.generated.destinations.FilterDestination @@ -190,6 +191,7 @@ fun SelectLocation( ) }, onSelectRelayList = vm::selectRelayList, + openDaitaSettings = dropUnlessResumed { navigator.navigate(DaitaDestination) }, ) } @@ -216,6 +218,7 @@ fun SelectLocationScreen( onEditLocationsCustomList: (RelayItem.CustomList) -> Unit = {}, onDeleteCustomList: (RelayItem.CustomList) -> Unit = {}, onSelectRelayList: (RelayListType) -> Unit = {}, + openDaitaSettings: () -> Unit = {}, ) { val backgroundColor = MaterialTheme.colorScheme.surface @@ -282,6 +285,7 @@ fun SelectLocationScreen( state = state, backgroundColor = backgroundColor, onSelectRelay = onSelectRelay, + openDaitaSettings = openDaitaSettings, onUpdateBottomSheetState = { newState -> locationBottomSheetState = newState }, ) } @@ -312,6 +316,7 @@ private fun RelayLists( state: SelectLocationUiState, backgroundColor: Color, onSelectRelay: (RelayItem) -> Unit, + openDaitaSettings: () -> Unit, onUpdateBottomSheetState: (LocationBottomSheetState) -> Unit, ) { // For multihop we want to start on the entry list. @@ -346,6 +351,7 @@ private fun RelayLists( backgroundColor = backgroundColor, relayListType = RelayListType.entries[pageIndex], onSelectRelay = onSelectRelay, + openDaitaSettings = openDaitaSettings, onUpdateBottomSheetState = onUpdateBottomSheetState, ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/DaitaUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/DaitaUiState.kt new file mode 100644 index 000000000000..59f00d0347c7 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/DaitaUiState.kt @@ -0,0 +1,3 @@ +package net.mullvad.mullvadvpn.compose.state + +data class DaitaUiState(val daitaEnabled: Boolean, val directOnly: Boolean) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationListUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationListUiState.kt index bb320de81d3f..d470187bcf88 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationListUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationListUiState.kt @@ -6,6 +6,8 @@ sealed interface SelectLocationListUiState { data object Loading : SelectLocationListUiState + data object EntryBlocked : SelectLocationListUiState + data class Content( val relayListItems: List, val customLists: List, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SettingsUiState.kt index 4ebbf9ad23fe..ad8dbd0e22a2 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SettingsUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SettingsUiState.kt @@ -4,6 +4,7 @@ data class SettingsUiState( val appVersion: String, val isLoggedIn: Boolean, val isSupportedVersion: Boolean, + val isDaitaEnabled: Boolean, val isPlayBuild: Boolean, val multihopEnabled: Boolean, ) 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 eede76ff7c01..49d0ebd4aa34 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 @@ -12,7 +12,6 @@ import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem data class VpnSettingsUiState( val mtu: Mtu?, val isLocalNetworkSharingEnabled: Boolean, - val isDaitaEnabled: Boolean, val isCustomDnsEnabled: Boolean, val customDnsItems: List, val contentBlockersOptions: DefaultDnsOptions, @@ -34,7 +33,6 @@ data class VpnSettingsUiState( fun createDefault( mtu: Mtu? = null, isLocalNetworkSharingEnabled: Boolean = false, - isDaitaEnabled: Boolean = false, isCustomDnsEnabled: Boolean = false, customDnsItems: List = emptyList(), contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(), @@ -51,7 +49,6 @@ data class VpnSettingsUiState( VpnSettingsUiState( mtu, isLocalNetworkSharingEnabled, - isDaitaEnabled, isCustomDnsEnabled, customDnsItems, contentBlockersOptions, 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 1d62de5bb25b..5591f79a478e 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 @@ -63,6 +63,7 @@ import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel import net.mullvad.mullvadvpn.viewmodel.CreateCustomListDialogViewModel import net.mullvad.mullvadvpn.viewmodel.CustomListLocationsViewModel import net.mullvad.mullvadvpn.viewmodel.CustomListsViewModel +import net.mullvad.mullvadvpn.viewmodel.DaitaViewModel import net.mullvad.mullvadvpn.viewmodel.DeleteApiAccessMethodConfirmationViewModel import net.mullvad.mullvadvpn.viewmodel.DeleteCustomListConfirmationViewModel import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel @@ -219,7 +220,7 @@ val uiModule = module { viewModel { LoginViewModel(get(), get(), get()) } viewModel { PrivacyDisclaimerViewModel(get(), IS_PLAY_BUILD) } viewModel { SelectLocationViewModel(get(), get(), get(), get(), get(), get()) } - viewModel { SettingsViewModel(get(), get(), get(), IS_PLAY_BUILD) } + viewModel { SettingsViewModel(get(), get(), get(), get(), IS_PLAY_BUILD) } viewModel { SplashViewModel(get(), get(), get(), get()) } viewModel { VoucherDialogViewModel(get()) } viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get()) } @@ -263,8 +264,9 @@ val uiModule = module { ) } viewModel { (relayListType: RelayListType) -> - SelectLocationListViewModel(relayListType, get(), get(), get(), get(), get(), get()) + SelectLocationListViewModel(relayListType, get(), get(), get(), get(), get(), get(), get()) } + viewModel { DaitaViewModel(get()) } // This view model must be single so we correctly attach lifecycle and share it with activity single { NoDaemonViewModel(get()) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt index 0aeef5e219a1..e1c8273cf86e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt @@ -73,9 +73,9 @@ fun RelayItem.CustomList.filter( fun RelayItem.Location.Country.filter( ownership: Constraint, providers: Constraint, - shouldFilterByDaita: Boolean, + filterDaita: Boolean, ): RelayItem.Location.Country? { - val cities = cities.mapNotNull { it.filter(ownership, providers, shouldFilterByDaita) } + val cities = cities.mapNotNull { it.filter(ownership, providers, filterDaita) } return if (cities.isNotEmpty()) { this.copy(cities = cities) } else { @@ -96,8 +96,8 @@ private fun RelayItem.Location.City.filter( } } -private fun RelayItem.Location.Relay.hasMatchingDaitaSetting(isDaitaEnabled: Boolean): Boolean { - return if (isDaitaEnabled) daita else true +private fun RelayItem.Location.Relay.hasMatchingDaitaSetting(filterDaita: Boolean): Boolean { + return if (filterDaita) daita else true } private fun RelayItem.Location.Relay.filter( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt index 8be8d2ae7ed2..e6b03ee59980 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt @@ -72,4 +72,6 @@ class SettingsRepository( managementService.setAllowLan(isEnabled) suspend fun setDaitaEnabled(enabled: Boolean) = managementService.setDaitaEnabled(enabled) + + suspend fun setDaitaDirectOnly(enabled: Boolean) = managementService.setDaitaDirectOnly(enabled) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt index 3874785920dc..b6f9f3ca2d92 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt @@ -8,6 +8,7 @@ import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.Ownership import net.mullvad.mullvadvpn.lib.model.Provider import net.mullvad.mullvadvpn.lib.model.Providers +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository @@ -38,7 +39,7 @@ class FilterChipUseCase( selectedOwnership = selectedOwnership, selectedConstraintProviders = selectedConstraintProviders, allProviders = allProviders, - isDaitaEnabled = settings?.isDaitaEnabled() == true, + isDaitaEnabled = settings?.daitaAndDirectOnly() == true, isMultihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListType, ) @@ -88,6 +89,10 @@ class FilterChipUseCase( ): List = if (selectedOwnership == null) selectedProviders else selectedProviders.filter { it.ownership == selectedOwnership } + + private fun Settings.daitaAndDirectOnly() = + tunnelOptions.wireguard.daitaSettings.enabled && + tunnelOptions.wireguard.daitaSettings.directOnly } sealed interface FilterChip { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt index 5a83e483d5ee..d896b6c82a1b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt @@ -6,6 +6,7 @@ import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.Ownership import net.mullvad.mullvadvpn.lib.model.Providers import net.mullvad.mullvadvpn.lib.model.RelayItem +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.relaylist.filter import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.RelayListRepository @@ -32,7 +33,7 @@ class FilteredRelayListUseCase( providers = selectedProviders, shouldFilterByDaita = showOnlyRelaysWithDaita( - isDaitaEnabled = settings?.isDaitaEnabled() == true, + isDaitaEnabled = settings?.daitaAndDirectOnly() == true, isMultihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListType, ), @@ -44,4 +45,8 @@ class FilteredRelayListUseCase( providers: Constraint, shouldFilterByDaita: Boolean, ) = mapNotNull { it.filter(ownership, providers, shouldFilterByDaita) } + + private fun Settings.daitaAndDirectOnly() = + tunnelOptions.wireguard.daitaSettings.enabled && + tunnelOptions.wireguard.daitaSettings.directOnly } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt index af519841a249..f18721bbb642 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt @@ -7,6 +7,7 @@ import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.Ownership import net.mullvad.mullvadvpn.lib.model.Providers import net.mullvad.mullvadvpn.lib.model.RelayItem +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.relaylist.filter import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.SettingsRepository @@ -33,7 +34,7 @@ class FilterCustomListsRelayItemUseCase( providers = selectedProviders, shouldFilterByDaita = showOnlyRelaysWithDaita( - isDaitaEnabled = settings?.isDaitaEnabled() == true, + isDaitaEnabled = settings?.daitaAndDirectOnly() == true, isMultihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListType, ), @@ -45,4 +46,8 @@ class FilterCustomListsRelayItemUseCase( providers: Constraint, shouldFilterByDaita: Boolean, ) = mapNotNull { it.filter(ownership, providers, shouldFilterByDaita = shouldFilterByDaita) } + + private fun Settings.daitaAndDirectOnly() = + tunnelOptions.wireguard.daitaSettings.enabled && + tunnelOptions.wireguard.daitaSettings.directOnly } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt new file mode 100644 index 000000000000..3243239fedcd --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt @@ -0,0 +1,38 @@ +package net.mullvad.mullvadvpn.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import net.mullvad.mullvadvpn.compose.state.DaitaUiState +import net.mullvad.mullvadvpn.lib.model.Settings +import net.mullvad.mullvadvpn.repository.SettingsRepository + +class DaitaViewModel(private val settingsRepository: SettingsRepository) : ViewModel() { + + val uiState = + settingsRepository.settingsUpdates + .map { settings -> + DaitaUiState( + daitaEnabled = settings?.daitaSettings()?.enabled == true, + directOnly = settings?.daitaSettings()?.directOnly == true, + ) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = DaitaUiState(daitaEnabled = false, directOnly = false), + ) + + fun setDaita(enable: Boolean) { + viewModelScope.launch { settingsRepository.setDaitaEnabled(enable) } + } + + fun setDirectOnly(enable: Boolean) { + viewModelScope.launch { settingsRepository.setDaitaDirectOnly(enable) } + } + + private fun Settings.daitaSettings() = tunnelOptions.wireguard.daitaSettings +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModel.kt index 22309fecfd6a..5cc6f1562b43 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModel.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.stateIn import net.mullvad.mullvadvpn.compose.state.SettingsUiState import net.mullvad.mullvadvpn.lib.model.DeviceState import net.mullvad.mullvadvpn.lib.shared.DeviceRepository +import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository @@ -16,6 +17,7 @@ class SettingsViewModel( deviceRepository: DeviceRepository, appVersionInfoRepository: AppVersionInfoRepository, wireguardConstraintsRepository: WireguardConstraintsRepository, + settingsRepository: SettingsRepository, isPlayBuild: Boolean, ) : ViewModel() { @@ -24,13 +26,16 @@ class SettingsViewModel( deviceRepository.deviceState, appVersionInfoRepository.versionInfo, wireguardConstraintsRepository.wireguardConstraints, - ) { deviceState, versionInfo, wireguardConstraints -> + settingsRepository.settingsUpdates, + ) { deviceState, versionInfo, wireguardConstraints, settings -> SettingsUiState( isLoggedIn = deviceState is DeviceState.LoggedIn, appVersion = versionInfo.currentVersion, isSupportedVersion = versionInfo.isSupported, + multihopEnabled = wireguardConstraints?.isMultihopEnabled == true, + isDaitaEnabled = + settings?.tunnelOptions?.wireguard?.daitaSettings?.enabled == true, isPlayBuild = isPlayBuild, - multihopEnabled = wireguardConstraints?.isMultihopEnabled ?: false, ) } .stateIn( @@ -40,6 +45,7 @@ class SettingsViewModel( appVersion = "", isLoggedIn = false, isSupportedVersion = true, + isDaitaEnabled = false, isPlayBuild = isPlayBuild, multihopEnabled = false, ), 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 e160776ee09d..90f98fceaa8a 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 @@ -67,9 +67,8 @@ class VpnSettingsViewModel( ) { settings, portRanges, customWgPort, autoStartAndConnectOnBoot -> VpnSettingsViewModelState( mtuValue = settings?.tunnelOptions?.wireguard?.mtu, - isLocalNetworkSharingEnabled = settings?.allowLan ?: false, - isDaitaEnabled = settings?.isDaitaEnabled() ?: false, - isCustomDnsEnabled = settings?.isCustomDnsEnabled() ?: false, + isLocalNetworkSharingEnabled = settings?.allowLan == true, + isCustomDnsEnabled = settings?.isCustomDnsEnabled() == true, customDnsList = settings?.addresses()?.asStringAddressList() ?: listOf(), contentBlockersOptions = settings?.contentBlockersSettings() ?: DefaultDnsOptions(), 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 624716198d6f..e8ccf8f4a0d2 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 @@ -12,7 +12,6 @@ import net.mullvad.mullvadvpn.lib.model.QuantumResistantState data class VpnSettingsViewModelState( val mtuValue: Mtu?, val isLocalNetworkSharingEnabled: Boolean, - val isDaitaEnabled: Boolean, val isCustomDnsEnabled: Boolean, val customDnsList: List, val contentBlockersOptions: DefaultDnsOptions, @@ -34,7 +33,6 @@ data class VpnSettingsViewModelState( VpnSettingsUiState( mtuValue, isLocalNetworkSharingEnabled, - isDaitaEnabled, isCustomDnsEnabled, customDnsList, contentBlockersOptions, @@ -54,7 +52,6 @@ data class VpnSettingsViewModelState( VpnSettingsViewModelState( mtuValue = null, isLocalNetworkSharingEnabled = false, - isDaitaEnabled = false, isCustomDnsEnabled = false, customDnsList = listOf(), contentBlockersOptions = DefaultDnsOptions(), diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt index d5063f0f4434..46d8ac519de5 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt @@ -12,7 +12,9 @@ import net.mullvad.mullvadvpn.compose.state.SelectLocationListUiState import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.GeoLocationId import net.mullvad.mullvadvpn.lib.model.RelayItemId +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.RelayListRepository +import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.usecase.FilteredRelayListUseCase import net.mullvad.mullvadvpn.usecase.SelectedLocationUseCase @@ -27,16 +29,25 @@ class SelectLocationListViewModel( private val wireguardConstraintsRepository: WireguardConstraintsRepository, private val relayListRepository: RelayListRepository, customListsRelayItemUseCase: CustomListsRelayItemUseCase, + settingsRepository: SettingsRepository, ) : ViewModel() { private val _expandedItems: MutableStateFlow> = MutableStateFlow(initialExpand(initialSelection())) val uiState: StateFlow = - combine(relayListItems(), customListsRelayItemUseCase()) { relayListItems, customLists -> - SelectLocationListUiState.Content( - relayListItems = relayListItems, - customLists = customLists, - ) + combine( + relayListItems(), + customListsRelayItemUseCase(), + settingsRepository.settingsUpdates, + ) { relayListItems, customLists, settings -> + if (relayListType == RelayListType.ENTRY && settings?.entryBlocked() == true) { + SelectLocationListUiState.EntryBlocked + } else { + SelectLocationListUiState.Content( + relayListItems = relayListItems, + customLists = customLists, + ) + } } .stateIn(viewModelScope, SharingStarted.Lazily, SelectLocationListUiState.Loading) @@ -86,4 +97,11 @@ class SelectLocationListViewModel( wireguardConstraintsRepository.wireguardConstraints.value?.entryLocation RelayListType.EXIT -> relayListRepository.selectedLocation.value }?.getOrNull() + + // If Daita is enabled without direct only, it is not possible to manually select the entry + // location. + private fun Settings.entryBlocked() = + tunnelOptions.wireguard.daitaSettings.enabled && + !tunnelOptions.wireguard.daitaSettings.directOnly && + relaySettings.relayConstraints.wireguardConstraints.isMultihopEnabled } diff --git a/android/app/src/main/res/drawable/daita_illustration_1.xml b/android/app/src/main/res/drawable/daita_illustration_1.xml new file mode 100644 index 000000000000..918f0c9e6e63 --- /dev/null +++ b/android/app/src/main/res/drawable/daita_illustration_1.xml @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/daita_illustration_2.xml b/android/app/src/main/res/drawable/daita_illustration_2.xml new file mode 100644 index 000000000000..b8de37fadfb7 --- /dev/null +++ b/android/app/src/main/res/drawable/daita_illustration_2.xml @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCaseTest.kt index 8b3d6d68a2ae..221d89cf4006 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCaseTest.kt @@ -108,10 +108,14 @@ class FilterChipUseCaseTest { } @Test - fun `when Daita is enabled and multihop is disabled should return Daita filter chip`() = + fun `when Daita with direct only is enabled and multihop is disabled should return Daita filter chip`() = runTest { // Arrange - settings.value = mockk(relaxed = true) { every { isDaitaEnabled() } returns true } + settings.value = + mockk(relaxed = true) { + every { this@mockk.tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { tunnelOptions.wireguard.daitaSettings.directOnly } returns true + } wireguardConstraints.value = mockk(relaxed = true) { every { isMultihopEnabled } returns false } @@ -121,10 +125,29 @@ class FilterChipUseCaseTest { } @Test - fun `when Daita is enabled and multihop is enabled and relay list type is entry should return Daita filter chip`() = + fun `when Daita without direct only is enabled and multihop is disabled should return no filter chip`() = runTest { // Arrange - settings.value = mockk(relaxed = true) { every { isDaitaEnabled() } returns true } + settings.value = + mockk(relaxed = true) { + every { tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { tunnelOptions.wireguard.daitaSettings.directOnly } returns false + } + wireguardConstraints.value = + mockk(relaxed = true) { every { isMultihopEnabled } returns false } + + filterChipUseCase(RelayListType.EXIT).test { assertLists(emptyList(), awaitItem()) } + } + + @Test + fun `when Daita with direct only is enabled and multihop is enabled and relay list type is entry should return Daita filter chip`() = + runTest { + // Arrange + settings.value = + mockk(relaxed = true) { + every { tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { tunnelOptions.wireguard.daitaSettings.directOnly } returns true + } wireguardConstraints.value = mockk(relaxed = true) { every { isMultihopEnabled } returns true } @@ -134,10 +157,29 @@ class FilterChipUseCaseTest { } @Test - fun `when Daita is enabled and multihop is enabled and relay list type is exit should return no filter`() = + fun `when Daita with direct only is enabled and multihop is enabled and relay list type is exit should return no filter`() = + runTest { + // Arrange + settings.value = + mockk(relaxed = true) { + every { tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { tunnelOptions.wireguard.daitaSettings.directOnly } returns true + } + wireguardConstraints.value = + mockk(relaxed = true) { every { isMultihopEnabled } returns true } + + filterChipUseCase(RelayListType.EXIT).test { assertLists(emptyList(), awaitItem()) } + } + + @Test + fun `when Daita without direct only is enabled and multihop is enabled and relay list type is exit should return no filter`() = runTest { // Arrange - settings.value = mockk(relaxed = true) { every { isDaitaEnabled() } returns true } + settings.value = + mockk(relaxed = true) { + every { tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { tunnelOptions.wireguard.daitaSettings.directOnly } returns false + } wireguardConstraints.value = mockk(relaxed = true) { every { isMultihopEnabled } returns true } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt index f2468cbb11e0..7bc05df05c9c 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt @@ -12,8 +12,10 @@ import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.DeviceState +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.lib.model.WireguardConstraints import net.mullvad.mullvadvpn.lib.shared.DeviceRepository +import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.ui.VersionInfo import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository @@ -28,10 +30,12 @@ class SettingsViewModelTest { private val mockDeviceRepository: DeviceRepository = mockk() private val mockAppVersionInfoRepository: AppVersionInfoRepository = mockk() private val mockWireguardConstraintsRepository: WireguardConstraintsRepository = mockk() + private val mockSettingsRepository: SettingsRepository = mockk() private val versionInfo = MutableStateFlow(VersionInfo(currentVersion = "", isSupported = false)) private val wireguardConstraints = MutableStateFlow(mockk(relaxed = true)) + private val settings = MutableStateFlow(mockk(relaxed = true)) private lateinit var viewModel: SettingsViewModel @@ -43,12 +47,14 @@ class SettingsViewModelTest { every { mockAppVersionInfoRepository.versionInfo } returns versionInfo every { mockWireguardConstraintsRepository.wireguardConstraints } returns wireguardConstraints + every { mockSettingsRepository.settingsUpdates } returns settings viewModel = SettingsViewModel( deviceRepository = mockDeviceRepository, appVersionInfoRepository = mockAppVersionInfoRepository, wireguardConstraintsRepository = mockWireguardConstraintsRepository, + settingsRepository = mockSettingsRepository, isPlayBuild = false, ) } 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 427b003d332c..aae283e91ec4 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 @@ -18,8 +18,10 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import mullvad_daemon.management_interface.daitaSettings import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.Constraint +import net.mullvad.mullvadvpn.lib.model.DaitaSettings import net.mullvad.mullvadvpn.lib.model.Mtu import net.mullvad.mullvadvpn.lib.model.Port import net.mullvad.mullvadvpn.lib.model.PortRange @@ -131,7 +133,7 @@ class VpnSettingsViewModelTest { WireguardTunnelOptions( mtu = Mtu(0), quantumResistant = expectedResistantState, - daita = false, + daitaSettings = DaitaSettings(enabled = false, directOnly = false), ) every { mockSettings.tunnelOptions } returns mockTunnelOptions @@ -167,7 +169,7 @@ class VpnSettingsViewModelTest { WireguardTunnelOptions( mtu = null, quantumResistant = QuantumResistantState.Off, - daita = false, + daitaSettings = DaitaSettings(enabled = false, directOnly = false), ), dnsOptions = mockk(relaxed = true), ) diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt index 358487717016..acdf3f5c9557 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt @@ -15,7 +15,9 @@ import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.GeoLocationId import net.mullvad.mullvadvpn.lib.model.RelayItem import net.mullvad.mullvadvpn.lib.model.RelayItemSelection +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.RelayListRepository +import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.usecase.FilteredRelayListUseCase import net.mullvad.mullvadvpn.usecase.SelectedLocationUseCase @@ -36,12 +38,14 @@ class SelectLocationListViewModelTest { private val mockWireguardConstraintsRepository: WireguardConstraintsRepository = mockk() private val mockRelayListRepository: RelayListRepository = mockk() private val mockCustomListRelayItemsUseCase: CustomListsRelayItemUseCase = mockk() + private val mockSettingsRepository: SettingsRepository = mockk() private val filteredRelayList = MutableStateFlow>(emptyList()) private val selectedLocationFlow = MutableStateFlow(mockk(relaxed = true)) private val filteredCustomListRelayItems = MutableStateFlow>(emptyList()) private val customListRelayItems = MutableStateFlow>(emptyList()) + private val settings = MutableStateFlow(mockk(relaxed = true)) private lateinit var viewModel: SelectLocationListViewModel @@ -57,6 +61,7 @@ class SelectLocationListViewModelTest { every { mockFilteredCustomListRelayItemsUseCase(any()) } returns filteredCustomListRelayItems every { mockCustomListRelayItemsUseCase() } returns customListRelayItems + every { mockSettingsRepository.settingsUpdates } returns settings } @Test @@ -125,6 +130,7 @@ class SelectLocationListViewModelTest { wireguardConstraintsRepository = mockWireguardConstraintsRepository, relayListRepository = mockRelayListRepository, customListsRelayItemUseCase = mockCustomListRelayItemsUseCase, + settingsRepository = mockSettingsRepository, ) private fun RelayListItem.relayItemId() = diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt index bd27574cbeb2..870859f95ff5 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt @@ -62,6 +62,7 @@ import net.mullvad.mullvadvpn.lib.model.CustomList as ModelCustomList import net.mullvad.mullvadvpn.lib.model.CustomListAlreadyExists import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.CustomListName +import net.mullvad.mullvadvpn.lib.model.DaitaSettings import net.mullvad.mullvadvpn.lib.model.DeleteCustomListError import net.mullvad.mullvadvpn.lib.model.DeleteDeviceError import net.mullvad.mullvadvpn.lib.model.Device @@ -123,6 +124,8 @@ import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken import net.mullvad.mullvadvpn.lib.model.WireguardEndpointData as ModelWireguardEndpointData import net.mullvad.mullvadvpn.lib.model.addresses import net.mullvad.mullvadvpn.lib.model.customOptions +import net.mullvad.mullvadvpn.lib.model.directOnly +import net.mullvad.mullvadvpn.lib.model.enabled import net.mullvad.mullvadvpn.lib.model.entryLocation import net.mullvad.mullvadvpn.lib.model.isMultihopEnabled import net.mullvad.mullvadvpn.lib.model.location @@ -508,15 +511,19 @@ class ManagementService( suspend fun setDaitaEnabled(enabled: Boolean): Either = Either.catch { - val daitaSettings = - ManagementInterface.DaitaSettings.newBuilder() - .setEnabled(enabled) - // Before Multihop is supported on Android, calling `setDirectOnly` with - // false will cause undefined behaviour. Will be fixed by as part of - // DROID-1412. - .setDirectOnly(true) - .build() - grpc.setDaitaSettings(daitaSettings) + val currentDaitaSettings = getSettings().tunnelOptions.wireguard.daitaSettings + val updatedDaitaSettings = DaitaSettings.enabled.set(currentDaitaSettings, enabled) + grpc.setDaitaSettings(updatedDaitaSettings.fromDomain()) + } + .mapLeft(SetDaitaSettingsError::Unknown) + .mapEmpty() + + suspend fun setDaitaDirectOnly(enabled: Boolean): Either = + Either.catch { + val currentDaitaSettings = getSettings().tunnelOptions.wireguard.daitaSettings + val updatedDaitaSettings = + DaitaSettings.directOnly.set(currentDaitaSettings, enabled) + grpc.setDaitaSettings(updatedDaitaSettings.fromDomain()) } .mapLeft(SetDaitaSettingsError::Unknown) .mapEmpty() diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt index b3fe88bdc8f2..f62124a1712b 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt @@ -8,6 +8,7 @@ import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions import net.mullvad.mullvadvpn.lib.model.CustomList import net.mullvad.mullvadvpn.lib.model.CustomListId +import net.mullvad.mullvadvpn.lib.model.DaitaSettings import net.mullvad.mullvadvpn.lib.model.DefaultDnsOptions import net.mullvad.mullvadvpn.lib.model.DnsOptions import net.mullvad.mullvadvpn.lib.model.DnsState @@ -253,3 +254,9 @@ internal fun ShadowsocksSettings.fromDomain(): ManagementInterface.ShadowsocksSe is Constraint.Only -> ManagementInterface.ShadowsocksSettings.newBuilder().setPort(port.value.value).build() } + +internal fun DaitaSettings.fromDomain(): ManagementInterface.DaitaSettings = + ManagementInterface.DaitaSettings.newBuilder() + .setEnabled(enabled) + .setDirectOnly(directOnly) + .build() diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index fe0222596b9a..624a2fa3c9d8 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -26,6 +26,7 @@ import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions import net.mullvad.mullvadvpn.lib.model.CustomList import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.CustomListName +import net.mullvad.mullvadvpn.lib.model.DaitaSettings import net.mullvad.mullvadvpn.lib.model.DefaultDnsOptions import net.mullvad.mullvadvpn.lib.model.Device import net.mullvad.mullvadvpn.lib.model.DeviceId @@ -397,9 +398,12 @@ internal fun ManagementInterface.TunnelOptions.WireguardOptions.toDomain(): Wire WireguardTunnelOptions( mtu = if (hasMtu()) Mtu(mtu) else null, quantumResistant = quantumResistant.toDomain(), - daita = daita.enabled, + daitaSettings = daita.toDomain(), ) +internal fun ManagementInterface.DaitaSettings.toDomain(): DaitaSettings = + DaitaSettings(enabled = enabled, directOnly = directOnly) + internal fun ManagementInterface.QuantumResistantState.toDomain(): QuantumResistantState = when (state) { ManagementInterface.QuantumResistantState.State.AUTO -> QuantumResistantState.Auto diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DaitaSettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DaitaSettings.kt new file mode 100644 index 000000000000..791970cf708c --- /dev/null +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DaitaSettings.kt @@ -0,0 +1,8 @@ +package net.mullvad.mullvadvpn.lib.model + +import arrow.optics.optics + +@optics +data class DaitaSettings(val enabled: Boolean, val directOnly: Boolean) { + companion object +} diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt index b3f1a2e8a071..99e8a2b8dc6e 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt @@ -14,7 +14,5 @@ data class Settings( val splitTunnelSettings: SplitTunnelSettings, val apiAccessMethodSettings: List, ) { - fun isDaitaEnabled() = tunnelOptions.wireguard.daita - companion object } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt index 70b1599c5557..f6a489df1274 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt @@ -1,7 +1,12 @@ package net.mullvad.mullvadvpn.lib.model +import arrow.optics.optics + +@optics data class WireguardTunnelOptions( val mtu: Mtu?, val quantumResistant: QuantumResistantState, - val daita: Boolean, -) + val daitaSettings: DaitaSettings, +) { + companion object +} diff --git a/android/lib/resource/src/main/res/values-da/strings.xml b/android/lib/resource/src/main/res/values-da/strings.xml index 88c135c30464..269b58c1e92f 100644 --- a/android/lib/resource/src/main/res/values-da/strings.xml +++ b/android/lib/resource/src/main/res/values-da/strings.xml @@ -94,7 +94,7 @@ Indstil port Gyldige områder: %1$s Kunne ikke fortolke værten for den tilpassede tunnel. Prøv at ændre dine indstillinger. - %1$s (%2$s) skjuler mønstre i din krypterede VPN-trafik. Hvis nogen overvåger din forbindelse, gør dette det betydeligt sværere for dem at identificere, hvilke websteder du besøger. Mønstrene skjules ved omhyggeligt at tilføje netværksstøj og gøre alle netværkspakker lige store. + Ved at bruge sofistikeret AI er det muligt at analysere trafikken af datapakker, der går ind og ud af din enhed (selv hvis trafikken er krypteret). Denne funktion er ikke tilgængelig på alle servere. Du skal muligvis ændre placering efter aktivering. Bemærk: Da dette øger din samlede netværkstrafik, skal du være forsigtig, hvis du har et abonnement med begrænset datamængde. Det kan også påvirke din netværkshastighed og dit batteriforbrug negativt. Slet @@ -110,6 +110,7 @@ Dette er det navn, der er tildelt enheden. Hver enhed, der er logget på en Mullvad-konto, får et unikt navn, der hjælper dig med at identificere den, når du administrerer dine enheder i appen eller på webstedet. Du kan have op til 5 enheder logget ind på én Mullvad-konto. Hvis du logger ud, fjernes enheden og enhedsnavnet. Når du logger på igen, får enheden et nyt navn. + Kun direkte Kassér Vil du kassere ændringer? Afbryd forbindelse diff --git a/android/lib/resource/src/main/res/values-de/strings.xml b/android/lib/resource/src/main/res/values-de/strings.xml index fdaceb289916..f0d8b50f5d29 100644 --- a/android/lib/resource/src/main/res/values-de/strings.xml +++ b/android/lib/resource/src/main/res/values-de/strings.xml @@ -94,7 +94,7 @@ Port festlegen Gültige Bereiche: %1$s Der Host des benutzerdefinierten Tunnels konnte nicht aufgelöst werden. Versuchen Sie, Ihre Einstellungen zu ändern. - %1$s (%2$s) verbirgt Muster in Ihrem verschlüsselten VPN-Traffic. Wenn jemand Ihre Verbindung überwacht, ist es für ihn wesentlich schwieriger zu erkennen, welche Websites Sie besuchen. Dazu fügt es vorsichtig Netzwerkrauschen hinzu und sorgt dafür, dass alle Netzwerkpakete die gleiche Größe haben. + Durch den Einsatz hochentwickelter KI ist es möglich, den Traffic von Datenpaketen zu analysieren, die auf Ihrem Gerät ein- und ausgehen (selbst wenn der Verkehr verschlüsselt ist). Diese Funktion ist nicht auf allen Servern verfügbar. Möglicherweise müssen Sie nach der Aktivierung den Standort wechseln. Achtung! Da dies Ihren gesamten Netzwerk-Traffic erhöht, sollten Sie vorsichtig sein, wenn Sie einen begrenzten Datentarif haben. Es kann sich auch negativ auf Ihre Netzwerkgeschwindigkeit und den Akkuverbrauch auswirken. Löschen @@ -110,6 +110,7 @@ Dies ist der dem Gerät zugewiesene Name. Jedes Gerät, das in einem Mullvad-Konto angemeldet ist, erhält einen eindeutigen Namen, mit dem Sie es identifizieren können, wenn Sie Ihre Geräte in der App oder auf der Website verwalten. Es sind pro Mullvad-Konto bis zu 5 angemeldete Geräte möglich. Wenn Sie sich abmelden, werden das Gerät und der Gerätename entfernt. Wenn Sie sich wieder anmelden, erhält das Gerät einen neuen Namen. + Nur direkt Verwerfen Änderungen verwerfen? Verbindung trennen diff --git a/android/lib/resource/src/main/res/values-es/strings.xml b/android/lib/resource/src/main/res/values-es/strings.xml index fb5981905b5c..4889b52eea8c 100644 --- a/android/lib/resource/src/main/res/values-es/strings.xml +++ b/android/lib/resource/src/main/res/values-es/strings.xml @@ -94,7 +94,7 @@ Establecer puerto Intervalos válidos: %1$s No se puede resolver el host del túnel personalizado. Pruebe a cambiar la configuración. - %1$s (%2$s) oculta los patrones en su tráfico VPN cifrado. Si alguien supervisa su conexión, esto le dificulta notablemente la tarea de identificar qué sitios web está visitando. Lo realiza añadiendo con cuidado ruido de red y haciendo que todos los paquetes de red tengan el mismo tamaño. + Al utilizar IA sofisticada, se puede analizar el tráfico de paquetes de datos que entran y salen de su dispositivo (incluso si el tráfico está cifrado). Esta característica no está disponible en todos los servidores. Podría tener que cambiar de ubicación tras habilitarla. Atención: Como esto aumenta el tráfico total de su red, tenga cuidado si tiene un plan de datos limitado. También puede afectar negativamente a la velocidad de su red y al uso de la batería. Eliminar @@ -110,6 +110,7 @@ Este es el nombre asignado al dispositivo. Cada dispositivo conectado a una cuenta de Mullvad recibe un nombre único que ayuda a identificarlo cuando gestiona sus dispositivos en la aplicación o en el sitio web. Puede tener hasta 5 dispositivos conectados a una cuenta de Mullvad. Si cierra sesión, se quita el dispositivo y el nombre del dispositivo. Cuando vuelve a iniciar sesión, el dispositivo recibirá un nombre nuevo. + Conexión directa Descartar ¿Descartar los cambios? Desconectar diff --git a/android/lib/resource/src/main/res/values-fi/strings.xml b/android/lib/resource/src/main/res/values-fi/strings.xml index 38e2a76a8d5d..d5059f175f19 100644 --- a/android/lib/resource/src/main/res/values-fi/strings.xml +++ b/android/lib/resource/src/main/res/values-fi/strings.xml @@ -94,7 +94,7 @@ Määritä portti Kelvolliset portit: %1$s Muokatun tunnelin isännän selvittäminen ei onnistu. Kokeile muuttaa asetuksiasi. - %1$s (%2$s) piilottaa kuviot salatusta VPN-liikenteestäsi. Jos joku tarkkailee yhteyttäsi, hänen on huomattavasti vaikeampi tunnistaa, millä verkkosivustoilla vierailet. Se tekee tämän lisäämällä varovasti verkkokohinaa ja tekemällä kaikista verkkopaketeista samankokoisia. + Käyttämällä edistynyttä tekoälyä on mahdollista analysoida laitteestasi lähtevä ja siihen tuleva datapakettiliikenne (vaikka liikenne olisi salattu). Ominaisuus ei ole käytettävissä kaikilla palvelimilla, joten saatat joutua vaihtamaan sijaintia otettuasi sen käyttöön. Huomioithan, että ominaisuuden käyttöönotto kasvattaa verkkoliikennettäsi kokonaisuudessaan ja voi myös vaikuttaa negatiivisesti verkon nopeuteen ja akun kestoon. Ominaisuuden käyttöönottoa kannattaa harkita tarkkaan, mikäli datapakettisi tiedonsiirtomäärä on rajoitettu. Poista @@ -110,6 +110,7 @@ Tämä on laitteelle annettu nimi. Jokainen Mullvad-tilille kirjautunut laite saa yksilöllisen nimen, jonka avulla sen voi tunnistaa laitteiden hallinnassa sovelluksessa tai verkkosivustolla. Yhdelle Mullvad-tilille voi olla kirjautuneena enintään viisi laitetta. Jos kirjaudut ulos, laite ja laitteen nimi poistetaan. Kun kirjaudut sisään uudelleen, laitteelle annetaan uusi nimi. + Vain suora Hylkää Hylätäänkö muokkaukset? Katkaise yhteys diff --git a/android/lib/resource/src/main/res/values-fr/strings.xml b/android/lib/resource/src/main/res/values-fr/strings.xml index cd70ac2701bd..588352f5e881 100644 --- a/android/lib/resource/src/main/res/values-fr/strings.xml +++ b/android/lib/resource/src/main/res/values-fr/strings.xml @@ -94,7 +94,7 @@ Définir le port Plages valides : %1$s Échec de la résolution de l\'hôte du tunnel personnalisé. Essayez de modifier vos paramètres. - %1$s (%2$s) dissimule des motifs dans votre trafic VPN chiffré. Si quelqu\'un surveille votre connexion, il lui sera beaucoup plus difficile d\'identifier les sites web que vous visitez. Pour ce faire, il ajoute soigneusement du bruit réseau et fait en sorte que tous les paquets de réseau aient la même taille. + Utiliser une IA sophistiquée peut permettre d\'analyser le trafic des paquets de données entrant et sortant de votre appareil (même si le trafic est chiffré). Cette fonctionnalité n\'est pas disponible sur tous les serveurs. Vous devrez peut-être changer d\'emplacement après l\'avoir activée. Attention : étant donné que cette opération augmente le trafic total de votre réseau, faites attention si vous disposez d\'un forfait à données limitées. Cela peut également avoir un impact négatif sur la vitesse de votre réseau et l\'utilisation de la batterie. Supprimer @@ -110,6 +110,7 @@ Il s\'agit du nom attribué à l\'appareil. Chaque appareil connecté à un compte Mullvad reçoit un nom unique qui vous aide à l\'identifier lorsque vous gérez vos appareils dans l\'application ou sur le site Web. Vous pouvez connecter jusqu\'à 5 appareils au même compte Mullvad. Si vous vous déconnectez, l\'appareil et son nom sont supprimés. Lorsque vous vous reconnectez, l\'appareil reçoit un nouveau nom. + Directe uniquement Annuler Annuler les modifications ? Déconnexion diff --git a/android/lib/resource/src/main/res/values-it/strings.xml b/android/lib/resource/src/main/res/values-it/strings.xml index 70a08dbf7c1b..7e0839eefa7d 100644 --- a/android/lib/resource/src/main/res/values-it/strings.xml +++ b/android/lib/resource/src/main/res/values-it/strings.xml @@ -94,7 +94,7 @@ Imposta porta Intervalli validi: %1$s Impossibile risolvere l\'host del tunnel personalizzato. Prova a modificare le impostazioni. - %1$s (%2$s) nasconde i percorsi in un traffico VPN crittografato. Se qualcuno sta monitorando la tua connessione, sarà molto più difficile per lui identificare quali siti web stai visitando. Ciò avviene aggiungendo con attenzione rumore di rete e rendendo tutti i pacchetti di rete della stessa dimensione. + Utilizzando un\'IA sofisticata è possibile analizzare il traffico dei pacchetti di dati in entrata e in uscita dal dispositivo (anche se il traffico è crittografato). Questa funzionalità non è disponibile su tutti i server. Potrebbe essere necessario modificare la posizione dopo l\'abilitazione. Attenzione: poiché questa operazione aumenta il traffico di rete totale, usala con cautela se disponi di un piano dati limitato. Può anche avere un impatto negativo sulla velocità della rete e sul consumo della batteria. Elimina @@ -110,6 +110,7 @@ Questo è il nome assegnato al dispositivo. Ogni dispositivo connesso a un account Mullvad riceve un nome univoco che ti aiuta a identificarlo quando gestisci i tuoi dispositivi nell\'app o sul sito web. Puoi avere fino a 5 dispositivi registrati su un account Mullvad. Se ti disconnetti, il dispositivo e il relativo nome verranno rimossi. Quando accedi nuovamente, il dispositivo assumerà un nuovo nome. + Solo diretta Ignora modifiche Ignorare le modifiche? Disconnetti diff --git a/android/lib/resource/src/main/res/values-ja/strings.xml b/android/lib/resource/src/main/res/values-ja/strings.xml index 2fb24ebe8e87..e64113b69c70 100644 --- a/android/lib/resource/src/main/res/values-ja/strings.xml +++ b/android/lib/resource/src/main/res/values-ja/strings.xml @@ -94,7 +94,7 @@ ポートを設定 有効な範囲: %1$s カスタムトンネルのホストを解決できません。設定を変更してみてください。 - %1$s (%2$s) は暗号化されたVPNトラフィックのパターンを隠します。これにより、接続が監視されている場合でもアクセス先のウェブサイトを特定するのを極めて困難にします。ネットワークノイズを入念に追加し、すべてのネットワークパケットを同じサイズにすることによってこれを実現しています。 + 高度なAIを使用することで、(トラフィックが暗号化されている場合でも) デバイスが送受信するデータパケットのトラフィックを分析することは可能です。 この機能はすべてのサーバーでご利用いただけるわけではありません。有効にした後に場所の変更が必要となる可能性があります。 注意: これにより、ネットワークトラフィックの総数が増加します。上限のあるデータプランをご利用の場合にはご注意ください。また、ネットワークの速度とバッテリー使用量にも悪影響を及ぼす可能性があります。 削除 @@ -110,6 +110,7 @@ これはデバイスに割り当てられる名前です。Mullvadアカウントにログインするデバイスごとに一意の名前が付けられるため、アプリまたはウェブサイトでデバイスを管理する際にデバイスを区別しやすくなります。 1つのMullvadアカウントで最大5台のデバイスにログインできます。 ログアウトすると、デバイスとデバイス名が削除されます。もう一度ログインすると、デバイスに新しい名前が付けられます。 + ダイレクトのみ 破棄 変更内容を破棄しますか? 接続解除 diff --git a/android/lib/resource/src/main/res/values-ko/strings.xml b/android/lib/resource/src/main/res/values-ko/strings.xml index f8727aabbc34..881a9421a3a7 100644 --- a/android/lib/resource/src/main/res/values-ko/strings.xml +++ b/android/lib/resource/src/main/res/values-ko/strings.xml @@ -94,7 +94,7 @@ 포트 설정 유효한 범위: %1$s 사용자 지정 터널의 호스트를 확인할 수 없습니다. 설정을 변경해 보세요. - %1$s(%2$s)는 암호화된 VPN 트래픽의 패턴을 숨깁니다. 누군가 사용자의 연결을 모니터링하고 있다면 이 기능은 사용자가 어느 웹사이트를 방문하고 있는지, 누구와 대화하고 있는지 아주 식별하기 어렵게 만듭니다. 이는 정교하게 네트워크 노이즈를 추가하고 모든 네트워크 패킷을 동일한 사이즈로 만드는 방식으로 달성됩니다. + 정교한 AI를 사용하면, 트래픽이 암호화된 경우라도 장치를 드나드는 데이터 패킷의 트래픽을 분석하는 것이 가능합니다. 이 기능은 일부 서버에서 이용 가능하지 않습니다. 활성화 후 위치를 변경해야 할 수도 있습니다. 주의: 이러한 방식은 총 네트워크 트래픽을 증가시키므로, 제한된 데이터 플랜 사용 시에는 유의하시기 바랍니다. 또한 네트워크 속도와 배터리 사용에 부정적인 영향을 미칠 수 있습니다. 삭제 @@ -110,6 +110,7 @@ 이것은 장치에 할당된 이름입니다. Mullvad 계정에 로그인된 각 장치에는 앱이나 웹사이트에서 장치를 관리할 때 장치를 보다 쉽게 식별할 수 있는 고유한 이름이 부여됩니다. 최대 5개의 장치에서 하나의 Mullvad 계정에 로그인할 수 있습니다. 로그아웃하면 해당 장치와 장치 이름이 제거됩니다. 다시 로그인하면 장치에 새 이름이 부여됩니다. + 직접 전용 취소 변경 사항을 취소하시겠습니까? 연결 끊기 diff --git a/android/lib/resource/src/main/res/values-my/strings.xml b/android/lib/resource/src/main/res/values-my/strings.xml index 5c9cd497f23a..fe25eae34773 100644 --- a/android/lib/resource/src/main/res/values-my/strings.xml +++ b/android/lib/resource/src/main/res/values-my/strings.xml @@ -94,7 +94,7 @@ ပေါ့တ် သတ်မှတ်ရန် အကျုံးဝင်သည့် အပိုင်းအခြား- %1$s စိတ်ကြိုက်ပြုလုပ်ထားသည့် Tunnel ၏ Host ကို ဖြေရှင်း၍ မရနိုင်ပါ။ သင့်ဆက်တင်ကို ပြောင်းကြည့်ပါ။ - %1$s (%2$s) က သင်၏ ကုဒ်ပြောင်းဝှက်ထားသော VPN ဝင်ရောက်ကြည့်ရှုမှုထဲရှိ ပက်တန်များကို ဝှက်ပေးသည်။ သင့်ချိတ်ဆက်မှုကို တစ်စုံတစ်ယောက်က စောင့်ကြည့်နေပါက မည်သည့်ဝက်ဘ်ဆိုက်များထဲ သင်ဝင်ရောက်နေသည်ကို ၎င်းတို့ ခွဲခြားဖော်ထုတ်ဖို့ရာ ပို၍ သိသိသာသာ ခက်ခဲသွားစေပါသည်။ သည် ကွန်ရက် အနှောင့်အယှက်လျှပ်လိုင်းကို ဂရုတစိုက် ထည့်ပြီး ကွန်ရက် ပက်ကက်အားလုံးကို အရွယ်အစားတူညီအောင် ပြုလုပ်ခြင်းအားဖြင့် ပိုမိုခက်ခဲအောင် ဆောင်ရွက်ခြင်းဖြစ်သည်။ + ခေတ်မီဆန်းပြားသော AI ကိုအသုံးပြုခြင်းဖြင့် (အသွားအလာကို ကုဒ်ဝှက်ထားသည့်တိုင်) သင့်စက်၏အဝင်အထွက်ဒေတာပက်ကတ်များ၏ အသွားအလာကို ပိုင်းခြားစိတ်ဖြာနိုင်သည်။ ဤလုပ်ဆောင်ချက်ကို ဆာဗာအားလုံးတွင် မရရှိနိုင်ပါ။ ဖွင့်ပြီးနောက် တည်နေရာ ပြောင်းလဲရန် လိုအပ်နိုင်သည်။ သတိပြုရန်- ၎င်းသည် သင်၏ စုစုပေါင်းကွန်ရက်သုံးစွဲမှုကို တိုးစေသောကြောင့် သင့်ဒေတာပလန်မှာ အကန့်အသတ်ဖြင့်သာ ရှိပါက သတိထားပါ။ ၎င်းသည် သင်၏ကွန်ရက်အမြန်နှုန်းနှင့် ဘက်ထရီအသုံးပြုမှုကိုလည်း ထိခိုက်စေနိုင်သည်။ ဖျက်ရန် @@ -110,6 +110,7 @@ ဤအမည်မှာ စက်အတွက် သတ်မှတ်ထားသော အမည် ဖြစ်ပါသည်။ Mullvad အကောင့်တစ်ခုတွင် ဝင်ရောက်ထားသည့် စက်တစ်ခုစီသည် တစ်မူထူးခြားသည့် အမည်တစ်ခု ရရှိမည်ဖြစ်ပြီး အက်ပ် သို့မဟုတ် ဝက်ဘ်ဆိုက်ပေါ်တွင် သင့်စက်များကို စီမံသည့်အခါ သင်အနေဖြင့် ကွဲကွဲပြားပြားသိရှိအောင် ကူညီပေးပါသည်။ Mullvad အကောင့်တစ်ခုတွင် စက် 5 ခုအထိ ဝင်ရောက်ထားနိုင်ပါသည်။ ထွက်လိုက်ပါက စက်နှင့် စက်အမည်ကို ဖယ်ရှားပါသည်။ နောက်တစ်ကြိမ် ပြန်ဝင်ရောက်သည့်အခါ စက်သည် အမည်သစ်တစ်ခု ရရှိပါမည်။ + တိုက်ရိုက်သာ ပယ်ဖျက်မည် အပြောင်းအလဲများကို ပယ်ဖျက်မည်လား။ ချိတ်ဆက်မှုဖြုတ်ရန် diff --git a/android/lib/resource/src/main/res/values-nb/strings.xml b/android/lib/resource/src/main/res/values-nb/strings.xml index bef18f462924..912675a2fdf2 100644 --- a/android/lib/resource/src/main/res/values-nb/strings.xml +++ b/android/lib/resource/src/main/res/values-nb/strings.xml @@ -94,7 +94,7 @@ Konfigurer port Gyldige verdiområder: %1$s Kunne ikke løse vert for egendefinert tunnel. Forsøk å endre innstillingene dine. - %1$s (%2$s) skjuler mønstre i den krypterte VPN-trafikken. Hvis noen overvåker tilkoblingen din, gjør dette det betydelig vanskeligere for dem å identifisere hvilke nettsteder du besøker. Dette gjøres ved å legge til nettverksstøy på en forsiktig måte og gjøre alle nettverkspakker like store. + Ved hjelp av avansert kunstig intelligens er det mulig å analysere trafikken av datapakker som går inn og ut av enheten din (selv om trafikken er kryptert). Denne funksjonen er ikke tilgjengelig på alle servere. Det kan hende du må endre plassering etter aktivering. Merk: Siden dette øker den totale nettverkstrafikken, bør du være forsiktig hvis du har et abonnement med begrenset data. Det kan også negativt påvirke nettverkshastigheten og batteribruken. Slett @@ -110,6 +110,7 @@ Dette er navnet som er tildelt enheten. Enhver enhet som er logget inn på en Mullvad-konto, får et unikt navn som gjør det enklere for deg å identifisere den når du administrerer enheten i appen eller på nettsiden. Du kan ha opptil fem enheter logget inn på samme Mullvad-konto. Hvis du logger ut, vil enheten og enhetsnavnet bli fjernet. Når du logger inn igjen, vil enheten få et nytt navn. + Kun direkte Forkast Forkaste endringer? Koble fra diff --git a/android/lib/resource/src/main/res/values-nl/strings.xml b/android/lib/resource/src/main/res/values-nl/strings.xml index e965388f4024..25f1ee14d698 100644 --- a/android/lib/resource/src/main/res/values-nl/strings.xml +++ b/android/lib/resource/src/main/res/values-nl/strings.xml @@ -94,7 +94,7 @@ Poort instellen Geldige bereiken: %1$s Kan host van aangepaste tunnel niet omzetten. Probeer uw instellingen te wijzigen. - %1$s (%2$s) verbergt patronen in het versleutelde VPN-verkeer. Als iemand de verbinding in de gaten houdt, maakt dit het aanzienlijk moeilijker voor diegene om te zien welke websites u bezoekt. Dit wordt gedaan door zorgvuldig netwerkruis toe te voegen en alle netwerkpakketten even groot te maken. + Door gebruik te maken van geavanceerde AI is het mogelijk om het verkeer van datapakketten te analyseren die uw apparaat in- en uitgaan (zelfs als het verkeer versleuteld is). Deze functie is niet op alle servers beschikbaar. Mogelijk moet u na het inschakelen van locatie veranderen. Let op: omdat dit het totale netwerkverkeer verhoogt, moet u voorzichtig zijn als u een beperkt data-abonnement hebt. Ook kan het uw netwerksnelheid en batterijgebruik beïnvloeden. Verwijderen @@ -110,6 +110,7 @@ Dit is de naam die aan het apparaat is toegewezen. Elk apparaat dat is aangemeld op een Mullvad-account, krijgt een unieke naam waarmee u het kunt identificeren wanneer u uw apparaten beheert in de app of op de website. U kunt maximaal 5 apparaten aangemeld hebben op één Mullvad-account. Als u zich afmeldt, worden het apparaat en de apparaatnaam verwijderd. Wanneer u zich weer aanmeldt, krijgt het apparaat een nieuwe naam. + Alleen rechtstreeks Verwerpen Wijzigingen verwerpen? Verbinding verbreken diff --git a/android/lib/resource/src/main/res/values-pl/strings.xml b/android/lib/resource/src/main/res/values-pl/strings.xml index 9fd1e9de55aa..fdde512ddee6 100644 --- a/android/lib/resource/src/main/res/values-pl/strings.xml +++ b/android/lib/resource/src/main/res/values-pl/strings.xml @@ -94,7 +94,7 @@ Ustaw port Prawidłowe zakresy: %1$s Nie można rozpoznać hosta tunelu niestandardowego. Spróbuj zmienić ustawienia. - %1$s (%2$s) ukrywa wzorce w zaszyfrowanym ruchu VPN. Oznacza to, że jeśli ktoś monitoruje połączenie, identyfikacja odwiedzanych witryn jest znacznie utrudniona. Dzieje się tak poprzez ostrożne dodawanie szumów sieciowych i ustawianie tego samego rozmiaru wszystkich pakietów sieciowych. + Dzięki użyciu zaawansowanej sztucznej inteligencji możliwe jest analizowanie ruchu pakietów danych przychodzących i wychodzących z Twojego urządzenia (nawet jeśli ruch jest szyfrowany). Funkcja niedostępna na wszystkich serwerach. Po włączeniu może być konieczna zmiana lokalizacji. Uwaga: ponieważ zwiększa to łączny ruch sieciowy, zachowaj ostrożność, jeśli masz ograniczony pakiet danych. Może to również negatywnie wpłynąć na szybkość sieci i poziom naładowania. Usuń @@ -110,6 +110,7 @@ Jest to nazwa przypisana do urządzenia. Każde urządzenie zalogowane na koncie Mullvad otrzymuje unikalną nazwę, która pozwala zidentyfikować je podczas zarządzania urządzeniami w aplikacji lub za pośrednictwem witryny internetowej. Na jednym koncie Mullvad może być zalogowanych maksymalnie 5 urządzeń. Przy wylogowaniu urządzenie i jego nazwa zostają usunięte. Po ponownym zalogowaniu urządzenie otrzymuje nową nazwę. + Tylko Direct Odrzuć Odrzucić zmiany? Rozłącz diff --git a/android/lib/resource/src/main/res/values-pt/strings.xml b/android/lib/resource/src/main/res/values-pt/strings.xml index 34175af58247..dd7e3538286b 100644 --- a/android/lib/resource/src/main/res/values-pt/strings.xml +++ b/android/lib/resource/src/main/res/values-pt/strings.xml @@ -94,7 +94,7 @@ Definir porta Intervalos válidos: %1$s Não foi possível resolver o anfitrião do túnel personalizado. Experimente alterar as suas definições. - %1$s (%2$s) oculta padrões no seu tráfego VPN encriptado. Se alguém estiver a monitorizar a sua ligação, isto dificulta significativamente a identificação dos sites que visita. Para tal, adiciona cuidadosamente ruído de rede e torna todos os pacotes de rede do mesmo tamanho. + Utilizando uma IA sofisticada, é possível analisar o tráfego de pacotes de dados que entram e saem do seu dispositivo (mesmo que o tráfego esteja encriptado). Esta funcionalidade não está disponível em todos os servidores. Poderá ser necessário alterar a localização após a ativação. Atenção: visto que isto aumenta o tráfego total da sua rede, tenha cuidado se tiver um plano de dados limitado. Também pode afetar negativamente a velocidade da rede e a utilização da bateria. Eliminar @@ -110,6 +110,7 @@ Este é o nome atribuído ao dispositivo. Cada dispositivo com sessão iniciada numa conta Mullvad recebe um nome único que lhe ajuda a identificá-lo quando gere os seus dispositivos na app ou no site. Pode ter até 5 dispositivos com sessão iniciada numa só conta Mullvad. Se terminar a sessão, o dispositivo e o nome do dispositivo são removidos. Quando voltar a iniciar sessão, o dispositivo recebe um novo nome. + Apenas direta Descartar Descartar alterações? Desligar diff --git a/android/lib/resource/src/main/res/values-ru/strings.xml b/android/lib/resource/src/main/res/values-ru/strings.xml index 4d7e03a9684b..5bb2803d4eb7 100644 --- a/android/lib/resource/src/main/res/values-ru/strings.xml +++ b/android/lib/resource/src/main/res/values-ru/strings.xml @@ -94,7 +94,7 @@ Установить порт Допустимые диапазоны: %1$s Не удалось преобразовать имя узла пользовательского туннеля. Попробуйте изменить настройки. - %1$s (%2$s) скрывает характерные признаки в зашифрованном VPN-трафике. Если кто-то отслеживает ваше подключение, ему будет значительно сложнее определить, какие веб-сайты вы посещаете. Для этого в трафик добавляется сетевой шум, а все пакеты приводятся к одному размеру. + С помощью сложных ИИ-алгоритмов можно анализировать входящий и исходящий трафик пакетов данных вашего устройства (даже если трафик зашифрован). Эта функция доступна не на всех серверах. Возможно, после ее включения придется сменить местоположение. Внимание! При использовании этой функции общий сетевой трафик увеличивается. Имейте это в виду, если у вас тарифный план с ограничением по трафику. Также может снизиться скорость сети и повыситься расход заряда аккумулятора. Удалить @@ -110,6 +110,7 @@ Это имя, присвоенное устройству. Каждое устройство, подключенное к учетной записи Mullvad, получает уникальное имя, которое помогает вам идентифицировать его при управлении устройствами в приложении или на сайте. К одной учетной записи Mullvad можно подключить до 5 устройств. Если вы выйдете, устройство и его имя удалятся. При повторном входе устройство получит новое имя. + Только напрямую Сбросить Сбросить изменения? Отключить diff --git a/android/lib/resource/src/main/res/values-sv/strings.xml b/android/lib/resource/src/main/res/values-sv/strings.xml index a1f0557de748..646b09b73c1d 100644 --- a/android/lib/resource/src/main/res/values-sv/strings.xml +++ b/android/lib/resource/src/main/res/values-sv/strings.xml @@ -94,7 +94,7 @@ Ställ in port Giltiga intervall: %1$s Det går inte att lösa värd för anpassad tunnel. Försök att ändra inställningarna. - %1$s (%2$s) döljer mönster i din krypterade VPN-trafik. Om någon övervakar din anslutning blir det mycket svårare för hen att identifiera vilka webbplatser du besöker. Den gör det genom att noggrant lägga till nätverksbrus och se till så att alla nätverkspaket har samma storlek. + Med sofistikerad AI är det möjligt att analysera trafiken för datapaket som går in och ut från din enhet (även om trafiken är krypterad). Det här funktionen är inte tillgänglig på alla servrar. Du kanske måste ändra plats efter aktivering. Obs! Detta ökar nätverkstrafiken så var försiktig om du har ett abonnemang med begränsad mängd data. Det kan även ha en negativ påverkan på din nätverkshastighet och batterianvändning. Ta bort @@ -110,6 +110,7 @@ Det här är namnet som tilldelas enheten. Varje enhet som är inloggad på ett Mullvad-konto får ett unikt namn som hjälper dig att identifiera den när du hanterar dina enheter i appen eller på webbplatsen. Upp till fem enheter kan vara inloggade på ett Mullvad-konto. Om du loggar ut tas enheten och enhetsnamnet bort. När du loggar in igen får enheten ett nytt namn. + Endast direkt Ignorera Ignorera ändringarna? Koppla från diff --git a/android/lib/resource/src/main/res/values-th/strings.xml b/android/lib/resource/src/main/res/values-th/strings.xml index 9664d0a91788..713ad4df0256 100644 --- a/android/lib/resource/src/main/res/values-th/strings.xml +++ b/android/lib/resource/src/main/res/values-th/strings.xml @@ -94,7 +94,7 @@ ตั้งค่าพอร์ต ช่วงที่ใช้ได้: %1$s ไม่พบโฮสต์ของช่องทางแบบกำหนดเอง กรุณาลองเปลี่ยนการตั้งค่าของคุณ - %1$s (%2$s) ซ่อนรูปแบบในการรับส่งข้อมูล VPN ที่เข้ารหัสของคุณ หากมีใครกำลังเฝ้าดูการเชื่อมต่อของคุณอยู่ สิ่งนี้จะทำให้การระบุเว็บไซต์ที่คุณกำลังเยี่ยมชมยากขึ้นอย่างมาก ซึ่งทำได้โดยการเพิ่มสัญญาณรบกวนเครือข่ายอย่างระมัดระวัง และทำให้แพ็กเก็ตเครือข่ายทั้งหมดมีขนาดเท่ากันหมด + โดยการใช้ AI ที่ซับซ้อน จึงเป็นไปได้ที่จะวิเคราะห์ปริมาณการรับส่งข้อมูล ที่เข้าออกอุปกรณ์ของคุณได้ (แม้ว่าการรับส่งข้อมูลจะเข้ารหัสไว้ก็ตาม) คุณลักษณะนี้ไม่พร้อมใช้งานบนทุกเซิร์ฟเวอร์ คุณอาจต้องต้องเปลี่ยนตำแหน่งที่ตั้ง หลังจากเปิดใช้งาน โปรดทราบ: โปรดใช้ความระมัดระวัง หากคุณมีแผนข้อมูลที่มีปริมาณจำกัด เนื่องจากการดำเนินการนี้ จะเพิ่มการรับส่งข้อมูลโดยรวมของคุณขึ้น การดำเนินการนี้ อาจส่งผลเชิงลบต่อความเร็วเครือข่าย และการใช้งานแบตเตอรี่ของคุณได้ ลบ @@ -110,6 +110,7 @@ นี่เป็นชื่อที่มอบหมายให้กับอุปกรณ์ อุปกรณ์แต่ละเครื่องที่ลงชื่อเข้าใช้บนบัญชี Mullvad จะได้รับชื่อเฉพาะ ที่จะช่วยคุณระบุอุปกรณ์ ในขณะที่คุณจัดการอุปกรณ์ของคุณในแอปหรือบนเว็บไซต์ คุณสามารถลงชื่อเข้าใช้อุปกรณ์ได้สูงสุด 5 เครื่อง กับบัญชี Mullvad หนึ่งบัญชี หากคุณออกจากระบบ อุปกรณ์และชื่ออุปกรณ์จะถูกลบออก และเมื่อคุณลงชื่อเข้าใช้อีกครั้ง อุปกรณ์จะได้รับชื่อใหม่ + โดยตรงเท่านั้น ละทิ้ง ละทิ้งการเปลี่ยนแปลงหรือไม่ ตัดการเชื่อมต่อ diff --git a/android/lib/resource/src/main/res/values-tr/strings.xml b/android/lib/resource/src/main/res/values-tr/strings.xml index cdb01b9d2f13..ce1a257b6d28 100644 --- a/android/lib/resource/src/main/res/values-tr/strings.xml +++ b/android/lib/resource/src/main/res/values-tr/strings.xml @@ -94,7 +94,7 @@ Portu ayarla Geçerli aralıklar: %1$s Özel tünel ana bilgisayarı çözülemedi. Ayarlarınızı değiştirmeyi deneyin. - %1$s (%2$s), şifrelenmiş VPN trafiğinizdeki kalıpları gizler. Bu sayede, başka biri bağlantınızı izliyorsa ziyaret ettiğiniz web sitelerini tespit etmesi çok daha zor olacaktır. Özellik, dikkatli bir şekilde ağ paraziti ekleyerek ve tüm ağ paketlerini aynı boyuta getirerek sizi izlenmekten korur. + Gelişmiş yapay zeka kullanarak cihazınıza gelen ve giden veri paketlerinin trafiğini (trafik şifrelenmiş olsa bile) analiz etmek mümkündür. Bu özellik tüm sunucularda mevcut değildir. Etkinleştirdikten sonra konumu değiştirmeniz gerekebilir. Dikkat: Bu özellik, toplam ağ trafiğinizi artıracağından sınırlı bir veri planınız varsa dikkatli olun. Ağ hızınız ve pil kullanımınız da bu durumdan olumsuz etkilenebilir. Sil @@ -110,6 +110,7 @@ Bu, cihaza atanan addır. Mullvad hesabında oturum açan her cihaza, uygulamadaki veya web sitesindeki cihazlarınızı yönetirken tanımlamanıza yardımcı olacak benzersiz bir ad verilir. Bir Mullvad hesabı ile en fazla 5 cihazda oturum açabilirsiniz. Oturumu kapatırsanız cihaz ve cihaz adı kaldırılır. Tekrar oturum açtığınızda cihaza yeni bir ad verilir. + Yalnızca doğrudan İptal et Değişiklikler iptal edilsin mi? Bağlantıyı Kes diff --git a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml index 39306a64aef6..46a9ac30fa36 100644 --- a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml +++ b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml @@ -94,7 +94,7 @@ 设置端口 有效范围:%1$s 无法解析自定义隧道的主机。请尝试更改您的设置。 - %1$s (%2$s) 会隐藏您的加密 VPN 流量中的模式。如果有人在监视您的连接,这会大大提高他们识别您所访问的网站的难度。实现方法是精心添加网络噪声并将所有网络数据包处理为相同的大小。 + 通过使用复杂 AI,可以分析传入和传出您的设备的数据包流量(即使流量已加密)。 此功能并非在所有服务器上都可用。启用后,您可能需要更改位置。 注意:这样会增加您的网络总流量,因此,如果您的数据套餐流量有限,请谨慎使用。这种方式还会对网速和电池用量产生负面影响。 删除 @@ -110,6 +110,7 @@ 这是为设备分配的名称。每台登录 Mulvad 帐户的设备都会获得一个唯一名称,有助于您在应用或网站上管理设备时识别各个设备。 一个 Mulvad 帐户最多可以登录 5 台设备。 如果您退出登录,设备和设备名称将被移除。当您再次登录时,设备将获得新名称。 + 仅直连 舍弃 舍弃更改? 断开连接 diff --git a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml index 8be3c88b5931..0637ce1422c9 100644 --- a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml +++ b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml @@ -94,7 +94,7 @@ 設定連接埠 有效範圍:%1$s 無法解析自訂通道的主機。請嘗試變更您的設定。 - %1$s (%2$s) 會在您的加密 VPN 流量中隱藏模式。如果有人正在監控您的連線,這會大大增加他們的難度,讓他們難以辨識您正在瀏覽哪些網站。這個方法會仔細加入網路噪音,讓所有網路資料封包大小皆相同,以達到此目的。 + 可透過精密的 AI 分析進出裝置的資料封包流量 (即使流量已加密)。 此功能尚未在所有伺服器上開放。啟用後,您可能需要變更位置。 注意:這會增加您的網路總流量。如果您方案中的流量有限,請謹慎使用。這還會對您的網路速度和電池使用情況產生負面影響。 刪除 @@ -110,6 +110,7 @@ 這是系統指派給裝置的名稱。每台登入 Mulvad 帳戶的裝置都會獲得一個獨特名稱,有助於您在應用程式或網站上管理裝置時識別各台裝置。 一個 Mulvad 帳戶最多可以登入 5 台裝置。 如果您登出,則系統會移除裝置和裝置名稱。當您再次登入時,裝置則會獲得新名稱。 + 僅直接連線 捨棄 要捨棄變更嗎? 中斷連線 diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index 4625fb3b5f08..ce89373cba8b 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -356,7 +356,7 @@ Failed to set to current - Unknown reason %s was removed from \"%s\" \"%s\" was created - %s (%s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size. + By enabling \"Direct Only\" you will have to manually select a server that is DAITA-enabled. This can cause you to end up in a blocked state until you have selected a compatible server in the \"Select location\" view. Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage. Setting: %s Enable anyway @@ -407,4 +407,15 @@ Search results Filters: Type at least 2 characters to start searching. + DAITA (Defense against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic. + By using sophisticated AI it’s possible to analyze the traffic of data packets going in and out of your device (even if the traffic is encrypted). + If an observer monitors these data packets, DAITA makes it significantly harder for them to identify which websites you are visiting or with whom you are communicating. + DAITA does this by carefully adding network noise and making all network packets the same size. + Not all our servers are DAITA-enabled. Therefore, we use multihop automatically to enable DAITA with any server. + Attention: Be cautious if you have a limited data plan as this feature will increase your network traffic. + Direct only + Enable \"Direct only\" + Not all our servers are DAITA-enabled. In order to use the internet, you might have to select a new location after enabling. + The entry server for multihop is currently overridden by DAITA. To select an entry server, please first enable “Direct only” or disable “DAITA” in the settings. + Open DAITA settings diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot index ca8a81df025d..d51b0f787fa2 100644 --- a/desktop/packages/mullvad-vpn/locales/messages.pot +++ b/desktop/packages/mullvad-vpn/locales/messages.pot @@ -2209,9 +2209,6 @@ msgctxt "wireguard-settings-view" msgid "Which TCP port the UDP-over-TCP obfuscation protocol should connect to on the VPN server." msgstr "" -msgid "%s (%s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size." -msgstr "" - msgid "%s (Entry)" msgstr "" @@ -2302,6 +2299,9 @@ msgstr "" msgid "At least one method needs to be enabled" msgstr "" +msgid "Attention: Be cautious if you have a limited data plan as this feature will increase your network traffic." +msgstr "" + msgid "Attention: If \"Block connections without VPN\" is enabled, \"Local network sharing\" will not work." msgstr "" @@ -2329,6 +2329,9 @@ msgstr "" msgid "Blocking internet (device offline)" msgstr "" +msgid "By enabling \"Direct Only\" you will have to manually select a server that is DAITA-enabled. This can cause you to end up in a blocked state until you have selected a compatible server in the \"Select location\" view." +msgstr "" + msgid "Changelog" msgstr "" @@ -2377,6 +2380,12 @@ msgstr "" msgid "DAITA" msgstr "" +msgid "DAITA (Defense against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic." +msgstr "" + +msgid "DAITA does this by carefully adding network noise and making all network packets the same size." +msgstr "" + msgid "DNS settings might not go into effect immediately" msgstr "" @@ -2428,6 +2437,9 @@ msgstr "" msgid "Edit name" msgstr "" +msgid "Enable \"Direct only\"" +msgstr "" + msgid "Enable method" msgstr "" @@ -2467,6 +2479,9 @@ msgstr "" msgid "Google Play unavailable" msgstr "" +msgid "If an observer monitors these data packets, DAITA makes it significantly harder for them to identify which websites you are visiting or with whom you are communicating." +msgstr "" + msgid "If the split tunneling feature is used, then the app queries your system for a list of all installed applications. This list is only retrieved in the split tunneling view. The list of installed applications is never sent from the device." msgstr "" @@ -2524,9 +2539,18 @@ msgstr "" msgid "No result for \"%s\", please try a different search" msgstr "" +msgid "Not all our servers are DAITA-enabled. In order to use the internet, you might have to select a new location after enabling." +msgstr "" + +msgid "Not all our servers are DAITA-enabled. Therefore, we use multihop automatically to enable DAITA with any server." +msgstr "" + msgid "Not found" msgstr "" +msgid "Open DAITA settings" +msgstr "" + msgid "Overrides active" msgstr "" @@ -2629,6 +2653,9 @@ msgstr "" msgid "The \"Current\" method represent which method the app is using to reach the API." msgstr "" +msgid "The entry server for multihop is currently overridden by DAITA. To select an entry server, please first enable “Direct only” or disable “DAITA” in the settings." +msgstr "" + msgid "The local DNS server will not work unless you enable \"Local Network Sharing\" under VPN settings." msgstr ""