diff options
12 files changed, 197 insertions, 71 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingsScreenTest.kt index 1aaf119773..340b903886 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingsScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingsScreenTest.kt @@ -12,6 +12,8 @@ import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify import io.mockk.verifyAll +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import net.mullvad.mullvadvpn.compose.state.AdvancedSettingsUiState import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem import net.mullvad.mullvadvpn.viewmodel.StagedDns @@ -32,7 +34,10 @@ class AdvancedSettingsScreenTest { fun testDefaultState() { // Arrange composeTestRule.setContent { - AdvancedSettingScreen(uiState = AdvancedSettingsUiState.DefaultUiState()) + AdvancedSettingScreen( + uiState = AdvancedSettingsUiState.DefaultUiState(), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() + ) } // Assert @@ -51,7 +56,8 @@ class AdvancedSettingsScreenTest { // Arrange composeTestRule.setContent { AdvancedSettingScreen( - uiState = AdvancedSettingsUiState.DefaultUiState(mtu = VALID_DUMMY_MTU_VALUE) + uiState = AdvancedSettingsUiState.DefaultUiState(mtu = VALID_DUMMY_MTU_VALUE), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -67,7 +73,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.DefaultUiState(), - onMtuCellClick = mockedClickHandler + onMtuCellClick = mockedClickHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -84,7 +91,8 @@ class AdvancedSettingsScreenTest { // Arrange composeTestRule.setContent { AdvancedSettingScreen( - uiState = AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = EMPTY_STRING) + uiState = AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = EMPTY_STRING), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -99,7 +107,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = - AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = VALID_DUMMY_MTU_VALUE) + AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = VALID_DUMMY_MTU_VALUE), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -115,7 +124,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = EMPTY_STRING), - onMtuInputChange = mockedInputHandler + onMtuInputChange = mockedInputHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -135,7 +145,8 @@ class AdvancedSettingsScreenTest { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = VALID_DUMMY_MTU_VALUE), - onSaveMtuClick = mockedSubmitHandler + onSaveMtuClick = mockedSubmitHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -153,7 +164,10 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = - AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = INVALID_DUMMY_MTU_VALUE) + AdvancedSettingsUiState.MtuDialogUiState( + mtuEditValue = INVALID_DUMMY_MTU_VALUE + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -169,7 +183,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = EMPTY_STRING), - onRestoreMtuClick = mockedClickHandler + onRestoreMtuClick = mockedClickHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -188,7 +203,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.MtuDialogUiState(mtuEditValue = EMPTY_STRING), - onCancelMtuDialogClicked = mockedClickHandler + onCancelMtuDialogClicked = mockedClickHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -207,7 +223,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.DefaultUiState(), - onSplitTunnelingNavigationClick = mockedClickHandler + onSplitTunnelingNavigationClick = mockedClickHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -232,9 +249,10 @@ class AdvancedSettingsScreenTest { listOf( CustomDnsItem(address = DUMMY_DNS_ADDRESS, false), CustomDnsItem(address = DUMMY_DNS_ADDRESS_2, false), - CustomDnsItem(address = DUMMY_DNS_ADDRESS_3, false) - ) - ) + CustomDnsItem(address = DUMMY_DNS_ADDRESS_3, false), + ), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -256,8 +274,9 @@ class AdvancedSettingsScreenTest { uiState = AdvancedSettingsUiState.DefaultUiState( isCustomDnsEnabled = false, - customDnsItems = listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, false)) - ) + customDnsItems = listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, false)), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -277,8 +296,9 @@ class AdvancedSettingsScreenTest { isCustomDnsEnabled = true, isAllowLanEnabled = true, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)) - ) + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -297,8 +317,9 @@ class AdvancedSettingsScreenTest { isCustomDnsEnabled = true, isAllowLanEnabled = false, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)) - ) + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -317,8 +338,9 @@ class AdvancedSettingsScreenTest { isCustomDnsEnabled = true, isAllowLanEnabled = true, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)) - ) + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -337,8 +359,9 @@ class AdvancedSettingsScreenTest { isCustomDnsEnabled = true, isAllowLanEnabled = false, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)) - ) + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -356,7 +379,8 @@ class AdvancedSettingsScreenTest { composeTestRule.setContent { AdvancedSettingScreen( uiState = AdvancedSettingsUiState.DefaultUiState(isCustomDnsEnabled = true), - onDnsClick = mockedClickHandler + onDnsClick = mockedClickHandler, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -377,9 +401,10 @@ class AdvancedSettingsScreenTest { AdvancedSettingsUiState.DnsDialogUiState( stagedDns = StagedDns.NewDns( - item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false) - ) - ) + item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false), + ), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -398,9 +423,10 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.EditDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false), - index = 0 - ) - ) + index = 0, + ), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -419,10 +445,11 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.NewDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = true), - validationResult = StagedDns.ValidationResult.Success + validationResult = StagedDns.ValidationResult.Success, ), - isAllowLanEnabled = false - ) + isAllowLanEnabled = false, + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -441,10 +468,11 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.NewDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = true), - validationResult = StagedDns.ValidationResult.Success + validationResult = StagedDns.ValidationResult.Success, ), - isAllowLanEnabled = true - ) + isAllowLanEnabled = true, + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -463,10 +491,11 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.NewDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false), - validationResult = StagedDns.ValidationResult.Success + validationResult = StagedDns.ValidationResult.Success, ), - isAllowLanEnabled = true - ) + isAllowLanEnabled = true, + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -485,10 +514,11 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.NewDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false), - validationResult = StagedDns.ValidationResult.Success + validationResult = StagedDns.ValidationResult.Success, ), - isAllowLanEnabled = false - ) + isAllowLanEnabled = false, + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -507,9 +537,10 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.NewDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false), - validationResult = StagedDns.ValidationResult.InvalidAddress - ) - ) + validationResult = StagedDns.ValidationResult.InvalidAddress, + ), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -528,9 +559,10 @@ class AdvancedSettingsScreenTest { stagedDns = StagedDns.NewDns( item = CustomDnsItem(DUMMY_DNS_ADDRESS, isLocal = false), - validationResult = StagedDns.ValidationResult.DuplicateAddress - ) - ) + validationResult = StagedDns.ValidationResult.DuplicateAddress, + ), + ), + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ContentBlockersInfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ContentBlockersInfoDialog.kt index 8dde91d091..29a57ed331 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ContentBlockersInfoDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ContentBlockersInfoDialog.kt @@ -13,7 +13,13 @@ fun ContentBlockersInfoDialog(onDismiss: () -> Unit) { appendLine(stringResource(id = R.string.dns_content_blockers_info)) append(stringResource(id = R.string.dns_content_blockers_warning)) }, - additionalInfo = textResource(id = R.string.dns_content_blockers_custom_dns_warning), + additionalInfo = + buildString { + appendLine(textResource(id = R.string.dns_content_blockers_custom_dns_warning)) + appendLine( + stringResource(id = R.string.settings_changes_effect_warning_content_blocker) + ) + }, onDismiss = onDismiss ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomDnsInfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomDnsInfoDialog.kt new file mode 100644 index 0000000000..ce3a325780 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomDnsInfoDialog.kt @@ -0,0 +1,13 @@ +package net.mullvad.mullvadvpn.compose.dialog + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import net.mullvad.mullvadvpn.R + +@Composable +fun CustomDnsInfoDialog(onDismiss: () -> Unit) { + InfoDialog( + message = stringResource(id = R.string.settings_changes_effect_warning_content_blocker), + onDismiss = onDismiss + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt index ddc60ad566..b7a6e4ba0d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt @@ -37,11 +37,7 @@ private fun PreviewChangelogDialogWithTwoLongItems() { "The purpose of this specific sample text is to visualize a long text that will " + "result in multiple lines in the changelog dialog." - InfoDialog( - message = longPreviewText, - additionalInfo = longPreviewText, - onDismiss = {}, - ) + InfoDialog(message = longPreviewText, additionalInfo = longPreviewText, onDismiss = {}) } @Composable @@ -55,13 +51,13 @@ fun InfoDialog(message: String, additionalInfo: String? = null, onDismiss: () -> modifier = Modifier.fillMaxWidth().height(iconHeight), painter = painterResource(id = R.drawable.icon_info), contentDescription = "", - tint = MullvadWhite, + tint = MullvadWhite ) }, text = { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(top = verticalSpacing), + modifier = Modifier.padding(top = verticalSpacing) ) { Text( text = message, @@ -91,9 +87,9 @@ fun InfoDialog(message: String, additionalInfo: String? = null, onDismiss: () -> colors = ButtonDefaults.buttonColors( backgroundColor = colorResource(id = R.color.blue), - contentColor = colorResource(id = R.color.white), + contentColor = colorResource(id = R.color.white) ), - onClick = { onDismiss() }, + onClick = { onDismiss() } ) { Text( text = stringResource(R.string.changes_dialog_dismiss_button), @@ -106,6 +102,6 @@ fun InfoDialog(message: String, additionalInfo: String? = null, onDismiss: () -> dismissOnClickOutside = true, dismissOnBackPress = true, ), - backgroundColor = colorResource(id = R.color.darkBlue), + backgroundColor = colorResource(id = R.color.darkBlue) ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt index 92c58f1f77..bfe0088136 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt @@ -1,5 +1,6 @@ package net.mullvad.mullvadvpn.compose.screen +import android.widget.Toast import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -17,6 +18,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -24,6 +26,7 @@ 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 import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -32,6 +35,9 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import me.onebone.toolbar.ScrollStrategy import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState import net.mullvad.mullvadvpn.R @@ -47,6 +53,7 @@ import net.mullvad.mullvadvpn.compose.component.CollapsableAwareToolbarScaffold import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar import net.mullvad.mullvadvpn.compose.dialog.ContentBlockersInfoDialog +import net.mullvad.mullvadvpn.compose.dialog.CustomDnsInfoDialog import net.mullvad.mullvadvpn.compose.dialog.DnsDialog import net.mullvad.mullvadvpn.compose.dialog.MalwareInfoDialog import net.mullvad.mullvadvpn.compose.dialog.MtuDialog @@ -65,7 +72,7 @@ private fun PreviewAdvancedSettings() { AdvancedSettingsUiState.DefaultUiState( mtu = "1337", isCustomDnsEnabled = true, - customDnsItems = listOf(CustomDnsItem("0.0.0.0", false)) + customDnsItems = listOf(CustomDnsItem("0.0.0.0", false)), ), onMtuCellClick = {}, onMtuInputChange = {}, @@ -86,8 +93,10 @@ private fun PreviewAdvancedSettings() { onCancelDnsDialogClick = {}, onContentsBlockersInfoClicked = {}, onMalwareInfoClicked = {}, + onCustomDnsInfoClicked = {}, onDismissInfoClicked = {}, - onBackClick = {} + onBackClick = {}, + toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow() ) } @@ -116,9 +125,11 @@ fun AdvancedSettingScreen( onCancelDnsDialogClick: () -> Unit = {}, onContentsBlockersInfoClicked: () -> Unit = {}, onMalwareInfoClicked: () -> Unit = {}, + onCustomDnsInfoClicked: () -> Unit = {}, onDismissInfoClicked: () -> Unit = {}, onBackClick: () -> Unit = {}, - onStopEvent: () -> Unit = {} + onStopEvent: () -> Unit = {}, + toastMessagesSharedFlow: SharedFlow<String> ) { val cellVerticalSpacing = dimensionResource(id = R.dimen.cell_label_vertical_padding) val cellHorizontalSpacing = dimensionResource(id = R.dimen.cell_left_padding) @@ -146,6 +157,9 @@ fun AdvancedSettingScreen( is AdvancedSettingsUiState.ContentBlockersInfoDialogUiState -> { ContentBlockersInfoDialog(onDismissInfoClicked) } + is AdvancedSettingsUiState.CustomDnsInfoDialogUiState -> { + CustomDnsInfoDialog(onDismissInfoClicked) + } is AdvancedSettingsUiState.MalwareInfoDialogUiState -> { MalwareInfoDialog(onDismissInfoClicked) } @@ -181,8 +195,14 @@ fun AdvancedSettingScreen( modifier = scaffoldModifier, backTitle = stringResource(id = R.string.settings) ) - } + }, ) { + val context = LocalContext.current + LaunchedEffect(Unit) { + toastMessagesSharedFlow.collect { message -> + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + } DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP) { @@ -275,7 +295,7 @@ fun AdvancedSettingScreen( start = cellHorizontalSpacing, top = topPadding, end = cellHorizontalSpacing, - bottom = cellVerticalSpacing, + bottom = cellVerticalSpacing ) ) } @@ -288,7 +308,8 @@ fun AdvancedSettingScreen( title = stringResource(R.string.enable_custom_dns), isToggled = uiState.isCustomDnsEnabled, isEnabled = uiState.contentBlockersOptions.isAnyBlockerEnabled().not(), - onCellClicked = { newValue -> onToggleDnsClick(newValue) } + onCellClicked = { newValue -> onToggleDnsClick(newValue) }, + onInfoClicked = { onCustomDnsInfoClicked() } ) } @@ -316,7 +337,7 @@ fun AdvancedSettingScreen( bodyView = {}, subtitle = null, background = MullvadBlue20, - startPadding = biggerPadding + startPadding = biggerPadding, ) } } @@ -330,7 +351,7 @@ fun AdvancedSettingScreen( start = cellHorizontalSpacing, top = topPadding, end = cellHorizontalSpacing, - bottom = cellVerticalSpacing + bottom = cellVerticalSpacing, ) ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/AdvancedSettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/AdvancedSettingsUiState.kt index 71b608b51b..feaa8e0824 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/AdvancedSettingsUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/AdvancedSettingsUiState.kt @@ -45,6 +45,14 @@ sealed interface AdvancedSettingsUiState { override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions() ) : AdvancedSettingsUiState + data class CustomDnsInfoDialogUiState( + override val mtu: String = "", + override val isCustomDnsEnabled: Boolean = false, + override val isAllowLanEnabled: Boolean = false, + override val customDnsItems: List<CustomDnsItem> = listOf(), + override val contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions() + ) : AdvancedSettingsUiState + data class MalwareInfoDialogUiState( override val mtu: String = "", override val isCustomDnsEnabled: Boolean = false, 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 8427ae0773..1d1f02138e 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 @@ -88,7 +88,13 @@ val uiModule = module { ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG) } viewModel { PrivacyDisclaimerViewModel(get()) } - viewModel { AdvancedSettingsViewModel(repository = get(), inetAddressValidator = get()) } + viewModel { + AdvancedSettingsViewModel( + get(), + get(), + get(), + ) + } } const val APPS_SCOPE = "APPS_SCOPE" diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AdvancedFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AdvancedFragment.kt index 62661b0455..5e9be88387 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AdvancedFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AdvancedFragment.kt @@ -46,10 +46,12 @@ class AdvancedFragment : BaseFragment() { onRemoveDnsClick = vm::onRemoveDnsClick, onCancelDnsDialogClick = vm::onCancelDialogClick, onContentsBlockersInfoClicked = vm::onContentsBlockerInfoClick, + onCustomDnsInfoClicked = vm::onCustomDnsInfoClick, onMalwareInfoClicked = vm::onMalwareInfoClick, onDismissInfoClicked = vm::onDismissInfoClick, onBackClick = { activity?.onBackPressed() }, - onStopEvent = vm::onStopEvent + onStopEvent = vm::onStopEvent, + toastMessagesSharedFlow = vm.toastMessages ) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModel.kt index 540ed235ce..89f2df1399 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModel.kt @@ -1,18 +1,22 @@ package net.mullvad.mullvadvpn.viewmodel +import android.content.res.Resources import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import java.net.InetAddress import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.state.AdvancedSettingsUiState import net.mullvad.mullvadvpn.model.DefaultDnsOptions import net.mullvad.mullvadvpn.model.DnsState @@ -24,9 +28,13 @@ import org.apache.commons.validator.routines.InetAddressValidator class AdvancedSettingsViewModel( private val repository: SettingsRepository, private val inetAddressValidator: InetAddressValidator, + private val resources: Resources, private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : ViewModel() { + private val _toastMessages = MutableSharedFlow<String>(extraBufferCapacity = 1) + val toastMessages = _toastMessages.asSharedFlow() + private val dialogState = MutableStateFlow<AdvancedSettingsDialogState>(AdvancedSettingsDialogState.NoDialog) @@ -90,6 +98,10 @@ class AdvancedSettingsViewModel( dialogState.update { AdvancedSettingsDialogState.ContentBlockersInfoDialog } } + fun onCustomDnsInfoClick() { + dialogState.update { AdvancedSettingsDialogState.CustomDnsInfoDialog } + } + fun onMalwareInfoClick() { dialogState.update { AdvancedSettingsDialogState.MalwareInfoDialog } } @@ -192,36 +204,44 @@ class AdvancedSettingsViewModel( hideDialog() } - fun onToggleDnsClick(isEnabled: Boolean) = updateCustomDnsState(isEnabled) + fun onToggleDnsClick(isEnabled: Boolean) { + updateCustomDnsState(isEnabled) + showApplySettingChangesWarningToast() + } fun onToggleBlockAds(isEnabled: Boolean) { updateDefaultDnsOptionsViaRepository( vmState.value.contentBlockersOptions.copy(blockAds = isEnabled) ) + showApplySettingChangesWarningToast() } fun onToggleBlockTrackers(isEnabled: Boolean) { updateDefaultDnsOptionsViaRepository( vmState.value.contentBlockersOptions.copy(blockTrackers = isEnabled) ) + showApplySettingChangesWarningToast() } fun onToggleBlockMalware(isEnabled: Boolean) { updateDefaultDnsOptionsViaRepository( vmState.value.contentBlockersOptions.copy(blockMalware = isEnabled) ) + showApplySettingChangesWarningToast() } fun onToggleBlockAdultContent(isEnabled: Boolean) { updateDefaultDnsOptionsViaRepository( vmState.value.contentBlockersOptions.copy(blockAdultContent = isEnabled) ) + showApplySettingChangesWarningToast() } fun onToggleBlockGambling(isEnabled: Boolean) { updateDefaultDnsOptionsViaRepository( vmState.value.contentBlockersOptions.copy(blockGambling = isEnabled) ) + showApplySettingChangesWarningToast() } fun onRemoveDnsClick() = @@ -314,6 +334,10 @@ class AdvancedSettingsViewModel( } } + private fun showApplySettingChangesWarningToast() { + _toastMessages.tryEmit(resources.getString(R.string.settings_changes_effect_warning_short)) + } + companion object { private const val EMPTY_STRING = "" } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModelState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModelState.kt index 109e518ad5..fddcdfb5b7 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModelState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AdvancedSettingsViewModelState.kt @@ -39,6 +39,14 @@ data class AdvancedSettingsViewModelState( customDnsItems = customDnsList, contentBlockersOptions = contentBlockersOptions ) + is AdvancedSettingsDialogState.CustomDnsInfoDialog -> + AdvancedSettingsUiState.CustomDnsInfoDialogUiState( + mtu = mtuValue, + isCustomDnsEnabled = isCustomDnsEnabled, + isAllowLanEnabled = isAllowLanEnabled, + customDnsItems = customDnsList, + contentBlockersOptions = contentBlockersOptions + ) is AdvancedSettingsDialogState.MalwareInfoDialog -> AdvancedSettingsUiState.MalwareInfoDialogUiState( mtu = mtuValue, @@ -82,6 +90,8 @@ sealed class AdvancedSettingsDialogState { object ContentBlockersInfoDialog : AdvancedSettingsDialogState() + object CustomDnsInfoDialog : AdvancedSettingsDialogState() + object MalwareInfoDialog : AdvancedSettingsDialogState() } diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 6b1bdaac30..b0431964e2 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -184,4 +184,6 @@ <string name="custom_dns_disable_mode_subtitle"> <![CDATA[Disable all <b>DNS content blockers</b> above to activate this setting.]]> </string> + <string name="settings_changes_effect_warning_short">DNS settings might not go into effect immediately</string> + <string name="settings_changes_effect_warning_content_blocker">Changes to DNS related settings might not go into effect immediately due to cached results.</string> </resources> diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 14d1a38dc5..8efe5a86a2 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -1647,6 +1647,9 @@ msgstr "" msgid "Blocking internet (device offline)" msgstr "" +msgid "Changes to DNS related settings might not go into effect immediately due to cached results." +msgstr "" + msgid "Copied Mullvad account number to clipboard" msgstr "" @@ -1659,6 +1662,9 @@ msgstr "" msgid "Custom DNS server addresses %s are invalid" msgstr "" +msgid "DNS settings might not go into effect immediately" +msgstr "" + msgid "Enable" msgstr "" |
