summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-12-02 10:46:36 +0100
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-12-02 11:05:06 +0100
commite0ef5463c2087f073fbf86a347903644aa4543ed (patch)
tree0308fdc27a7800608898ca00b96c959b2b87f81f /android
parent64a5704fd0dc57cc73669251da43d4285fa42e92 (diff)
downloadmullvadvpn-e0ef5463c2087f073fbf86a347903644aa4543ed.tar.xz
mullvadvpn-e0ef5463c2087f073fbf86a347903644aa4543ed.zip
Implement support for daita with multihop
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/constant/ContentType.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaDirectOnlyConfirmationDialog.kt (renamed from android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt)24
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaDirectOnlyInfoDialog.kt (renamed from android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/DaitaInfoDialog.kt)15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SelectLocationsUiStatePreviewParameterProvider.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/SettingsUiStatePreviewParameterProvider.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DaitaScreen.kt204
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt23
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt33
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationList.kt42
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/location/SelectLocationScreen.kt102
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/DaitaUiState.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationListUiState.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SettingsUiState.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt11
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt38
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModel.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt29
-rw-r--r--android/app/src/main/res/drawable/daita_illustration_1.xml342
-rw-r--r--android/app/src/main/res/drawable/daita_illustration_2.xml402
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt18
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt7
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DaitaSettings.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt9
-rw-r--r--android/lib/resource/src/main/res/values/strings.xml14
38 files changed, 1276 insertions, 167 deletions
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/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/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>