diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2024-12-02 11:05:23 +0100 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2024-12-02 11:05:23 +0100 |
| commit | 2e022babf06b47e100d0cf98a79751d0088d689e (patch) | |
| tree | c826f4e15a7c3b8fe611d8d279487f0212ae6820 | |
| parent | 64a5704fd0dc57cc73669251da43d4285fa42e92 (diff) | |
| parent | 124bf647bf8c949d15bbc72aa0ff4a2e87a67549 (diff) | |
| download | mullvadvpn-2e022babf06b47e100d0cf98a79751d0088d689e.tar.xz mullvadvpn-2e022babf06b47e100d0cf98a79751d0088d689e.zip | |
Merge branch 'implement-support-for-daita-with-multihop-droid-1412'
67 files changed, 1521 insertions, 213 deletions
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index cae6231483..7924049815 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/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt index e691909a40..464fa1181a 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt @@ -34,6 +34,7 @@ class SettingsScreenTest { isSupportedVersion = true, isPlayBuild = false, multihopEnabled = false, + isDaitaEnabled = false, ) ) } @@ -58,6 +59,7 @@ class SettingsScreenTest { isSupportedVersion = true, isPlayBuild = false, multihopEnabled = false, + isDaitaEnabled = false, ) ) } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreenTest.kt index a154344f26..cf3380e97a 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreenTest.kt @@ -64,7 +64,7 @@ class SelectLocationScreenTest { setContentWithTheme { SelectLocationScreen( state = - SelectLocationUiState( + SelectLocationUiState.Data( // searchTerm = "", filterChips = emptyList(), multihopEnabled = false, @@ -96,7 +96,7 @@ class SelectLocationScreenTest { setContentWithTheme { SelectLocationScreen( state = - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = emptyList(), multihopEnabled = false, relayListType = RelayListType.EXIT, @@ -124,7 +124,7 @@ class SelectLocationScreenTest { setContentWithTheme { SelectLocationScreen( state = - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = emptyList(), multihopEnabled = false, relayListType = RelayListType.EXIT, @@ -156,7 +156,7 @@ class SelectLocationScreenTest { setContentWithTheme { SelectLocationScreen( state = - SelectLocationUiState( + SelectLocationUiState.Data( // searchTerm = "", filterChips = emptyList(), multihopEnabled = false, @@ -189,7 +189,7 @@ class SelectLocationScreenTest { setContentWithTheme { SelectLocationScreen( state = - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = emptyList(), multihopEnabled = false, relayListType = RelayListType.EXIT, 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 04b29268a5..47b6ba7923 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 index b79814746c..a1b6e7bba0 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<RootGraph>(style = DestinationStyle.Dialog::class) @Composable -fun DaitaConfirmation(navigator: ResultBackNavigator<Boolean>) { +fun DaitaDirectOnlyConfirmation(navigator: ResultBackNavigator<Boolean>) { 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 index 4cfbbb087e..64e5cd46de 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<RootGraph>(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/SelectLocationsUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SelectLocationsUiStatePreviewParameterProvider.kt index b0415b1c7e..6893397f9f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SelectLocationsUiStatePreviewParameterProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SelectLocationsUiStatePreviewParameterProvider.kt @@ -12,12 +12,13 @@ class SelectLocationsUiStatePreviewParameterProvider : PreviewParameterProvider<SelectLocationUiState> { override val values = sequenceOf( - SelectLocationUiState( + SelectLocationUiState.Loading, + SelectLocationUiState.Data( filterChips = emptyList(), multihopEnabled = false, relayListType = RelayListType.EXIT, ), - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = listOf( FilterChip.Ownership(ownership = ModelOwnership.Rented), @@ -26,12 +27,12 @@ class SelectLocationsUiStatePreviewParameterProvider : multihopEnabled = false, relayListType = RelayListType.EXIT, ), - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = emptyList(), multihopEnabled = true, relayListType = RelayListType.ENTRY, ), - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = listOf( FilterChip.Ownership(ownership = ModelOwnership.MullvadOwned), 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 18f422a988..5a7a6b276a 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<Setting appVersion = "2222.22", isLoggedIn = true, isSupportedVersion = true, + isDaitaEnabled = true, isPlayBuild = true, multihopEnabled = false, ), @@ -17,6 +18,7 @@ class SettingsUiStatePreviewParameterProvider : PreviewParameterProvider<Setting appVersion = "9000.1", isLoggedIn = false, isSupportedVersion = false, + isDaitaEnabled = false, isPlayBuild = false, multihopEnabled = false, ), diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt index c041f5516c..796965d856 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt @@ -20,7 +20,6 @@ class VpnSettingsUiStatePreviewParameterProvider : PreviewParameterProvider<VpnS VpnSettingsUiState.createDefault( mtu = Mtu(MTU), isLocalNetworkSharingEnabled = true, - isDaitaEnabled = true, isCustomDnsEnabled = true, customDnsItems = listOf(CustomDnsItem("0.0.0.0", false)), contentBlockersOptions = diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DaitaScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DaitaScreen.kt new file mode 100644 index 0000000000..c7c3b61752 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DaitaScreen.kt @@ -0,0 +1,204 @@ +package net.mullvad.mullvadvpn.compose.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.compose.dropUnlessResumed +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.generated.destinations.DaitaDirectOnlyConfirmationDestination +import com.ramcosta.composedestinations.generated.destinations.DaitaDirectOnlyInfoDestination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.result.ResultRecipient +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.cell.HeaderSwitchComposeCell +import net.mullvad.mullvadvpn.compose.cell.SwitchComposeSubtitleCell +import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton +import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar +import net.mullvad.mullvadvpn.compose.state.DaitaUiState +import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition +import net.mullvad.mullvadvpn.compose.util.OnNavResultValue +import net.mullvad.mullvadvpn.lib.theme.AppTheme +import net.mullvad.mullvadvpn.lib.theme.Dimens +import net.mullvad.mullvadvpn.viewmodel.DaitaViewModel +import org.koin.androidx.compose.koinViewModel + +@Preview +@Composable +private fun PreviewDaitaScreen() { + AppTheme { DaitaScreen(state = DaitaUiState(daitaEnabled = false, directOnly = false)) } +} + +@Destination<RootGraph>(style = SlideInFromRightTransition::class) +@Composable +fun Daita( + navigator: DestinationsNavigator, + daitaConfirmationDialogResult: ResultRecipient<DaitaDirectOnlyConfirmationDestination, Boolean>, +) { + val viewModel = koinViewModel<DaitaViewModel>() + val state by viewModel.uiState.collectAsStateWithLifecycle() + + daitaConfirmationDialogResult.OnNavResultValue { + if (it) { + viewModel.setDirectOnly(true) + } + } + + DaitaScreen( + state = state, + onDaitaEnabled = viewModel::setDaita, + onDirectOnlyClick = { 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 = {}, + onDirectOnlyClick: (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 = onDirectOnlyClick, + 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 b8c418cd06..75ba5abdd8 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( @@ -100,6 +103,9 @@ fun SettingsScreen( ) { if (state.isLoggedIn) { itemWithDivider { + DaitaCell(isDaitaEnabled = state.isDaitaEnabled, onDaitaClick = onDaitaClick) + } + itemWithDivider { MultihopCell( isMultihopEnabled = state.multihopEnabled, onMultihopClick = onMultihopClick, @@ -221,6 +227,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) TwoRowCell( 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 24b19cfae6..f763272438 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<DnsDestination, DnsDialogResult>, customWgPortResult: ResultRecipient<WireguardCustomPortDestination, Port?>, mtuDialogResult: ResultRecipient<MtuDestination, Boolean>, - daitaConfirmationDialogResult: ResultRecipient<DaitaConfirmationDestination, Boolean>, ) { val vm = koinViewModel<VpnSettingsViewModel>() 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<PortRange>) -> 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 8f07ab180e..3538aacff1 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 3e40d57090..d6d4721f20 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 @@ -3,7 +3,9 @@ package net.mullvad.mullvadvpn.compose.screen.location import android.annotation.SuppressLint import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -26,6 +28,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -39,6 +42,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 @@ -53,6 +57,7 @@ import net.mullvad.mullvadvpn.compose.button.MullvadSegmentedEndButton import net.mullvad.mullvadvpn.compose.button.MullvadSegmentedStartButton import net.mullvad.mullvadvpn.compose.cell.FilterRow import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData +import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.compose.component.ScaffoldWithSmallTopBar import net.mullvad.mullvadvpn.compose.extensions.dropUnlessResumed import net.mullvad.mullvadvpn.compose.preview.SelectLocationsUiStatePreviewParameterProvider @@ -69,7 +74,7 @@ import net.mullvad.mullvadvpn.viewmodel.location.SelectLocationSideEffect import net.mullvad.mullvadvpn.viewmodel.location.SelectLocationViewModel import org.koin.androidx.compose.koinViewModel -@Preview("Default|Filters|Multihop|Multihop and Filters") +@Preview("Loading|Default|Filters|Multihop|Multihop and Filters") @Composable private fun PreviewSelectLocationScreen( @PreviewParameter(SelectLocationsUiStatePreviewParameterProvider::class) @@ -190,6 +195,7 @@ fun SelectLocation( ) }, onSelectRelayList = vm::selectRelayList, + openDaitaSettings = dropUnlessResumed { navigator.navigate(DaitaDestination) }, ) } @@ -216,6 +222,7 @@ fun SelectLocationScreen( onEditLocationsCustomList: (RelayItem.CustomList) -> Unit = {}, onDeleteCustomList: (RelayItem.CustomList) -> Unit = {}, onSelectRelayList: (RelayListType) -> Unit = {}, + openDaitaSettings: () -> Unit = {}, ) { val backgroundColor = MaterialTheme.colorScheme.surface @@ -232,14 +239,19 @@ fun SelectLocationScreen( }, snackbarHostState = snackbarHostState, actions = { - IconButton(onClick = { onSearchClick(state.relayListType) }) { + IconButton( + enabled = state is SelectLocationUiState.Data, + onClick = { + if (state is SelectLocationUiState.Data) onSearchClick(state.relayListType) + }, + ) { Icon( imageVector = Icons.Default.Search, - contentDescription = stringResource(id = R.string.filter), + contentDescription = stringResource(id = R.string.search), tint = MaterialTheme.colorScheme.onSurface, ) } - IconButton(onClick = onFilterClick) { + IconButton(enabled = state is SelectLocationUiState.Data, onClick = onFilterClick) { Icon( imageVector = Icons.Default.FilterList, contentDescription = stringResource(id = R.string.filter), @@ -261,32 +273,51 @@ fun SelectLocationScreen( onHideBottomSheet = { locationBottomSheetState = null }, ) - Column(modifier = modifier.background(backgroundColor).fillMaxSize()) { - AnimatedContent(targetState = state.filterChips, label = "Select location top bar") { - filterChips -> - if (filterChips.isNotEmpty()) { - FilterRow( - filters = filterChips, - onRemoveOwnershipFilter = removeOwnershipFilter, - onRemoveProviderFilter = removeProviderFilter, - ) + Column( + modifier = modifier.background(backgroundColor).fillMaxSize(), + verticalArrangement = + when (state) { + SelectLocationUiState.Loading -> Arrangement.Center + is SelectLocationUiState.Data -> Arrangement.Top + }, + ) { + when (state) { + SelectLocationUiState.Loading -> { + Loading() } - } + is SelectLocationUiState.Data -> { + AnimatedContent( + targetState = state.filterChips, + label = "Select location top bar", + ) { filterChips -> + if (filterChips.isNotEmpty()) { + FilterRow( + filters = filterChips, + onRemoveOwnershipFilter = removeOwnershipFilter, + onRemoveProviderFilter = removeProviderFilter, + ) + } + } - if (state.multihopEnabled) { - MultihopBar(state.relayListType, onSelectRelayList) - } + if (state.multihopEnabled) { + MultihopBar(state.relayListType, onSelectRelayList) + } - if (state.filterChips.isNotEmpty() || state.multihopEnabled) { - Spacer(modifier = Modifier.height(height = Dimens.verticalSpace)) - } + if (state.filterChips.isNotEmpty() || state.multihopEnabled) { + Spacer(modifier = Modifier.height(height = Dimens.verticalSpace)) + } - RelayLists( - state = state, - backgroundColor = backgroundColor, - onSelectRelay = onSelectRelay, - onUpdateBottomSheetState = { newState -> locationBottomSheetState = newState }, - ) + RelayLists( + state = state, + backgroundColor = backgroundColor, + onSelectRelay = onSelectRelay, + openDaitaSettings = openDaitaSettings, + onUpdateBottomSheetState = { newState -> + locationBottomSheetState = newState + }, + ) + } + } } } } @@ -312,22 +343,15 @@ private fun MultihopBar(relayListType: RelayListType, onSelectRelayList: (RelayL @Composable private fun RelayLists( - state: SelectLocationUiState, + state: SelectLocationUiState.Data, backgroundColor: Color, onSelectRelay: (RelayItem) -> Unit, + openDaitaSettings: () -> Unit, onUpdateBottomSheetState: (LocationBottomSheetState) -> Unit, ) { - // For multihop we want to start on the entry list. - // If multihop is not enabled we want to start on the exit list. - // The exit endpoint is what is selected when multihop is disabled. val pagerState = rememberPagerState( - initialPage = - if (state.multihopEnabled) { - RelayListType.ENTRY.ordinal - } else { - RelayListType.EXIT.ordinal - }, + initialPage = state.relayListType.ordinal, pageCount = { RelayListType.entries.size }, ) LaunchedEffect(state.relayListType) { @@ -349,7 +373,13 @@ private fun RelayLists( backgroundColor = backgroundColor, relayListType = RelayListType.entries[pageIndex], onSelectRelay = onSelectRelay, + openDaitaSettings = openDaitaSettings, onUpdateBottomSheetState = onUpdateBottomSheetState, ) } } + +@Composable +private fun ColumnScope.Loading() { + MullvadCircularProgressIndicatorLarge(modifier = Modifier.align(Alignment.CenterHorizontally)) +} 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 0000000000..59f00d0347 --- /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 bb320de81d..d470187bcf 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<RelayListItem>, val customLists: List<RelayItem.CustomList>, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt index bb61bd4e7d..fd2abab8c4 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt @@ -2,8 +2,12 @@ package net.mullvad.mullvadvpn.compose.state import net.mullvad.mullvadvpn.usecase.FilterChip -data class SelectLocationUiState( - val filterChips: List<FilterChip>, - val multihopEnabled: Boolean, - val relayListType: RelayListType, -) +sealed interface SelectLocationUiState { + data object Loading : SelectLocationUiState + + data class Data( + val filterChips: List<FilterChip>, + val multihopEnabled: Boolean, + val relayListType: RelayListType, + ) : SelectLocationUiState +} 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 4ebbf9ad23..ad8dbd0e22 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 eede76ff7c..49d0ebd4aa 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<CustomDnsItem>, 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<CustomDnsItem> = 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 f43f1caf8f..df35e54006 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 @@ -217,8 +218,8 @@ val uiModule = module { viewModel { WireguardCustomPortDialogViewModel(get()) } 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 { SelectLocationViewModel(get(), get(), get(), get(), get(), get(), get()) } + 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()) } @@ -262,8 +263,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 f21adee735..5584d8e991 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 @@ -95,8 +95,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 8be8d2ae7e..e6b03ee599 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 366a7321f6..37e5f71ecc 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, + daitaDirectOnly = settings?.daitaAndDirectOnly() == true, isMultihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListType, ) @@ -48,7 +49,7 @@ class FilterChipUseCase( selectedOwnership: Constraint<Ownership>, selectedConstraintProviders: Constraint<Providers>, allProviders: List<Provider>, - isDaitaEnabled: Boolean, + daitaDirectOnly: Boolean, isMultihopEnabled: Boolean, relayListType: RelayListType, ): List<FilterChip> { @@ -72,7 +73,7 @@ class FilterChipUseCase( } if ( shouldFilterByDaita( - isDaitaEnabled = isDaitaEnabled, + daitaDirectOnly = daitaDirectOnly, relayListType = relayListType, isMultihopEnabled = isMultihopEnabled, ) @@ -88,6 +89,10 @@ class FilterChipUseCase( ): List<Provider> = 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 6712d9275f..15eee7e4ed 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 = shouldFilterByDaita( - isDaitaEnabled = settings?.isDaitaEnabled() == true, + daitaDirectOnly = settings?.daitaAndDirectOnly() == true, isMultihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListType, ), @@ -44,4 +45,8 @@ class FilteredRelayListUseCase( providers: Constraint<Providers>, 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 c326b176a5..42250ec1dc 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, daita = shouldFilterByDaita( - isDaitaEnabled = settings?.isDaitaEnabled() == true, + daitaDirectOnly = settings?.daitaAndDirectOnly() == true, isMultihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListType, ), @@ -45,4 +46,8 @@ class FilterCustomListsRelayItemUseCase( providers: Constraint<Providers>, daita: Boolean, ) = mapNotNull { it.filter(ownership, providers, daita = daita) } + + private fun Settings.daitaAndDirectOnly() = + tunnelOptions.wireguard.daitaSettings.enabled && + tunnelOptions.wireguard.daitaSettings.directOnly } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt index 717d007f92..8049bc2d28 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt @@ -3,10 +3,10 @@ package net.mullvad.mullvadvpn.util import net.mullvad.mullvadvpn.compose.state.RelayListType fun shouldFilterByDaita( - isDaitaEnabled: Boolean, + daitaDirectOnly: Boolean, isMultihopEnabled: Boolean, relayListType: RelayListType, ) = - isDaitaEnabled && + daitaDirectOnly && (relayListType == RelayListType.ENTRY || !isMultihopEnabled && relayListType == RelayListType.EXIT) 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 0000000000..3243239fed --- /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 22309fecfd..5cc6f1562b 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 e160776ee0..90f98fceaa 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 624716198d..e8ccf8f4a0 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<CustomDnsItem>, 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 d5063f0f44..46d8ac519d 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<Set<String>> = MutableStateFlow(initialExpand(initialSelection())) val uiState: StateFlow<SelectLocationListUiState> = - 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/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt index dd6736a45d..f78b59c3fb 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt @@ -18,9 +18,11 @@ import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.RelayItem +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.CustomListsRepository import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.RelayListRepository +import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.usecase.FilterChipUseCase import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase @@ -34,6 +36,7 @@ class SelectLocationViewModel( private val relayListRepository: RelayListRepository, private val wireguardConstraintsRepository: WireguardConstraintsRepository, private val filterChipUseCase: FilterChipUseCase, + private val settingsRepository: SettingsRepository, ) : ViewModel() { private val _relayListType: MutableStateFlow<RelayListType> = MutableStateFlow(initialRelayListSelection()) @@ -44,30 +47,24 @@ class SelectLocationViewModel( wireguardConstraintsRepository.wireguardConstraints, _relayListType, ) { filterChips, wireguardConstraints, relayListSelection -> - SelectLocationUiState( + SelectLocationUiState.Data( filterChips = filterChips, multihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListSelection, ) } - .stateIn( - viewModelScope, - SharingStarted.Lazily, - SelectLocationUiState( - filterChips = emptyList(), - multihopEnabled = false, - relayListType = RelayListType.EXIT, - ), - ) + .stateIn(viewModelScope, SharingStarted.Lazily, SelectLocationUiState.Loading) private val _uiSideEffect = Channel<SelectLocationSideEffect>() val uiSideEffect = _uiSideEffect.receiveAsFlow() private fun initialRelayListSelection() = - if (wireguardConstraintsRepository.wireguardConstraints.value?.isMultihopEnabled == true) { - RelayListType.ENTRY - } else { - RelayListType.EXIT + when { + settingsRepository.settingsUpdates.value?.daitaWithoutDirectOnly() == true -> + RelayListType.EXIT + wireguardConstraintsRepository.wireguardConstraints.value?.isMultihopEnabled == true -> + RelayListType.ENTRY + else -> RelayListType.EXIT } private fun filterChips() = _relayListType.flatMapLatest { filterChipUseCase(it) } @@ -133,6 +130,10 @@ class SelectLocationViewModel( fun removeProviderFilter() { viewModelScope.launch { relayListFilterRepository.updateSelectedProviders(Constraint.Any) } } + + private fun Settings.daitaWithoutDirectOnly() = + tunnelOptions.wireguard.daitaSettings.enabled && + !tunnelOptions.wireguard.daitaSettings.directOnly } sealed interface SelectLocationSideEffect { 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 0000000000..918f0c9e6e --- /dev/null +++ b/android/app/src/main/res/drawable/daita_illustration_1.xml @@ -0,0 +1,342 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:width="328dp" + android:height="103dp" + android:viewportWidth="328" + android:viewportHeight="103" + tools:ignore="VectorRaster"> + <path + android:pathData="M0,12C0,5.37 5.37,0 12,0H316C322.63,0 328,5.37 328,12V90.5C328,97.13 322.63,102.5 316,102.5H12C5.37,102.5 0,97.13 0,90.5V12Z" + android:fillColor="#152637"/> + <path + android:pathData="M1,12C1,5.92 5.92,1 12,1H316C322.08,1 327,5.92 327,12V90.5C327,96.58 322.08,101.5 316,101.5H12C5.92,101.5 1,96.58 1,90.5V12Z" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#304358"/> + <path + android:pathData="M75.17,52.39C74.54,52.39 74.03,51.88 74.03,51.25L74.03,50.11C74.03,49.48 74.54,48.97 75.17,48.97C75.8,48.97 76.31,49.48 76.31,50.11L76.31,51.25C76.31,51.88 75.8,52.39 75.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M102.5,52.39C101.87,52.39 101.36,51.88 101.36,51.25L101.36,50.11C101.36,49.48 101.87,48.97 102.5,48.97C103.13,48.97 103.64,49.48 103.64,50.11L103.64,51.25C103.64,51.88 103.13,52.39 102.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M107.06,52.39C106.43,52.39 105.92,51.88 105.92,51.25L105.92,50.11C105.92,49.48 106.43,48.97 107.06,48.97C107.68,48.97 108.19,49.48 108.19,50.11L108.19,51.25C108.19,51.88 107.68,52.39 107.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M111.61,52.39C110.98,52.39 110.47,51.88 110.47,51.25L110.47,50.11C110.47,49.48 110.98,48.97 111.61,48.97C112.24,48.97 112.75,49.48 112.75,50.11L112.75,51.25C112.75,51.88 112.24,52.39 111.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M116.17,52.39C115.54,52.39 115.03,51.88 115.03,51.25L115.03,50.11C115.03,49.48 115.54,48.97 116.17,48.97C116.8,48.97 117.31,49.48 117.31,50.11L117.31,51.25C117.31,51.88 116.8,52.39 116.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M120.72,52.39C120.09,52.39 119.58,51.88 119.58,51.25L119.58,50.11C119.58,49.48 120.09,48.97 120.72,48.97C121.35,48.97 121.86,49.48 121.86,50.11L121.86,51.25C121.86,51.88 121.35,52.39 120.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M125.28,52.39C124.65,52.39 124.14,51.88 124.14,51.25L124.14,50.11C124.14,49.48 124.65,48.97 125.28,48.97C125.91,48.97 126.42,49.48 126.42,50.11L126.42,51.25C126.42,51.88 125.91,52.39 125.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M129.83,52.39C129.2,52.39 128.69,51.88 128.69,51.25L128.69,50.11C128.69,49.48 129.2,48.97 129.83,48.97C130.46,48.97 130.97,49.48 130.97,50.11L130.97,51.25C130.97,51.88 130.46,52.39 129.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M134.39,52.39C133.76,52.39 133.25,51.88 133.25,51.25L133.25,50.11C133.25,49.48 133.76,48.97 134.39,48.97C135.02,48.97 135.53,49.48 135.53,50.11L135.53,51.25C135.53,51.88 135.02,52.39 134.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M138.94,52.39C138.32,52.39 137.81,51.88 137.81,51.25L137.81,50.11C137.81,49.48 138.32,48.97 138.94,48.97C139.57,48.97 140.08,49.48 140.08,50.11L140.08,51.25C140.08,51.88 139.57,52.39 138.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M143.5,52.39C142.87,52.39 142.36,51.88 142.36,51.25L142.36,50.11C142.36,49.48 142.87,48.97 143.5,48.97C144.13,48.97 144.64,49.48 144.64,50.11L144.64,51.25C144.64,51.88 144.13,52.39 143.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M184.5,52.39C183.87,52.39 183.36,51.88 183.36,51.25L183.36,50.11C183.36,49.48 183.87,48.97 184.5,48.97C185.13,48.97 185.64,49.48 185.64,50.11L185.64,51.25C185.64,51.88 185.13,52.39 184.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M189.06,52.39C188.43,52.39 187.92,51.88 187.92,51.25L187.92,50.11C187.92,49.48 188.43,48.97 189.06,48.97C189.68,48.97 190.19,49.48 190.19,50.11L190.19,51.25C190.19,51.88 189.68,52.39 189.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M193.61,52.39C192.98,52.39 192.47,51.88 192.47,51.25L192.47,50.11C192.47,49.48 192.98,48.97 193.61,48.97C194.24,48.97 194.75,49.48 194.75,50.11L194.75,51.25C194.75,51.88 194.24,52.39 193.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M198.17,52.39C197.54,52.39 197.03,51.88 197.03,51.25L197.03,50.11C197.03,49.48 197.54,48.97 198.17,48.97C198.8,48.97 199.31,49.48 199.31,50.11L199.31,51.25C199.31,51.88 198.8,52.39 198.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M202.72,52.39C202.09,52.39 201.58,51.88 201.58,51.25L201.58,50.11C201.58,49.48 202.09,48.97 202.72,48.97C203.35,48.97 203.86,49.48 203.86,50.11L203.86,51.25C203.86,51.88 203.35,52.39 202.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M207.28,52.39C206.65,52.39 206.14,51.88 206.14,51.25L206.14,50.11C206.14,49.48 206.65,48.97 207.28,48.97C207.91,48.97 208.42,49.48 208.42,50.11L208.42,51.25C208.42,51.88 207.91,52.39 207.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M211.83,52.39C211.2,52.39 210.69,51.88 210.69,51.25L210.69,50.11C210.69,49.48 211.2,48.97 211.83,48.97C212.46,48.97 212.97,49.48 212.97,50.11L212.97,51.25C212.97,51.88 212.46,52.39 211.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M216.39,52.39C215.76,52.39 215.25,51.88 215.25,51.25L215.25,50.11C215.25,49.48 215.76,48.97 216.39,48.97C217.02,48.97 217.53,49.48 217.53,50.11L217.53,51.25C217.53,51.88 217.02,52.39 216.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M220.94,52.39C220.32,52.39 219.81,51.88 219.81,51.25L219.81,50.11C219.81,49.48 220.32,48.97 220.94,48.97C221.57,48.97 222.08,49.48 222.08,50.11L222.08,51.25C222.08,51.88 221.57,52.39 220.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M225.5,52.39C224.87,52.39 224.36,51.88 224.36,51.25L224.36,50.11C224.36,49.48 224.87,48.97 225.5,48.97C226.13,48.97 226.64,49.48 226.64,50.11L226.64,51.25C226.64,51.88 226.13,52.39 225.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M230.06,52.39C229.43,52.39 228.92,51.88 228.92,51.25L228.92,50.11C228.92,49.48 229.43,48.97 230.06,48.97C230.68,48.97 231.19,49.48 231.19,50.11L231.19,51.25C231.19,51.88 230.68,52.39 230.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M234.61,52.39C233.98,52.39 233.47,51.88 233.47,51.25L233.47,50.11C233.47,49.48 233.98,48.97 234.61,48.97C235.24,48.97 235.75,49.48 235.75,50.11L235.75,51.25C235.75,51.88 235.24,52.39 234.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M239.17,52.39C238.54,52.39 238.03,51.88 238.03,51.25L238.03,50.11C238.03,49.48 238.54,48.97 239.17,48.97C239.8,48.97 240.31,49.48 240.31,50.11L240.31,51.25C240.31,51.88 239.8,52.39 239.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M243.72,52.39C243.09,52.39 242.58,51.88 242.58,51.25L242.58,50.11C242.58,49.48 243.09,48.97 243.72,48.97C244.35,48.97 244.86,49.48 244.86,50.11L244.86,51.25C244.86,51.88 244.35,52.39 243.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M248.28,52.39C247.65,52.39 247.14,51.88 247.14,51.25L247.14,50.11C247.14,49.48 247.65,48.97 248.28,48.97C248.91,48.97 249.42,49.48 249.42,50.11L249.42,51.25C249.42,51.88 248.91,52.39 248.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M252.83,52.39C252.2,52.39 251.69,51.88 251.69,51.25L251.69,50.11C251.69,49.48 252.2,48.97 252.83,48.97C253.46,48.97 253.97,49.48 253.97,50.11L253.97,51.25C253.97,51.88 253.46,52.39 252.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M257.39,52.39C256.76,52.39 256.25,51.88 256.25,51.25L256.25,50.11C256.25,49.48 256.76,48.97 257.39,48.97C258.02,48.97 258.53,49.48 258.53,50.11L258.53,51.25C258.53,51.88 258.02,52.39 257.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M261.94,52.39C261.32,52.39 260.81,51.88 260.81,51.25L260.81,50.11C260.81,49.48 261.32,48.97 261.94,48.97C262.57,48.97 263.08,49.48 263.08,50.11L263.08,51.25C263.08,51.88 262.57,52.39 261.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M266.5,52.39C265.87,52.39 265.36,51.88 265.36,51.25L265.36,50.11C265.36,49.48 265.87,48.97 266.5,48.97C267.13,48.97 267.64,49.48 267.64,50.11L267.64,51.25C267.64,51.88 267.13,52.39 266.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M271.06,52.39C270.43,52.39 269.92,51.88 269.92,51.25L269.92,50.11C269.92,49.48 270.43,48.97 271.06,48.97C271.68,48.97 272.19,49.48 272.19,50.11L272.19,51.25C272.19,51.88 271.68,52.39 271.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M275.61,52.39C274.98,52.39 274.47,51.88 274.47,51.25L274.47,50.11C274.47,49.48 274.98,48.97 275.61,48.97C276.24,48.97 276.75,49.48 276.75,50.11L276.75,51.25C276.75,51.88 276.24,52.39 275.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M70.61,52.39C69.98,52.39 69.47,51.88 69.47,51.25L69.47,50.11C69.47,49.48 69.98,48.97 70.61,48.97C71.24,48.97 71.75,49.48 71.75,50.11L71.75,51.25C71.75,51.88 71.24,52.39 70.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M97.94,52.39C97.32,52.39 96.81,51.88 96.81,51.25L96.81,50.11C96.81,49.48 97.32,48.97 97.94,48.97C98.57,48.97 99.08,49.48 99.08,50.11L99.08,51.25C99.08,51.88 98.57,52.39 97.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M93.39,52.39C92.76,52.39 92.25,51.88 92.25,51.25L92.25,50.11C92.25,49.48 92.76,48.97 93.39,48.97C94.02,48.97 94.53,49.48 94.53,50.11L94.53,51.25C94.53,51.88 94.02,52.39 93.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M52.39,52.39C51.76,52.39 51.25,51.88 51.25,51.25L51.25,50.11C51.25,49.48 51.76,48.97 52.39,48.97C53.02,48.97 53.53,49.48 53.53,50.11L53.53,51.25C53.53,51.88 53.02,52.39 52.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M56.94,52.39C56.32,52.39 55.81,51.88 55.81,51.25L55.81,50.11C55.81,49.48 56.32,48.97 56.94,48.97C57.57,48.97 58.08,49.48 58.08,50.11L58.08,51.25C58.08,51.88 57.57,52.39 56.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M61.5,52.39C60.87,52.39 60.36,51.88 60.36,51.25L60.36,50.11C60.36,49.48 60.87,48.97 61.5,48.97C62.13,48.97 62.64,49.48 62.64,50.11L62.64,51.25C62.64,51.88 62.13,52.39 61.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M66.06,52.39C65.43,52.39 64.92,51.88 64.92,51.25L64.92,50.11C64.92,49.48 65.43,48.97 66.06,48.97C66.68,48.97 67.19,49.48 67.19,50.11L67.19,51.25C67.19,51.88 66.68,52.39 66.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M79.72,52.39C79.09,52.39 78.58,51.88 78.58,51.25L78.58,50.11C78.58,49.48 79.09,48.97 79.72,48.97C80.35,48.97 80.86,49.48 80.86,50.11L80.86,51.25C80.86,51.88 80.35,52.39 79.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M84.28,52.39C83.65,52.39 83.14,51.88 83.14,51.25L83.14,50.11C83.14,49.48 83.65,48.97 84.28,48.97C84.91,48.97 85.42,49.48 85.42,50.11L85.42,51.25C85.42,51.88 84.91,52.39 84.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M88.83,52.39C88.2,52.39 87.69,51.88 87.69,51.25L87.69,50.11C87.69,49.48 88.2,48.97 88.83,48.97C89.46,48.97 89.97,49.48 89.97,50.11L89.97,51.25C89.97,51.88 89.46,52.39 88.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M52.39,60.36L52.39,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M61.5,55.81L61.5,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M56.94,74.03L56.94,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M79.72,60.36L79.72,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M88.83,55.81L88.83,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M84.28,74.03L84.28,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M107.06,60.36L107.06,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M116.17,55.81L116.17,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M111.61,74.03L111.61,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M134.39,60.36L134.39,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M143.5,55.81L143.5,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M138.94,74.03L138.94,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M184.5,60.36L184.5,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M193.61,55.81L193.61,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M189.06,74.03L189.06,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M211.83,60.36L211.83,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M220.94,55.81L220.94,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M216.39,74.03L216.39,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M239.17,60.36L239.17,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M248.28,55.81L248.28,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M243.72,74.03L243.72,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M266.5,60.36L266.5,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M275.61,55.81L275.61,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M271.06,74.03L271.06,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M301.08,52.39C301.17,51.64 301.24,50.89 301.24,50.11C301.24,49.34 301.17,48.58 301.08,47.83H304.93C305.11,48.56 305.22,49.33 305.22,50.11C305.22,50.9 305.11,51.66 304.93,52.39M299.06,58.72C299.74,57.46 300.27,56.09 300.63,54.67H303.99C302.89,56.57 301.14,58.01 299.06,58.72ZM298.78,52.39H293.45C293.33,51.64 293.26,50.89 293.26,50.11C293.26,49.34 293.33,48.57 293.45,47.83H298.78C298.88,48.57 298.96,49.34 298.96,50.11C298.96,50.89 298.88,51.64 298.78,52.39ZM296.11,59.18C295.17,57.81 294.4,56.3 293.94,54.67H298.29C297.82,56.3 297.06,57.81 296.11,59.18ZM291.56,45.56H288.23C289.32,43.65 291.07,42.21 293.15,41.5C292.47,42.77 291.95,44.13 291.56,45.56ZM288.23,54.67H291.56C291.95,56.09 292.47,57.46 293.15,58.72C291.08,58.01 289.33,56.57 288.23,54.67ZM287.3,52.39C287.11,51.66 287,50.9 287,50.11C287,49.33 287.11,48.56 287.3,47.83H291.15C291.05,48.58 290.99,49.34 290.99,50.11C290.99,50.89 291.05,51.64 291.15,52.39M296.11,41.03C297.06,42.4 297.82,43.93 298.29,45.56H293.94C294.4,43.93 295.17,42.4 296.11,41.03ZM303.99,45.56H300.63C300.28,44.15 299.75,42.78 299.06,41.5C301.16,42.22 302.9,43.67 303.99,45.56ZM296.11,38.72C289.81,38.72 284.72,43.85 284.72,50.11C284.72,53.13 285.92,56.03 288.06,58.16C289.12,59.22 290.37,60.06 291.75,60.63C293.14,61.21 294.62,61.5 296.11,61.5C299.13,61.5 302.03,60.3 304.16,58.16C306.3,56.03 307.5,53.13 307.5,50.11C307.5,48.62 307.2,47.13 306.63,45.75C306.06,44.37 305.22,43.12 304.16,42.06C303.11,41 301.85,40.16 300.47,39.59C299.09,39.02 297.61,38.72 296.11,38.72Z" + android:fillColor="#ffffff" + tools:ignore="VectorPath" /> + <group> + <clip-path + android:pathData="M18.22,37.58h27.33v27.33h-27.33z"/> + <path + android:pathData="M26.19,63.78C25.57,63.78 25.03,63.55 24.59,63.11C24.14,62.66 23.92,62.13 23.92,61.5V41C23.92,40.37 24.14,39.84 24.59,39.39C25.03,38.95 25.57,38.72 26.19,38.72H37.58C38.21,38.72 38.75,38.95 39.19,39.39C39.64,39.84 39.86,40.37 39.86,41V61.5C39.86,62.13 39.64,62.66 39.19,63.11C38.75,63.55 38.21,63.78 37.58,63.78H26.19ZM26.19,60.36V61.5H37.58V60.36H26.19ZM26.19,58.08H37.58V44.42H26.19V58.08ZM26.19,42.14H37.58V41H26.19V42.14Z" + android:fillColor="#ffffff"/> + </group> + <path + android:pathData="M152.61,42.82V48.29C152.61,49.31 153.18,50.11 153.98,50.11H174.14C174.82,50.11 175.5,49.31 175.5,48.29V42.82C175.39,41.8 174.82,41 174.02,41H153.98C153.18,41 152.61,41.8 152.61,42.82ZM161.72,46.69V44.42H160.58V46.69H161.72ZM156.03,46.69H158.31V44.42H156.03V46.69ZM173.11,47.83H154.89V43.28H173.11V47.83ZM152.61,54.21V59.68C152.61,60.7 153.18,61.5 153.98,61.5H174.14C174.82,61.5 175.5,60.7 175.5,59.68V54.21C175.5,53.19 174.93,52.39 174.14,52.39H153.98C153.18,52.39 152.61,53.19 152.61,54.21ZM161.72,58.08V55.81H160.58V58.08H161.72ZM156.03,58.08H158.31V55.81H156.03V58.08ZM173.11,59.22H154.89V54.67H173.11V59.22Z" + android:fillColor="#ffffff"/> +</vector> 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 0000000000..b8de37fadf --- /dev/null +++ b/android/app/src/main/res/drawable/daita_illustration_2.xml @@ -0,0 +1,402 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:width="328dp" + android:height="103dp" + android:viewportWidth="328" + android:viewportHeight="103" + tools:ignore="VectorRaster"> + <path + android:pathData="M0,12C0,5.37 5.37,0 12,0H316C322.63,0 328,5.37 328,12V90.5C328,97.13 322.63,102.5 316,102.5H12C5.37,102.5 0,97.13 0,90.5V12Z" + android:fillColor="#152637"/> + <path + android:pathData="M1,12C1,5.92 5.92,1 12,1H316C322.08,1 327,5.92 327,12V90.5C327,96.58 322.08,101.5 316,101.5H12C5.92,101.5 1,96.58 1,90.5V12Z" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#304358"/> + <path + android:pathData="M75.17,52.39C74.54,52.39 74.03,51.88 74.03,51.25L74.03,50.11C74.03,49.48 74.54,48.97 75.17,48.97C75.8,48.97 76.31,49.48 76.31,50.11L76.31,51.25C76.31,51.88 75.8,52.39 75.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M102.5,52.39C101.87,52.39 101.36,51.88 101.36,51.25L101.36,50.11C101.36,49.48 101.87,48.97 102.5,48.97C103.13,48.97 103.64,49.48 103.64,50.11L103.64,51.25C103.64,51.88 103.13,52.39 102.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M107.06,52.39C106.43,52.39 105.92,51.88 105.92,51.25L105.92,50.11C105.92,49.48 106.43,48.97 107.06,48.97C107.68,48.97 108.19,49.48 108.19,50.11L108.19,51.25C108.19,51.88 107.68,52.39 107.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M111.61,52.39C110.98,52.39 110.47,51.88 110.47,51.25L110.47,50.11C110.47,49.48 110.98,48.97 111.61,48.97C112.24,48.97 112.75,49.48 112.75,50.11L112.75,51.25C112.75,51.88 112.24,52.39 111.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M116.17,52.39C115.54,52.39 115.03,51.88 115.03,51.25L115.03,50.11C115.03,49.48 115.54,48.97 116.17,48.97C116.8,48.97 117.31,49.48 117.31,50.11L117.31,51.25C117.31,51.88 116.8,52.39 116.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M120.72,52.39C120.09,52.39 119.58,51.88 119.58,51.25L119.58,50.11C119.58,49.48 120.09,48.97 120.72,48.97C121.35,48.97 121.86,49.48 121.86,50.11L121.86,51.25C121.86,51.88 121.35,52.39 120.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M125.28,52.39C124.65,52.39 124.14,51.88 124.14,51.25L124.14,50.11C124.14,49.48 124.65,48.97 125.28,48.97C125.91,48.97 126.42,49.48 126.42,50.11L126.42,51.25C126.42,51.88 125.91,52.39 125.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M129.83,52.39C129.2,52.39 128.69,51.88 128.69,51.25L128.69,50.11C128.69,49.48 129.2,48.97 129.83,48.97C130.46,48.97 130.97,49.48 130.97,50.11L130.97,51.25C130.97,51.88 130.46,52.39 129.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M134.39,52.39C133.76,52.39 133.25,51.88 133.25,51.25L133.25,50.11C133.25,49.48 133.76,48.97 134.39,48.97C135.02,48.97 135.53,49.48 135.53,50.11L135.53,51.25C135.53,51.88 135.02,52.39 134.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M138.94,52.39C138.32,52.39 137.81,51.88 137.81,51.25L137.81,50.11C137.81,49.48 138.32,48.97 138.94,48.97C139.57,48.97 140.08,49.48 140.08,50.11L140.08,51.25C140.08,51.88 139.57,52.39 138.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M143.5,52.39C142.87,52.39 142.36,51.88 142.36,51.25L142.36,50.11C142.36,49.48 142.87,48.97 143.5,48.97C144.13,48.97 144.64,49.48 144.64,50.11L144.64,51.25C144.64,51.88 144.13,52.39 143.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M184.5,52.39C183.87,52.39 183.36,51.88 183.36,51.25L183.36,50.11C183.36,49.48 183.87,48.97 184.5,48.97C185.13,48.97 185.64,49.48 185.64,50.11L185.64,51.25C185.64,51.88 185.13,52.39 184.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M189.06,52.39C188.43,52.39 187.92,51.88 187.92,51.25L187.92,50.11C187.92,49.48 188.43,48.97 189.06,48.97C189.68,48.97 190.19,49.48 190.19,50.11L190.19,51.25C190.19,51.88 189.68,52.39 189.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M193.61,52.39C192.98,52.39 192.47,51.88 192.47,51.25L192.47,50.11C192.47,49.48 192.98,48.97 193.61,48.97C194.24,48.97 194.75,49.48 194.75,50.11L194.75,51.25C194.75,51.88 194.24,52.39 193.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M198.17,52.39C197.54,52.39 197.03,51.88 197.03,51.25L197.03,50.11C197.03,49.48 197.54,48.97 198.17,48.97C198.8,48.97 199.31,49.48 199.31,50.11L199.31,51.25C199.31,51.88 198.8,52.39 198.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M202.72,52.39C202.09,52.39 201.58,51.88 201.58,51.25L201.58,50.11C201.58,49.48 202.09,48.97 202.72,48.97C203.35,48.97 203.86,49.48 203.86,50.11L203.86,51.25C203.86,51.88 203.35,52.39 202.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M207.28,52.39C206.65,52.39 206.14,51.88 206.14,51.25L206.14,50.11C206.14,49.48 206.65,48.97 207.28,48.97C207.91,48.97 208.42,49.48 208.42,50.11L208.42,51.25C208.42,51.88 207.91,52.39 207.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M211.83,52.39C211.2,52.39 210.69,51.88 210.69,51.25L210.69,50.11C210.69,49.48 211.2,48.97 211.83,48.97C212.46,48.97 212.97,49.48 212.97,50.11L212.97,51.25C212.97,51.88 212.46,52.39 211.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M216.39,52.39C215.76,52.39 215.25,51.88 215.25,51.25L215.25,50.11C215.25,49.48 215.76,48.97 216.39,48.97C217.02,48.97 217.53,49.48 217.53,50.11L217.53,51.25C217.53,51.88 217.02,52.39 216.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M220.94,52.39C220.32,52.39 219.81,51.88 219.81,51.25L219.81,50.11C219.81,49.48 220.32,48.97 220.94,48.97C221.57,48.97 222.08,49.48 222.08,50.11L222.08,51.25C222.08,51.88 221.57,52.39 220.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M225.5,52.39C224.87,52.39 224.36,51.88 224.36,51.25L224.36,50.11C224.36,49.48 224.87,48.97 225.5,48.97C226.13,48.97 226.64,49.48 226.64,50.11L226.64,51.25C226.64,51.88 226.13,52.39 225.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M230.06,52.39C229.43,52.39 228.92,51.88 228.92,51.25L228.92,50.11C228.92,49.48 229.43,48.97 230.06,48.97C230.68,48.97 231.19,49.48 231.19,50.11L231.19,51.25C231.19,51.88 230.68,52.39 230.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M234.61,52.39C233.98,52.39 233.47,51.88 233.47,51.25L233.47,50.11C233.47,49.48 233.98,48.97 234.61,48.97C235.24,48.97 235.75,49.48 235.75,50.11L235.75,51.25C235.75,51.88 235.24,52.39 234.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M239.17,52.39C238.54,52.39 238.03,51.88 238.03,51.25L238.03,50.11C238.03,49.48 238.54,48.97 239.17,48.97C239.8,48.97 240.31,49.48 240.31,50.11L240.31,51.25C240.31,51.88 239.8,52.39 239.17,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M243.72,52.39C243.09,52.39 242.58,51.88 242.58,51.25L242.58,50.11C242.58,49.48 243.09,48.97 243.72,48.97C244.35,48.97 244.86,49.48 244.86,50.11L244.86,51.25C244.86,51.88 244.35,52.39 243.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M248.28,52.39C247.65,52.39 247.14,51.88 247.14,51.25L247.14,50.11C247.14,49.48 247.65,48.97 248.28,48.97C248.91,48.97 249.42,49.48 249.42,50.11L249.42,51.25C249.42,51.88 248.91,52.39 248.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M252.83,52.39C252.2,52.39 251.69,51.88 251.69,51.25L251.69,50.11C251.69,49.48 252.2,48.97 252.83,48.97C253.46,48.97 253.97,49.48 253.97,50.11L253.97,51.25C253.97,51.88 253.46,52.39 252.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M257.39,52.39C256.76,52.39 256.25,51.88 256.25,51.25L256.25,50.11C256.25,49.48 256.76,48.97 257.39,48.97C258.02,48.97 258.53,49.48 258.53,50.11L258.53,51.25C258.53,51.88 258.02,52.39 257.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M261.94,52.39C261.32,52.39 260.81,51.88 260.81,51.25L260.81,50.11C260.81,49.48 261.32,48.97 261.94,48.97C262.57,48.97 263.08,49.48 263.08,50.11L263.08,51.25C263.08,51.88 262.57,52.39 261.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M266.5,52.39C265.87,52.39 265.36,51.88 265.36,51.25L265.36,50.11C265.36,49.48 265.87,48.97 266.5,48.97C267.13,48.97 267.64,49.48 267.64,50.11L267.64,51.25C267.64,51.88 267.13,52.39 266.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M271.06,52.39C270.43,52.39 269.92,51.88 269.92,51.25L269.92,50.11C269.92,49.48 270.43,48.97 271.06,48.97C271.68,48.97 272.19,49.48 272.19,50.11L272.19,51.25C272.19,51.88 271.68,52.39 271.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M275.61,52.39C274.98,52.39 274.47,51.88 274.47,51.25L274.47,50.11C274.47,49.48 274.98,48.97 275.61,48.97C276.24,48.97 276.75,49.48 276.75,50.11L276.75,51.25C276.75,51.88 276.24,52.39 275.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M70.61,52.39C69.98,52.39 69.47,51.88 69.47,51.25L69.47,50.11C69.47,49.48 69.98,48.97 70.61,48.97C71.24,48.97 71.75,49.48 71.75,50.11L71.75,51.25C71.75,51.88 71.24,52.39 70.61,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M97.94,52.39C97.32,52.39 96.81,51.88 96.81,51.25L96.81,50.11C96.81,49.48 97.32,48.97 97.94,48.97C98.57,48.97 99.08,49.48 99.08,50.11L99.08,51.25C99.08,51.88 98.57,52.39 97.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M93.39,52.39C92.76,52.39 92.25,51.88 92.25,51.25L92.25,50.11C92.25,49.48 92.76,48.97 93.39,48.97C94.02,48.97 94.53,49.48 94.53,50.11L94.53,51.25C94.53,51.88 94.02,52.39 93.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M52.39,52.39C51.76,52.39 51.25,51.88 51.25,51.25L51.25,50.11C51.25,49.48 51.76,48.97 52.39,48.97C53.02,48.97 53.53,49.48 53.53,50.11L53.53,51.25C53.53,51.88 53.02,52.39 52.39,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M56.94,52.39C56.32,52.39 55.81,51.88 55.81,51.25L55.81,50.11C55.81,49.48 56.32,48.97 56.94,48.97C57.57,48.97 58.08,49.48 58.08,50.11L58.08,51.25C58.08,51.88 57.57,52.39 56.94,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M61.5,52.39C60.87,52.39 60.36,51.88 60.36,51.25L60.36,50.11C60.36,49.48 60.87,48.97 61.5,48.97C62.13,48.97 62.64,49.48 62.64,50.11L62.64,51.25C62.64,51.88 62.13,52.39 61.5,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M66.06,52.39C65.43,52.39 64.92,51.88 64.92,51.25L64.92,50.11C64.92,49.48 65.43,48.97 66.06,48.97C66.68,48.97 67.19,49.48 67.19,50.11L67.19,51.25C67.19,51.88 66.68,52.39 66.06,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M79.72,52.39C79.09,52.39 78.58,51.88 78.58,51.25L78.58,50.11C78.58,49.48 79.09,48.97 79.72,48.97C80.35,48.97 80.86,49.48 80.86,50.11L80.86,51.25C80.86,51.88 80.35,52.39 79.72,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M84.28,52.39C83.65,52.39 83.14,51.88 83.14,51.25L83.14,50.11C83.14,49.48 83.65,48.97 84.28,48.97C84.91,48.97 85.42,49.48 85.42,50.11L85.42,51.25C85.42,51.88 84.91,52.39 84.28,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M88.83,52.39C88.2,52.39 87.69,51.88 87.69,51.25L87.69,50.11C87.69,49.48 88.2,48.97 88.83,48.97C89.46,48.97 89.97,49.48 89.97,50.11L89.97,51.25C89.97,51.88 89.46,52.39 88.83,52.39Z" + android:fillColor="#3E5F81" + android:fillType="evenOdd"/> + <path + android:pathData="M52.39,75.17C51.76,75.17 51.25,74.66 51.25,74.03L51.25,28.47C51.25,27.84 51.76,27.33 52.39,27.33C53.02,27.33 53.53,27.84 53.53,28.47L53.53,74.03C53.53,74.66 53.02,75.17 52.39,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M61.5,75.17C60.87,75.17 60.36,74.66 60.36,74.03L60.36,28.47C60.36,27.84 60.87,27.33 61.5,27.33C62.13,27.33 62.64,27.84 62.64,28.47L62.64,74.03C62.64,74.66 62.13,75.17 61.5,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M79.72,75.17C79.09,75.17 78.58,74.66 78.58,74.03L78.58,28.47C78.58,27.84 79.09,27.33 79.72,27.33C80.35,27.33 80.86,27.84 80.86,28.47L80.86,74.03C80.86,74.66 80.35,75.17 79.72,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M88.83,75.17C88.2,75.17 87.69,74.66 87.69,74.03L87.69,28.47C87.69,27.84 88.2,27.33 88.83,27.33C89.46,27.33 89.97,27.84 89.97,28.47L89.97,74.03C89.97,74.66 89.46,75.17 88.83,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M107.06,75.17C106.43,75.17 105.92,74.66 105.92,74.03L105.92,28.47C105.92,27.84 106.43,27.33 107.06,27.33C107.68,27.33 108.19,27.84 108.19,28.47L108.19,74.03C108.19,74.66 107.68,75.17 107.06,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M116.17,75.17C115.54,75.17 115.03,74.66 115.03,74.03L115.03,28.47C115.03,27.84 115.54,27.33 116.17,27.33C116.8,27.33 117.31,27.84 117.31,28.47L117.31,74.03C117.31,74.66 116.8,75.17 116.17,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M134.39,75.17C133.76,75.17 133.25,74.66 133.25,74.03L133.25,28.47C133.25,27.84 133.76,27.33 134.39,27.33C135.02,27.33 135.53,27.84 135.53,28.47L135.53,74.03C135.53,74.66 135.02,75.17 134.39,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M143.5,75.17C142.87,75.17 142.36,74.66 142.36,74.03L142.36,28.47C142.36,27.84 142.87,27.33 143.5,27.33C144.13,27.33 144.64,27.84 144.64,28.47L144.64,74.03C144.64,74.66 144.13,75.17 143.5,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M97.94,75.17C97.32,75.17 96.81,74.66 96.81,74.03L96.81,28.47C96.81,27.84 97.32,27.33 97.94,27.33C98.57,27.33 99.08,27.84 99.08,28.47L99.08,74.03C99.08,74.66 98.57,75.17 97.94,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M66.06,75.17C65.43,75.17 64.92,74.66 64.92,74.03L64.92,28.47C64.92,27.84 65.43,27.33 66.06,27.33C66.68,27.33 67.19,27.84 67.19,28.47L67.19,74.03C67.19,74.66 66.68,75.17 66.06,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M129.83,75.17C129.2,75.17 128.69,74.66 128.69,74.03L128.69,28.47C128.69,27.84 129.2,27.33 129.83,27.33C130.46,27.33 130.97,27.84 130.97,28.47L130.97,74.03C130.97,74.66 130.46,75.17 129.83,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M56.94,75.17C56.32,75.17 55.81,74.66 55.81,74.03L55.81,28.47C55.81,27.84 56.32,27.33 56.94,27.33C57.57,27.33 58.08,27.84 58.08,28.47L58.08,74.03C58.08,74.66 57.57,75.17 56.94,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M84.28,75.17C83.65,75.17 83.14,74.66 83.14,74.03L83.14,28.47C83.14,27.84 83.65,27.33 84.28,27.33C84.91,27.33 85.42,27.84 85.42,28.47L85.42,74.03C85.42,74.66 84.91,75.17 84.28,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M111.61,75.17C110.98,75.17 110.47,74.66 110.47,74.03L110.47,28.47C110.47,27.84 110.98,27.33 111.61,27.33C112.24,27.33 112.75,27.84 112.75,28.47L112.75,74.03C112.75,74.66 112.24,75.17 111.61,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M138.94,75.17C138.32,75.17 137.81,74.66 137.81,74.03L137.81,28.47C137.81,27.84 138.32,27.33 138.94,27.33C139.57,27.33 140.08,27.84 140.08,28.47L140.08,74.03C140.08,74.66 139.57,75.17 138.94,75.17Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M52.39,60.36L52.39,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M61.5,55.81L61.5,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M56.94,74.03L56.94,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M79.72,60.36L79.72,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M88.83,55.81L88.83,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M84.28,74.03L84.28,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M107.06,60.36L107.06,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M116.17,55.81L116.17,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M111.61,74.03L111.61,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M134.39,60.36L134.39,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M143.5,55.81L143.5,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M138.94,74.03L138.94,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M184.5,60.36L184.5,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M193.61,55.81L193.61,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M189.06,74.03L189.06,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M211.83,60.36L211.83,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M220.94,55.81L220.94,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M216.39,74.03L216.39,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M239.17,60.36L239.17,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M248.28,55.81L248.28,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M243.72,74.03L243.72,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M266.5,60.36L266.5,42.14" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M275.61,55.81L275.61,46.69" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M271.06,74.03L271.06,28.47" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M301.08,52.39C301.17,51.64 301.24,50.89 301.24,50.11C301.24,49.34 301.17,48.58 301.08,47.83H304.93C305.11,48.56 305.22,49.33 305.22,50.11C305.22,50.9 305.11,51.66 304.93,52.39M299.06,58.72C299.74,57.46 300.27,56.09 300.63,54.67H303.99C302.89,56.57 301.14,58.01 299.06,58.72ZM298.78,52.39H293.45C293.33,51.64 293.26,50.89 293.26,50.11C293.26,49.34 293.33,48.57 293.45,47.83H298.78C298.88,48.57 298.96,49.34 298.96,50.11C298.96,50.89 298.88,51.64 298.78,52.39ZM296.11,59.18C295.17,57.81 294.4,56.3 293.94,54.67H298.29C297.82,56.3 297.06,57.81 296.11,59.18ZM291.56,45.56H288.23C289.32,43.65 291.07,42.21 293.15,41.5C292.47,42.77 291.95,44.13 291.56,45.56ZM288.23,54.67H291.56C291.95,56.09 292.47,57.46 293.15,58.72C291.08,58.01 289.33,56.57 288.23,54.67ZM287.3,52.39C287.11,51.66 287,50.9 287,50.11C287,49.33 287.11,48.56 287.3,47.83H291.15C291.05,48.58 290.99,49.34 290.99,50.11C290.99,50.89 291.05,51.64 291.15,52.39M296.11,41.03C297.06,42.4 297.82,43.93 298.29,45.56H293.94C294.4,43.93 295.17,42.4 296.11,41.03ZM303.99,45.56H300.63C300.28,44.15 299.75,42.78 299.06,41.5C301.16,42.22 302.9,43.67 303.99,45.56ZM296.11,38.72C289.81,38.72 284.72,43.85 284.72,50.11C284.72,53.13 285.92,56.03 288.06,58.16C289.12,59.22 290.37,60.06 291.75,60.63C293.14,61.21 294.62,61.5 296.11,61.5C299.13,61.5 302.03,60.3 304.16,58.16C306.3,56.03 307.5,53.13 307.5,50.11C307.5,48.62 307.2,47.13 306.63,45.75C306.06,44.37 305.22,43.12 304.16,42.06C303.11,41 301.85,40.16 300.47,39.59C299.09,39.02 297.61,38.72 296.11,38.72Z" + android:fillColor="#ffffff" + tools:ignore="VectorPath" /> + <group> + <clip-path + android:pathData="M18.22,37.58h27.33v27.33h-27.33z"/> + <path + android:pathData="M26.19,63.78C25.57,63.78 25.03,63.55 24.59,63.11C24.14,62.66 23.92,62.13 23.92,61.5V41C23.92,40.37 24.14,39.84 24.59,39.39C25.03,38.95 25.57,38.72 26.19,38.72H37.58C38.21,38.72 38.75,38.95 39.19,39.39C39.64,39.84 39.86,40.37 39.86,41V61.5C39.86,62.13 39.64,62.66 39.19,63.11C38.75,63.55 38.21,63.78 37.58,63.78H26.19ZM26.19,60.36V61.5H37.58V60.36H26.19ZM26.19,58.08H37.58V44.42H26.19V58.08ZM26.19,42.14H37.58V41H26.19V42.14Z" + android:fillColor="#ffffff"/> + </group> + <path + android:pathData="M152.61,42.82V48.29C152.61,49.31 153.18,50.11 153.98,50.11H174.14C174.82,50.11 175.5,49.31 175.5,48.29V42.82C175.39,41.8 174.82,41 174.02,41H153.98C153.18,41 152.61,41.8 152.61,42.82ZM161.72,46.69V44.42H160.58V46.69H161.72ZM156.03,46.69H158.31V44.42H156.03V46.69ZM173.11,47.83H154.89V43.28H173.11V47.83ZM152.61,54.21V59.68C152.61,60.7 153.18,61.5 153.98,61.5H174.14C174.82,61.5 175.5,60.7 175.5,59.68V54.21C175.5,53.19 174.93,52.39 174.14,52.39H153.98C153.18,52.39 152.61,53.19 152.61,54.21ZM161.72,58.08V55.81H160.58V58.08H161.72ZM156.03,58.08H158.31V55.81H156.03V58.08ZM173.11,59.22H154.89V54.67H173.11V59.22Z" + android:fillColor="#ffffff"/> +</vector> 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 8b3d6d68a2..221d89cf40 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<Settings>(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<Settings>(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<Settings>(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<Settings>(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<Settings>(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/DaitaViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt new file mode 100644 index 0000000000..8eb9770826 --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt @@ -0,0 +1,88 @@ +package net.mullvad.mullvadvpn.viewmodel + +import app.cash.turbine.test +import arrow.core.right +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.state.DaitaUiState +import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.model.Settings +import net.mullvad.mullvadvpn.repository.SettingsRepository +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(TestCoroutineRule::class) +class DaitaViewModelTest { + private val mockSettingsRepository: SettingsRepository = mockk() + private val settings = MutableStateFlow<Settings>(mockk(relaxed = true)) + + private lateinit var viewModel: DaitaViewModel + + @BeforeEach + fun setUp() { + every { mockSettingsRepository.settingsUpdates } returns settings + viewModel = DaitaViewModel(mockSettingsRepository) + } + + @Test + fun `given daita enabled ui state should be daita enabled`() = runTest { + // Arrange + val expectedState = DaitaUiState(daitaEnabled = true, directOnly = false) + settings.value = mockk { + every { tunnelOptions.wireguard.daitaSettings } returns + mockk { + every { enabled } returns true + every { directOnly } returns false + } + } + + // Act, Assert + viewModel.uiState.test { assertEquals(expectedState, awaitItem()) } + } + + @Test + fun `given direct only enabled ui state should be direct only enabled`() = runTest { + // Arrange + val expectedState = DaitaUiState(daitaEnabled = false, directOnly = true) + settings.value = mockk { + every { tunnelOptions.wireguard.daitaSettings } returns + mockk { + every { enabled } returns false + every { directOnly } returns true + } + } + + // Act, Assert + viewModel.uiState.test { assertEquals(expectedState, awaitItem()) } + } + + @Test + fun `set daita should call settings repository set daita enabled`() { + // Arrange + coEvery { mockSettingsRepository.setDaitaEnabled(any()) } returns Unit.right() + + // Act + viewModel.setDaita(true) + + // Assert + coVerify { mockSettingsRepository.setDaitaEnabled(true) } + } + + @Test + fun `set direct only should call settings repository set daita direct only`() { + // Arrange + coEvery { mockSettingsRepository.setDaitaDirectOnly(any()) } returns Unit.right() + + // Act + viewModel.setDirectOnly(true) + + // Assert + coVerify { mockSettingsRepository.setDaitaDirectOnly(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 f2468cbb11..7bc05df05c 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<WireguardConstraints>(mockk(relaxed = true)) + private val settings = MutableStateFlow(mockk<Settings>(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 427b003d33..aae283e91e 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 3584877170..acdf3f5c95 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<List<RelayItem.Location.Country>>(emptyList()) private val selectedLocationFlow = MutableStateFlow<RelayItemSelection>(mockk(relaxed = true)) private val filteredCustomListRelayItems = MutableStateFlow<List<RelayItem.CustomList>>(emptyList()) private val customListRelayItems = MutableStateFlow<List<RelayItem.CustomList>>(emptyList()) + private val settings = MutableStateFlow(mockk<Settings>(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/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt index ef21eac139..77a5e948eb 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt @@ -34,6 +34,7 @@ import net.mullvad.mullvadvpn.relaylist.descendants import net.mullvad.mullvadvpn.repository.CustomListsRepository import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.RelayListRepository +import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.usecase.FilterChip import net.mullvad.mullvadvpn.usecase.FilterChipUseCase @@ -53,6 +54,7 @@ class SelectLocationViewModelTest { private val mockCustomListsRepository: CustomListsRepository = mockk() private val mockWireguardConstraintsRepository: WireguardConstraintsRepository = mockk() private val mockFilterChipUseCase: FilterChipUseCase = mockk() + private val mocksSettingsRepository: SettingsRepository = mockk() private lateinit var viewModel: SelectLocationViewModel @@ -67,6 +69,7 @@ class SelectLocationViewModelTest { every { mockWireguardConstraintsRepository.wireguardConstraints } returns wireguardConstraints every { mockFilterChipUseCase(any()) } returns filterChips + every { mocksSettingsRepository.settingsUpdates } returns MutableStateFlow(null) mockkStatic(RELAY_LIST_EXTENSIONS) mockkStatic(RELAY_ITEM_EXTENSIONS) @@ -79,6 +82,7 @@ class SelectLocationViewModelTest { customListsRepository = mockCustomListsRepository, filterChipUseCase = mockFilterChipUseCase, wireguardConstraintsRepository = mockWireguardConstraintsRepository, + settingsRepository = mocksSettingsRepository, ) } @@ -90,14 +94,7 @@ class SelectLocationViewModelTest { @Test fun `initial state should be correct`() = runTest { - Assertions.assertEquals( - SelectLocationUiState( - filterChips = emptyList(), - multihopEnabled = false, - relayListType = RelayListType.EXIT, - ), - viewModel.uiState.value, - ) + Assertions.assertEquals(SelectLocationUiState.Loading, viewModel.uiState.value) } @Test @@ -134,11 +131,15 @@ class SelectLocationViewModelTest { awaitItem() // Default value viewModel.selectRelayList(RelayListType.ENTRY) // Assert relay list type is entry - assertEquals(RelayListType.ENTRY, awaitItem().relayListType) + val firstState = awaitItem() + assertIs<SelectLocationUiState.Data>(firstState) + assertEquals(RelayListType.ENTRY, firstState.relayListType) // Select entry viewModel.selectRelay(mockRelayItem) - // Await an empty item - assertEquals(RelayListType.EXIT, awaitItem().relayListType) + // Assert relay list type is exit + val secondState = awaitItem() + assertIs<SelectLocationUiState.Data>(secondState) + assertEquals(RelayListType.EXIT, secondState.relayListType) coVerify { mockWireguardConstraintsRepository.setEntryLocation(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 bd27574cbe..d4cedd1e61 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 @@ -123,6 +123,7 @@ 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.enabled import net.mullvad.mullvadvpn.lib.model.entryLocation import net.mullvad.mullvadvpn.lib.model.isMultihopEnabled import net.mullvad.mullvadvpn.lib.model.location @@ -507,17 +508,12 @@ class ManagementService( .mapEmpty() suspend fun setDaitaEnabled(enabled: Boolean): Either<SetDaitaSettingsError, Unit> = - 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) - } + Either.catch { grpc.setEnableDaita(BoolValue.of(enabled)) } + .mapLeft(SetDaitaSettingsError::Unknown) + .mapEmpty() + + suspend fun setDaitaDirectOnly(enabled: Boolean): Either<SetDaitaSettingsError, Unit> = + Either.catch { grpc.setDaitaDirectOnly(BoolValue.of(enabled)) } .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 b3fe88bdc8..f62124a171 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 0412871f43..c7f47b0c29 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 @@ -436,9 +437,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 0000000000..791970cf70 --- /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 b3f1a2e8a0..99e8a2b8dc 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<ApiAccessMethodSetting>, ) { - 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 70b1599c55..f6a489df12 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 e1b4910074..5390ac7cba 100644 --- a/android/lib/resource/src/main/res/values-da/strings.xml +++ b/android/lib/resource/src/main/res/values-da/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Indstil port</string> <string name="custom_port_dialog_valid_ranges">Gyldige områder: %1$s</string> <string name="custom_tunnel_host_resolution_error">Kunne ikke fortolke værten for den tilpassede tunnel. Prøv at ændre dine indstillinger.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Denne funktion er ikke tilgængelig på alle servere. Du skal muligvis ændre placering efter aktivering.</string> <string name="daita_warning">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.</string> <string name="delete">Slet</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Du kan have op til 5 enheder logget ind på én Mullvad-konto.</string> <string name="device_name_info_third_paragraph">Hvis du logger ud, fjernes enheden og enhedsnavnet. Når du logger på igen, får enheden et nyt navn.</string> + <string name="direct_only">Kun direkte</string> <string name="discard">Kassér</string> <string name="discard_changes">Vil du kassere ændringer?</string> <string name="disconnect">Afbryd forbindelse</string> 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 5e385a7380..8545bfffb4 100644 --- a/android/lib/resource/src/main/res/values-de/strings.xml +++ b/android/lib/resource/src/main/res/values-de/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Port festlegen</string> <string name="custom_port_dialog_valid_ranges">Gültige Bereiche: %1$s</string> <string name="custom_tunnel_host_resolution_error">Der Host des benutzerdefinierten Tunnels konnte nicht aufgelöst werden. Versuchen Sie, Ihre Einstellungen zu ändern.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Diese Funktion ist nicht auf allen Servern verfügbar. Möglicherweise müssen Sie nach der Aktivierung den Standort wechseln.</string> <string name="daita_warning">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.</string> <string name="delete">Löschen</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Es sind pro Mullvad-Konto bis zu 5 angemeldete Geräte möglich.</string> <string name="device_name_info_third_paragraph">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.</string> + <string name="direct_only">Nur direkt</string> <string name="discard">Verwerfen</string> <string name="discard_changes">Änderungen verwerfen?</string> <string name="disconnect">Verbindung trennen</string> 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 53d7bbd09e..89224d8dad 100644 --- a/android/lib/resource/src/main/res/values-es/strings.xml +++ b/android/lib/resource/src/main/res/values-es/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Establecer puerto</string> <string name="custom_port_dialog_valid_ranges">Intervalos válidos: %1$s</string> <string name="custom_tunnel_host_resolution_error">No se puede resolver el host del túnel personalizado. Pruebe a cambiar la configuración.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Esta característica no está disponible en todos los servidores. Podría tener que cambiar de ubicación tras habilitarla.</string> <string name="daita_warning">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.</string> <string name="delete">Eliminar</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Puede tener hasta 5 dispositivos conectados a una cuenta de Mullvad.</string> <string name="device_name_info_third_paragraph">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.</string> + <string name="direct_only">Conexión directa</string> <string name="discard">Descartar</string> <string name="discard_changes">¿Descartar los cambios?</string> <string name="disconnect">Desconectar</string> 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 e072cbcaab..6b9d26de9f 100644 --- a/android/lib/resource/src/main/res/values-fi/strings.xml +++ b/android/lib/resource/src/main/res/values-fi/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Määritä portti</string> <string name="custom_port_dialog_valid_ranges">Kelvolliset portit: %1$s</string> <string name="custom_tunnel_host_resolution_error">Muokatun tunnelin isännän selvittäminen ei onnistu. Kokeile muuttaa asetuksiasi.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">Käyttämällä edistynyttä tekoälyä on mahdollista analysoida laitteestasi lähtevä ja siihen tuleva datapakettiliikenne (vaikka liikenne olisi salattu).</string> <string name="daita_relay_subset_warning">Ominaisuus ei ole käytettävissä kaikilla palvelimilla, joten saatat joutua vaihtamaan sijaintia otettuasi sen käyttöön.</string> <string name="daita_warning">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.</string> <string name="delete">Poista</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Yhdelle Mullvad-tilille voi olla kirjautuneena enintään viisi laitetta.</string> <string name="device_name_info_third_paragraph">Jos kirjaudut ulos, laite ja laitteen nimi poistetaan. Kun kirjaudut sisään uudelleen, laitteelle annetaan uusi nimi.</string> + <string name="direct_only">Vain suora</string> <string name="discard">Hylkää</string> <string name="discard_changes">Hylätäänkö muokkaukset?</string> <string name="disconnect">Katkaise yhteys</string> 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 3ddaf01124..830614ab69 100644 --- a/android/lib/resource/src/main/res/values-fr/strings.xml +++ b/android/lib/resource/src/main/res/values-fr/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Définir le port</string> <string name="custom_port_dialog_valid_ranges">Plages valides : %1$s</string> <string name="custom_tunnel_host_resolution_error">Échec de la résolution de l\'hôte du tunnel personnalisé. Essayez de modifier vos paramètres.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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é).</string> <string name="daita_relay_subset_warning">Cette fonctionnalité n\'est pas disponible sur tous les serveurs. Vous devrez peut-être changer d\'emplacement après l\'avoir activée.</string> <string name="daita_warning">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.</string> <string name="delete">Supprimer</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Vous pouvez connecter jusqu\'à 5 appareils au même compte Mullvad.</string> <string name="device_name_info_third_paragraph">Si vous vous déconnectez, l\'appareil et son nom sont supprimés. Lorsque vous vous reconnectez, l\'appareil reçoit un nouveau nom.</string> + <string name="direct_only">Directe uniquement</string> <string name="discard">Annuler</string> <string name="discard_changes">Annuler les modifications ?</string> <string name="disconnect">Déconnexion</string> 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 e5872d9d47..f7d67a62bc 100644 --- a/android/lib/resource/src/main/res/values-it/strings.xml +++ b/android/lib/resource/src/main/res/values-it/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Imposta porta</string> <string name="custom_port_dialog_valid_ranges">Intervalli validi: %1$s</string> <string name="custom_tunnel_host_resolution_error">Impossibile risolvere l\'host del tunnel personalizzato. Prova a modificare le impostazioni.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">Utilizzando un\'IA sofisticata è possibile analizzare il traffico dei pacchetti di dati in entrata e in uscita dal dispositivo (anche se il traffico è crittografato).</string> <string name="daita_relay_subset_warning">Questa funzionalità non è disponibile su tutti i server. Potrebbe essere necessario modificare la posizione dopo l\'abilitazione.</string> <string name="daita_warning">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.</string> <string name="delete">Elimina</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Puoi avere fino a 5 dispositivi registrati su un account Mullvad.</string> <string name="device_name_info_third_paragraph">Se ti disconnetti, il dispositivo e il relativo nome verranno rimossi. Quando accedi nuovamente, il dispositivo assumerà un nuovo nome.</string> + <string name="direct_only">Solo diretta</string> <string name="discard">Ignora modifiche</string> <string name="discard_changes">Ignorare le modifiche?</string> <string name="disconnect">Disconnetti</string> 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 5ddca0bb33..77b5b03341 100644 --- a/android/lib/resource/src/main/res/values-ja/strings.xml +++ b/android/lib/resource/src/main/res/values-ja/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">ポートを設定</string> <string name="custom_port_dialog_valid_ranges">有効な範囲: %1$s</string> <string name="custom_tunnel_host_resolution_error">カスタムトンネルのホストを解決できません。設定を変更してみてください。</string> - <string name="daita_info">%1$s (%2$s) は暗号化されたVPNトラフィックのパターンを隠します。これにより、接続が監視されている場合でもアクセス先のウェブサイトを特定するのを極めて困難にします。ネットワークノイズを入念に追加し、すべてのネットワークパケットを同じサイズにすることによってこれを実現しています。</string> + <string name="daita_description_slide_1_second_paragraph">高度なAIを使用することで、(トラフィックが暗号化されている場合でも) デバイスが送受信するデータパケットのトラフィックを分析することは可能です。</string> <string name="daita_relay_subset_warning">この機能はすべてのサーバーでご利用いただけるわけではありません。有効にした後に場所の変更が必要となる可能性があります。</string> <string name="daita_warning">注意: これにより、ネットワークトラフィックの総数が増加します。上限のあるデータプランをご利用の場合にはご注意ください。また、ネットワークの速度とバッテリー使用量にも悪影響を及ぼす可能性があります。</string> <string name="delete">削除</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">これはデバイスに割り当てられる名前です。Mullvadアカウントにログインするデバイスごとに一意の名前が付けられるため、アプリまたはウェブサイトでデバイスを管理する際にデバイスを区別しやすくなります。</string> <string name="device_name_info_second_paragraph">1つのMullvadアカウントで最大5台のデバイスにログインできます。</string> <string name="device_name_info_third_paragraph">ログアウトすると、デバイスとデバイス名が削除されます。もう一度ログインすると、デバイスに新しい名前が付けられます。</string> + <string name="direct_only">ダイレクトのみ</string> <string name="discard">破棄</string> <string name="discard_changes">変更内容を破棄しますか?</string> <string name="disconnect">接続解除</string> 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 a6cae7374f..a5b18b3030 100644 --- a/android/lib/resource/src/main/res/values-ko/strings.xml +++ b/android/lib/resource/src/main/res/values-ko/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">포트 설정</string> <string name="custom_port_dialog_valid_ranges">유효한 범위: %1$s</string> <string name="custom_tunnel_host_resolution_error">사용자 지정 터널의 호스트를 확인할 수 없습니다. 설정을 변경해 보세요.</string> - <string name="daita_info">%1$s(%2$s)는 암호화된 VPN 트래픽의 패턴을 숨깁니다. 누군가 사용자의 연결을 모니터링하고 있다면 이 기능은 사용자가 어느 웹사이트를 방문하고 있는지, 누구와 대화하고 있는지 아주 식별하기 어렵게 만듭니다. 이는 정교하게 네트워크 노이즈를 추가하고 모든 네트워크 패킷을 동일한 사이즈로 만드는 방식으로 달성됩니다.</string> + <string name="daita_description_slide_1_second_paragraph">정교한 AI를 사용하면, 트래픽이 암호화된 경우라도 장치를 드나드는 데이터 패킷의 트래픽을 분석하는 것이 가능합니다.</string> <string name="daita_relay_subset_warning">이 기능은 일부 서버에서 이용 가능하지 않습니다. 활성화 후 위치를 변경해야 할 수도 있습니다.</string> <string name="daita_warning">주의: 이러한 방식은 총 네트워크 트래픽을 증가시키므로, 제한된 데이터 플랜 사용 시에는 유의하시기 바랍니다. 또한 네트워크 속도와 배터리 사용에 부정적인 영향을 미칠 수 있습니다.</string> <string name="delete">삭제</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">이것은 장치에 할당된 이름입니다. Mullvad 계정에 로그인된 각 장치에는 앱이나 웹사이트에서 장치를 관리할 때 장치를 보다 쉽게 식별할 수 있는 고유한 이름이 부여됩니다.</string> <string name="device_name_info_second_paragraph">최대 5개의 장치에서 하나의 Mullvad 계정에 로그인할 수 있습니다.</string> <string name="device_name_info_third_paragraph">로그아웃하면 해당 장치와 장치 이름이 제거됩니다. 다시 로그인하면 장치에 새 이름이 부여됩니다.</string> + <string name="direct_only">직접 전용</string> <string name="discard">취소</string> <string name="discard_changes">변경 사항을 취소하시겠습니까?</string> <string name="disconnect">연결 끊기</string> 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 bfc50f72ff..864f97b06c 100644 --- a/android/lib/resource/src/main/res/values-my/strings.xml +++ b/android/lib/resource/src/main/res/values-my/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">ပေါ့တ် သတ်မှတ်ရန်</string> <string name="custom_port_dialog_valid_ranges">အကျုံးဝင်သည့် အပိုင်းအခြား- %1$s</string> <string name="custom_tunnel_host_resolution_error">စိတ်ကြိုက်ပြုလုပ်ထားသည့် Tunnel ၏ Host ကို ဖြေရှင်း၍ မရနိုင်ပါ။ သင့်ဆက်တင်ကို ပြောင်းကြည့်ပါ။</string> - <string name="daita_info">%1$s (%2$s) က သင်၏ ကုဒ်ပြောင်းဝှက်ထားသော VPN ဝင်ရောက်ကြည့်ရှုမှုထဲရှိ ပက်တန်များကို ဝှက်ပေးသည်။ သင့်ချိတ်ဆက်မှုကို တစ်စုံတစ်ယောက်က စောင့်ကြည့်နေပါက မည်သည့်ဝက်ဘ်ဆိုက်များထဲ သင်ဝင်ရောက်နေသည်ကို ၎င်းတို့ ခွဲခြားဖော်ထုတ်ဖို့ရာ ပို၍ သိသိသာသာ ခက်ခဲသွားစေပါသည်။ သည် ကွန်ရက် အနှောင့်အယှက်လျှပ်လိုင်းကို ဂရုတစိုက် ထည့်ပြီး ကွန်ရက် ပက်ကက်အားလုံးကို အရွယ်အစားတူညီအောင် ပြုလုပ်ခြင်းအားဖြင့် ပိုမိုခက်ခဲအောင် ဆောင်ရွက်ခြင်းဖြစ်သည်။</string> + <string name="daita_description_slide_1_second_paragraph">ခေတ်မီဆန်းပြားသော AI ကိုအသုံးပြုခြင်းဖြင့် (အသွားအလာကို ကုဒ်ဝှက်ထားသည့်တိုင်) သင့်စက်၏အဝင်အထွက်ဒေတာပက်ကတ်များ၏ အသွားအလာကို ပိုင်းခြားစိတ်ဖြာနိုင်သည်။</string> <string name="daita_relay_subset_warning">ဤလုပ်ဆောင်ချက်ကို ဆာဗာအားလုံးတွင် မရရှိနိုင်ပါ။ ဖွင့်ပြီးနောက် တည်နေရာ ပြောင်းလဲရန် လိုအပ်နိုင်သည်။</string> <string name="daita_warning">သတိပြုရန်- ၎င်းသည် သင်၏ စုစုပေါင်းကွန်ရက်သုံးစွဲမှုကို တိုးစေသောကြောင့် သင့်ဒေတာပလန်မှာ အကန့်အသတ်ဖြင့်သာ ရှိပါက သတိထားပါ။ ၎င်းသည် သင်၏ကွန်ရက်အမြန်နှုန်းနှင့် ဘက်ထရီအသုံးပြုမှုကိုလည်း ထိခိုက်စေနိုင်သည်။</string> <string name="delete">ဖျက်ရန်</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">ဤအမည်မှာ စက်အတွက် သတ်မှတ်ထားသော အမည် ဖြစ်ပါသည်။ Mullvad အကောင့်တစ်ခုတွင် ဝင်ရောက်ထားသည့် စက်တစ်ခုစီသည် တစ်မူထူးခြားသည့် အမည်တစ်ခု ရရှိမည်ဖြစ်ပြီး အက်ပ် သို့မဟုတ် ဝက်ဘ်ဆိုက်ပေါ်တွင် သင့်စက်များကို စီမံသည့်အခါ သင်အနေဖြင့် ကွဲကွဲပြားပြားသိရှိအောင် ကူညီပေးပါသည်။</string> <string name="device_name_info_second_paragraph">Mullvad အကောင့်တစ်ခုတွင် စက် 5 ခုအထိ ဝင်ရောက်ထားနိုင်ပါသည်။</string> <string name="device_name_info_third_paragraph">ထွက်လိုက်ပါက စက်နှင့် စက်အမည်ကို ဖယ်ရှားပါသည်။ နောက်တစ်ကြိမ် ပြန်ဝင်ရောက်သည့်အခါ စက်သည် အမည်သစ်တစ်ခု ရရှိပါမည်။</string> + <string name="direct_only">တိုက်ရိုက်သာ</string> <string name="discard">ပယ်ဖျက်မည်</string> <string name="discard_changes">အပြောင်းအလဲများကို ပယ်ဖျက်မည်လား။</string> <string name="disconnect">ချိတ်ဆက်မှုဖြုတ်ရန်</string> 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 3a543484d0..166fd406d5 100644 --- a/android/lib/resource/src/main/res/values-nb/strings.xml +++ b/android/lib/resource/src/main/res/values-nb/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Konfigurer port</string> <string name="custom_port_dialog_valid_ranges">Gyldige verdiområder: %1$s</string> <string name="custom_tunnel_host_resolution_error">Kunne ikke løse vert for egendefinert tunnel. Forsøk å endre innstillingene dine.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Denne funksjonen er ikke tilgjengelig på alle servere. Det kan hende du må endre plassering etter aktivering.</string> <string name="daita_warning">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.</string> <string name="delete">Slett</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Du kan ha opptil fem enheter logget inn på samme Mullvad-konto.</string> <string name="device_name_info_third_paragraph">Hvis du logger ut, vil enheten og enhetsnavnet bli fjernet. Når du logger inn igjen, vil enheten få et nytt navn.</string> + <string name="direct_only">Kun direkte</string> <string name="discard">Forkast</string> <string name="discard_changes">Forkaste endringer?</string> <string name="disconnect">Koble fra</string> 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 983c77ae06..f98ee8b11f 100644 --- a/android/lib/resource/src/main/res/values-nl/strings.xml +++ b/android/lib/resource/src/main/res/values-nl/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Poort instellen</string> <string name="custom_port_dialog_valid_ranges">Geldige bereiken: %1$s</string> <string name="custom_tunnel_host_resolution_error">Kan host van aangepaste tunnel niet omzetten. Probeer uw instellingen te wijzigen.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Deze functie is niet op alle servers beschikbaar. Mogelijk moet u na het inschakelen van locatie veranderen.</string> <string name="daita_warning">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.</string> <string name="delete">Verwijderen</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">U kunt maximaal 5 apparaten aangemeld hebben op één Mullvad-account.</string> <string name="device_name_info_third_paragraph">Als u zich afmeldt, worden het apparaat en de apparaatnaam verwijderd. Wanneer u zich weer aanmeldt, krijgt het apparaat een nieuwe naam.</string> + <string name="direct_only">Alleen rechtstreeks</string> <string name="discard">Verwerpen</string> <string name="discard_changes">Wijzigingen verwerpen?</string> <string name="disconnect">Verbinding verbreken</string> 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 e265669623..196fbd53c6 100644 --- a/android/lib/resource/src/main/res/values-pl/strings.xml +++ b/android/lib/resource/src/main/res/values-pl/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Ustaw port</string> <string name="custom_port_dialog_valid_ranges">Prawidłowe zakresy: %1$s</string> <string name="custom_tunnel_host_resolution_error">Nie można rozpoznać hosta tunelu niestandardowego. Spróbuj zmienić ustawienia.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Funkcja niedostępna na wszystkich serwerach. Po włączeniu może być konieczna zmiana lokalizacji.</string> <string name="daita_warning">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.</string> <string name="delete">Usuń</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Na jednym koncie Mullvad może być zalogowanych maksymalnie 5 urządzeń.</string> <string name="device_name_info_third_paragraph">Przy wylogowaniu urządzenie i jego nazwa zostają usunięte. Po ponownym zalogowaniu urządzenie otrzymuje nową nazwę.</string> + <string name="direct_only">Tylko Direct</string> <string name="discard">Odrzuć</string> <string name="discard_changes">Odrzucić zmiany?</string> <string name="disconnect">Rozłącz</string> 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 eb679214ed..331e409004 100644 --- a/android/lib/resource/src/main/res/values-pt/strings.xml +++ b/android/lib/resource/src/main/res/values-pt/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Definir porta</string> <string name="custom_port_dialog_valid_ranges">Intervalos válidos: %1$s</string> <string name="custom_tunnel_host_resolution_error">Não foi possível resolver o anfitrião do túnel personalizado. Experimente alterar as suas definições.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Esta funcionalidade não está disponível em todos os servidores. Poderá ser necessário alterar a localização após a ativação.</string> <string name="daita_warning">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.</string> <string name="delete">Eliminar</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Pode ter até 5 dispositivos com sessão iniciada numa só conta Mullvad.</string> <string name="device_name_info_third_paragraph">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.</string> + <string name="direct_only">Apenas direta</string> <string name="discard">Descartar</string> <string name="discard_changes">Descartar alterações?</string> <string name="disconnect">Desligar</string> 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 bc198f1a0d..763531ac6b 100644 --- a/android/lib/resource/src/main/res/values-ru/strings.xml +++ b/android/lib/resource/src/main/res/values-ru/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Установить порт</string> <string name="custom_port_dialog_valid_ranges">Допустимые диапазоны: %1$s</string> <string name="custom_tunnel_host_resolution_error">Не удалось преобразовать имя узла пользовательского туннеля. Попробуйте изменить настройки.</string> - <string name="daita_info">%1$s (%2$s) скрывает характерные признаки в зашифрованном VPN-трафике. Если кто-то отслеживает ваше подключение, ему будет значительно сложнее определить, какие веб-сайты вы посещаете. Для этого в трафик добавляется сетевой шум, а все пакеты приводятся к одному размеру.</string> + <string name="daita_description_slide_1_second_paragraph">С помощью сложных ИИ-алгоритмов можно анализировать входящий и исходящий трафик пакетов данных вашего устройства (даже если трафик зашифрован).</string> <string name="daita_relay_subset_warning">Эта функция доступна не на всех серверах. Возможно, после ее включения придется сменить местоположение.</string> <string name="daita_warning">Внимание! При использовании этой функции общий сетевой трафик увеличивается. Имейте это в виду, если у вас тарифный план с ограничением по трафику. Также может снизиться скорость сети и повыситься расход заряда аккумулятора.</string> <string name="delete">Удалить</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">Это имя, присвоенное устройству. Каждое устройство, подключенное к учетной записи Mullvad, получает уникальное имя, которое помогает вам идентифицировать его при управлении устройствами в приложении или на сайте.</string> <string name="device_name_info_second_paragraph">К одной учетной записи Mullvad можно подключить до 5 устройств.</string> <string name="device_name_info_third_paragraph">Если вы выйдете, устройство и его имя удалятся. При повторном входе устройство получит новое имя.</string> + <string name="direct_only">Только напрямую</string> <string name="discard">Сбросить</string> <string name="discard_changes">Сбросить изменения?</string> <string name="disconnect">Отключить</string> 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 812b4d58ad..3bc57fe717 100644 --- a/android/lib/resource/src/main/res/values-sv/strings.xml +++ b/android/lib/resource/src/main/res/values-sv/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Ställ in port</string> <string name="custom_port_dialog_valid_ranges">Giltiga intervall: %1$s</string> <string name="custom_tunnel_host_resolution_error">Det går inte att lösa värd för anpassad tunnel. Försök att ändra inställningarna.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> <string name="daita_relay_subset_warning">Det här funktionen är inte tillgänglig på alla servrar. Du kanske måste ändra plats efter aktivering.</string> <string name="daita_warning">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.</string> <string name="delete">Ta bort</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Upp till fem enheter kan vara inloggade på ett Mullvad-konto.</string> <string name="device_name_info_third_paragraph">Om du loggar ut tas enheten och enhetsnamnet bort. När du loggar in igen får enheten ett nytt namn.</string> + <string name="direct_only">Endast direkt</string> <string name="discard">Ignorera</string> <string name="discard_changes">Ignorera ändringarna?</string> <string name="disconnect">Koppla från</string> 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 1e4a912f8f..3b740a9667 100644 --- a/android/lib/resource/src/main/res/values-th/strings.xml +++ b/android/lib/resource/src/main/res/values-th/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">ตั้งค่าพอร์ต</string> <string name="custom_port_dialog_valid_ranges">ช่วงที่ใช้ได้: %1$s</string> <string name="custom_tunnel_host_resolution_error">ไม่พบโฮสต์ของช่องทางแบบกำหนดเอง กรุณาลองเปลี่ยนการตั้งค่าของคุณ</string> - <string name="daita_info">%1$s (%2$s) ซ่อนรูปแบบในการรับส่งข้อมูล VPN ที่เข้ารหัสของคุณ หากมีใครกำลังเฝ้าดูการเชื่อมต่อของคุณอยู่ สิ่งนี้จะทำให้การระบุเว็บไซต์ที่คุณกำลังเยี่ยมชมยากขึ้นอย่างมาก ซึ่งทำได้โดยการเพิ่มสัญญาณรบกวนเครือข่ายอย่างระมัดระวัง และทำให้แพ็กเก็ตเครือข่ายทั้งหมดมีขนาดเท่ากันหมด</string> + <string name="daita_description_slide_1_second_paragraph">โดยการใช้ AI ที่ซับซ้อน จึงเป็นไปได้ที่จะวิเคราะห์ปริมาณการรับส่งข้อมูล ที่เข้าออกอุปกรณ์ของคุณได้ (แม้ว่าการรับส่งข้อมูลจะเข้ารหัสไว้ก็ตาม)</string> <string name="daita_relay_subset_warning">คุณลักษณะนี้ไม่พร้อมใช้งานบนทุกเซิร์ฟเวอร์ คุณอาจต้องต้องเปลี่ยนตำแหน่งที่ตั้ง หลังจากเปิดใช้งาน</string> <string name="daita_warning">โปรดทราบ: โปรดใช้ความระมัดระวัง หากคุณมีแผนข้อมูลที่มีปริมาณจำกัด เนื่องจากการดำเนินการนี้ จะเพิ่มการรับส่งข้อมูลโดยรวมของคุณขึ้น การดำเนินการนี้ อาจส่งผลเชิงลบต่อความเร็วเครือข่าย และการใช้งานแบตเตอรี่ของคุณได้</string> <string name="delete">ลบ</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">นี่เป็นชื่อที่มอบหมายให้กับอุปกรณ์ อุปกรณ์แต่ละเครื่องที่ลงชื่อเข้าใช้บนบัญชี Mullvad จะได้รับชื่อเฉพาะ ที่จะช่วยคุณระบุอุปกรณ์ ในขณะที่คุณจัดการอุปกรณ์ของคุณในแอปหรือบนเว็บไซต์</string> <string name="device_name_info_second_paragraph">คุณสามารถลงชื่อเข้าใช้อุปกรณ์ได้สูงสุด 5 เครื่อง กับบัญชี Mullvad หนึ่งบัญชี</string> <string name="device_name_info_third_paragraph">หากคุณออกจากระบบ อุปกรณ์และชื่ออุปกรณ์จะถูกลบออก และเมื่อคุณลงชื่อเข้าใช้อีกครั้ง อุปกรณ์จะได้รับชื่อใหม่</string> + <string name="direct_only">โดยตรงเท่านั้น</string> <string name="discard">ละทิ้ง</string> <string name="discard_changes">ละทิ้งการเปลี่ยนแปลงหรือไม่</string> <string name="disconnect">ตัดการเชื่อมต่อ</string> 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 52657c107c..da0cd54f41 100644 --- a/android/lib/resource/src/main/res/values-tr/strings.xml +++ b/android/lib/resource/src/main/res/values-tr/strings.xml @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">Portu ayarla</string> <string name="custom_port_dialog_valid_ranges">Geçerli aralıklar: %1$s</string> <string name="custom_tunnel_host_resolution_error">Özel tünel ana bilgisayarı çözülemedi. Ayarlarınızı değiştirmeyi deneyin.</string> - <string name="daita_info">%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.</string> + <string name="daita_description_slide_1_second_paragraph">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.</string> <string name="daita_relay_subset_warning">Bu özellik tüm sunucularda mevcut değildir. Etkinleştirdikten sonra konumu değiştirmeniz gerekebilir.</string> <string name="daita_warning">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.</string> <string name="delete">Sil</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">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.</string> <string name="device_name_info_second_paragraph">Bir Mullvad hesabı ile en fazla 5 cihazda oturum açabilirsiniz.</string> <string name="device_name_info_third_paragraph">Oturumu kapatırsanız cihaz ve cihaz adı kaldırılır. Tekrar oturum açtığınızda cihaza yeni bir ad verilir.</string> + <string name="direct_only">Yalnızca doğrudan</string> <string name="discard">İptal et</string> <string name="discard_changes">Değişiklikler iptal edilsin mi?</string> <string name="disconnect">Bağlantıyı Kes</string> 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 94af479d52..0123aa689d 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 @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">设置端口</string> <string name="custom_port_dialog_valid_ranges">有效范围:%1$s</string> <string name="custom_tunnel_host_resolution_error">无法解析自定义隧道的主机。请尝试更改您的设置。</string> - <string name="daita_info">%1$s (%2$s) 会隐藏您的加密 VPN 流量中的模式。如果有人在监视您的连接,这会大大提高他们识别您所访问的网站的难度。实现方法是精心添加网络噪声并将所有网络数据包处理为相同的大小。</string> + <string name="daita_description_slide_1_second_paragraph">通过使用复杂 AI,可以分析传入和传出您的设备的数据包流量(即使流量已加密)。</string> <string name="daita_relay_subset_warning">此功能并非在所有服务器上都可用。启用后,您可能需要更改位置。</string> <string name="daita_warning">注意:这样会增加您的网络总流量,因此,如果您的数据套餐流量有限,请谨慎使用。这种方式还会对网速和电池用量产生负面影响。</string> <string name="delete">删除</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">这是为设备分配的名称。每台登录 Mulvad 帐户的设备都会获得一个唯一名称,有助于您在应用或网站上管理设备时识别各个设备。</string> <string name="device_name_info_second_paragraph">一个 Mulvad 帐户最多可以登录 5 台设备。</string> <string name="device_name_info_third_paragraph">如果您退出登录,设备和设备名称将被移除。当您再次登录时,设备将获得新名称。</string> + <string name="direct_only">仅直连</string> <string name="discard">舍弃</string> <string name="discard_changes">舍弃更改?</string> <string name="disconnect">断开连接</string> 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 e200ffe2f2..1ab25df468 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 @@ -93,7 +93,7 @@ <string name="custom_port_dialog_submit">設定連接埠</string> <string name="custom_port_dialog_valid_ranges">有效範圍:%1$s</string> <string name="custom_tunnel_host_resolution_error">無法解析自訂通道的主機。請嘗試變更您的設定。</string> - <string name="daita_info">%1$s (%2$s) 會在您的加密 VPN 流量中隱藏模式。如果有人正在監控您的連線,這會大大增加他們的難度,讓他們難以辨識您正在瀏覽哪些網站。這個方法會仔細加入網路噪音,讓所有網路資料封包大小皆相同,以達到此目的。</string> + <string name="daita_description_slide_1_second_paragraph">可透過精密的 AI 分析進出裝置的資料封包流量 (即使流量已加密)。</string> <string name="daita_relay_subset_warning">此功能尚未在所有伺服器上開放。啟用後,您可能需要變更位置。</string> <string name="daita_warning">注意:這會增加您的網路總流量。如果您方案中的流量有限,請謹慎使用。這還會對您的網路速度和電池使用情況產生負面影響。</string> <string name="delete">刪除</string> @@ -109,6 +109,7 @@ <string name="device_name_info_first_paragraph">這是系統指派給裝置的名稱。每台登入 Mulvad 帳戶的裝置都會獲得一個獨特名稱,有助於您在應用程式或網站上管理裝置時識別各台裝置。</string> <string name="device_name_info_second_paragraph">一個 Mulvad 帳戶最多可以登入 5 台裝置。</string> <string name="device_name_info_third_paragraph">如果您登出,則系統會移除裝置和裝置名稱。當您再次登入時,裝置則會獲得新名稱。</string> + <string name="direct_only">僅直接連線</string> <string name="discard">捨棄</string> <string name="discard_changes">要捨棄變更嗎?</string> <string name="disconnect">中斷連線</string> diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index 9877098d7c..cd71db65b3 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -359,7 +359,7 @@ <string name="failed_to_set_current_unknown_error">Failed to set to current - Unknown reason</string> <string name="location_was_removed_from_list">%s was removed from \"%s\"</string> <string name="create_custom_list_message">\"%s\" was created</string> - <string name="daita_info">%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.</string> + <string name="daita_info">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.</string> <string name="daita_warning">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.</string> <string name="setting_chip">Setting: %s</string> <string name="enable_anyway">Enable anyway</string> @@ -410,4 +410,16 @@ <string name="search_results">Search results</string> <string name="filters">Filters:</string> <string name="search_query_empty">Type at least 2 characters to start searching.</string> + <string name="daita_description_slide_1_first_paragraph">DAITA (Defense against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic.</string> + <string name="daita_description_slide_1_second_paragraph">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).</string> + <string name="daita_description_slide_1_third_paragraph">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.</string> + <string name="daita_description_slide_2_first_paragraph">DAITA does this by carefully adding network noise and making all network packets the same size.</string> + <string name="daita_description_slide_2_second_paragraph">Not all our servers are DAITA-enabled. Therefore, we use multihop automatically to enable DAITA with any server.</string> + <string name="daita_description_slide_2_third_paragraph">Attention: Be cautious if you have a limited data plan as this feature will increase your network traffic.</string> + <string name="direct_only">Direct only</string> + <string name="enable_direct_only">Enable \"Direct only\"</string> + <string name="direct_only_description">Not all our servers are DAITA-enabled. In order to use the internet, you might have to select a new location after enabling.</string> + <string name="multihop_entry_disabled_description">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.</string> + <string name="open_daita_settings">Open DAITA settings</string> + <string name="search">Search</string> </resources> diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot index 6bdcd42b51..8116274b5f 100644 --- a/desktop/packages/mullvad-vpn/locales/messages.pot +++ b/desktop/packages/mullvad-vpn/locales/messages.pot @@ -2221,9 +2221,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 "" @@ -2314,6 +2311,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 "" @@ -2341,6 +2341,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 "" @@ -2389,6 +2392,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 "" @@ -2440,6 +2449,9 @@ msgstr "" msgid "Edit name" msgstr "" +msgid "Enable \"Direct only\"" +msgstr "" + msgid "Enable method" msgstr "" @@ -2482,6 +2494,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 "" @@ -2539,9 +2554,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 "" @@ -2602,6 +2626,9 @@ msgstr "" msgid "Reset to default" msgstr "" +msgid "Search" +msgstr "" + msgid "Search results" msgstr "" @@ -2647,6 +2674,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 "" |
