summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/main
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-09-17 11:10:19 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-09-17 11:10:19 +0200
commit13af28efa7f73843327b017234640d2c58bbbeea (patch)
tree241cdbb77b6df7fe2e8dd9abbdbe5369ac2abdbf /android/app/src/main
parenta87b10565db5ccc1b49757e8ae6d65876bc18b18 (diff)
parent00154eb3bce1ed925b11d1cb6ef7b562cb78b524 (diff)
downloadmullvadvpn-13af28efa7f73843327b017234640d2c58bbbeea.tar.xz
mullvadvpn-13af28efa7f73843327b017234640d2c58bbbeea.zip
Merge branch 'refactor-port-dialog-viewmodel-droid-1009'
Diffstat (limited to 'android/app/src/main')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardCustomPortDialog.kt69
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WireguardCustomPortDialogViewModel.kt83
3 files changed, 121 insertions, 33 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardCustomPortDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardCustomPortDialog.kt
index 63f17c780b..9bf8b8bf3b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardCustomPortDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/WireguardCustomPortDialog.kt
@@ -3,12 +3,13 @@ package net.mullvad.mullvadvpn.compose.dialog
import android.os.Parcelable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
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.result.EmptyResultBackNavigator
@@ -18,11 +19,15 @@ import kotlinx.parcelize.Parcelize
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.test.CUSTOM_PORT_DIALOG_INPUT_TEST_TAG
import net.mullvad.mullvadvpn.compose.textfield.CustomPortTextField
+import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
import net.mullvad.mullvadvpn.lib.model.Port
import net.mullvad.mullvadvpn.lib.model.PortRange
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.util.asString
-import net.mullvad.mullvadvpn.util.inAnyOf
+import net.mullvad.mullvadvpn.viewmodel.WireguardCustomPortDialogSideEffect
+import net.mullvad.mullvadvpn.viewmodel.WireguardCustomPortDialogUiState
+import net.mullvad.mullvadvpn.viewmodel.WireguardCustomPortDialogViewModel
+import org.koin.androidx.compose.koinViewModel
@Preview
@Composable
@@ -47,39 +52,44 @@ data class WireguardCustomPortNavArgs(
@Destination<RootGraph>(style = DestinationStyle.Dialog::class)
@Composable
fun WireguardCustomPort(
- navArg: WireguardCustomPortNavArgs,
+ @Suppress("UNUSED_PARAMETER") navArg: WireguardCustomPortNavArgs,
backNavigator: ResultBackNavigator<Port?>,
) {
+ val viewModel = koinViewModel<WireguardCustomPortDialogViewModel>()
+
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ CollectSideEffectWithLifecycle(viewModel.uiSideEffect) {
+ when (it) {
+ is WireguardCustomPortDialogSideEffect.Success -> backNavigator.navigateBack(it.port)
+ }
+ }
+
WireguardCustomPortDialog(
- initialPort = navArg.customPort,
- allowedPortRanges = navArg.allowedPortRanges,
- onSave = { port -> backNavigator.navigateBack(port) },
- onDismiss = backNavigator::navigateBack,
+ uiState,
+ onInputChanged = viewModel::onInputChanged,
+ onSavePort = viewModel::onSaveClick,
+ onResetPort = viewModel::onResetClick,
+ onDismiss = dropUnlessResumed { backNavigator.navigateBack() },
)
}
@Composable
fun WireguardCustomPortDialog(
- initialPort: Port?,
- allowedPortRanges: List<PortRange>,
- onSave: (Port?) -> Unit,
+ state: WireguardCustomPortDialogUiState,
+ onInputChanged: (String) -> Unit,
+ onSavePort: (String) -> Unit,
+ onResetPort: () -> Unit,
onDismiss: () -> Unit,
) {
- val port = remember { mutableStateOf(initialPort?.value?.toString() ?: "") }
- val isValidPort = port.value.toPortOrNull()?.inAnyOf(allowedPortRanges) ?: false
-
InputDialog(
title = stringResource(id = R.string.custom_port_dialog_title),
input = {
CustomPortTextField(
- value = port.value,
- onSubmit = { input ->
- if (isValidPort) {
- onSave(input.toPortOrNull())
- }
- },
- onValueChanged = { input -> port.value = input },
- isValidValue = isValidPort,
+ value = state.portInput,
+ onValueChanged = onInputChanged,
+ onSubmit = onSavePort,
+ isValidValue = state.isValidInput,
maxCharLength = 5,
modifier = Modifier.testTag(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG).fillMaxWidth(),
)
@@ -87,20 +97,13 @@ fun WireguardCustomPortDialog(
message =
stringResource(
id = R.string.custom_port_dialog_valid_ranges,
- allowedPortRanges.asString(),
+ state.allowedPortRanges.asString(),
),
- confirmButtonEnabled = isValidPort,
+ confirmButtonEnabled = state.isValidInput,
confirmButtonText = stringResource(id = R.string.custom_port_dialog_submit),
onResetButtonText = stringResource(R.string.custom_port_dialog_remove),
onBack = onDismiss,
- onReset =
- if (initialPort != null) {
- { onSave(null) }
- } else {
- null
- },
- onConfirm = { onSave(port.value.toPortOrNull()) },
+ onReset = if (state.showResetToDefault) onResetPort else null,
+ onConfirm = { onSavePort(state.portInput) },
)
}
-
-private fun String.toPortOrNull() = toIntOrNull()?.let { Port(it) }
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 6b909d394d..32fad5614b 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
@@ -83,6 +83,7 @@ import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel
import net.mullvad.mullvadvpn.viewmodel.VpnPermissionViewModel
import net.mullvad.mullvadvpn.viewmodel.VpnSettingsViewModel
import net.mullvad.mullvadvpn.viewmodel.WelcomeViewModel
+import net.mullvad.mullvadvpn.viewmodel.WireguardCustomPortDialogViewModel
import org.apache.commons.validator.routines.InetAddressValidator
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
@@ -182,6 +183,7 @@ val uiModule = module {
viewModel { DeviceRevokedViewModel(get(), get()) }
viewModel { MtuDialogViewModel(get(), get()) }
viewModel { DnsDialogViewModel(get(), get(), get()) }
+ viewModel { WireguardCustomPortDialogViewModel(get()) }
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { PrivacyDisclaimerViewModel(get(), IS_PLAY_BUILD) }
viewModel {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WireguardCustomPortDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WireguardCustomPortDialogViewModel.kt
new file mode 100644
index 0000000000..b98801612e
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WireguardCustomPortDialogViewModel.kt
@@ -0,0 +1,83 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ramcosta.composedestinations.generated.destinations.WireguardCustomPortDestination
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.lib.model.Port
+import net.mullvad.mullvadvpn.lib.model.PortRange
+import net.mullvad.mullvadvpn.util.inAnyOf
+
+class WireguardCustomPortDialogViewModel(
+ savedStateHandle: SavedStateHandle,
+ private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
+) : ViewModel() {
+ private val navArgs = WireguardCustomPortDestination.argsFrom(savedStateHandle).navArg
+
+ private val _portInput = MutableStateFlow(navArgs.customPort?.value?.toString() ?: "")
+ private val _isValidPort = MutableStateFlow(_portInput.value.isValidPort())
+
+ val uiState: StateFlow<WireguardCustomPortDialogUiState> =
+ combine(_portInput, _isValidPort, ::createState)
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(),
+ createState(_portInput.value, _isValidPort.value),
+ )
+
+ private val _uiSideEffect = Channel<WireguardCustomPortDialogSideEffect>()
+ val uiSideEffect = _uiSideEffect.receiveAsFlow()
+
+ private fun createState(portInput: String, isValidPortInput: Boolean) =
+ WireguardCustomPortDialogUiState(
+ portInput = portInput,
+ isValidInput = isValidPortInput,
+ allowedPortRanges = navArgs.allowedPortRanges,
+ showResetToDefault = navArgs.customPort != null,
+ )
+
+ fun onInputChanged(value: String) {
+ _portInput.value = value
+ _isValidPort.value = value.isValidPort()
+ }
+
+ fun onSaveClick(portValue: String) =
+ viewModelScope.launch(dispatcher) {
+ val port = portValue.parseValidPort() ?: return@launch
+ _uiSideEffect.send(WireguardCustomPortDialogSideEffect.Success(port))
+ }
+
+ fun onResetClick() {
+ viewModelScope.launch(dispatcher) {
+ _uiSideEffect.send(WireguardCustomPortDialogSideEffect.Success(null))
+ }
+ }
+
+ private fun String.isValidPort(): Boolean = parseValidPort() != null
+
+ private fun String.parseValidPort(): Port? =
+ Port.fromString(this).getOrNull()?.takeIf { port ->
+ port.inAnyOf(navArgs.allowedPortRanges)
+ }
+}
+
+sealed interface WireguardCustomPortDialogSideEffect {
+ data class Success(val port: Port?) : WireguardCustomPortDialogSideEffect
+}
+
+data class WireguardCustomPortDialogUiState(
+ val portInput: String,
+ val isValidInput: Boolean,
+ val allowedPortRanges: List<PortRange>,
+ val showResetToDefault: Boolean,
+)