summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-03-13 13:23:03 +0100
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-03-14 14:53:44 +0100
commit79a26bc75dda68fada3f49ab9e8bd95daec358b3 (patch)
treefdda412f2f4ad0083dced05e86f8396e3461f1f9
parentd16b190c62611a72910fc7db59842bda019c7364 (diff)
downloadmullvadvpn-79a26bc75dda68fada3f49ab9e8bd95daec358b3.tar.xz
mullvadvpn-79a26bc75dda68fada3f49ab9e8bd95daec358b3.zip
Add edit custom list name dialog
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt107
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/UpdateCustomListUiState.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt76
3 files changed, 188 insertions, 0 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt
new file mode 100644
index 0000000000..9f46ee1d5a
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt
@@ -0,0 +1,107 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+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 com.ramcosta.composedestinations.annotation.Destination
+import com.ramcosta.composedestinations.result.ResultBackNavigator
+import com.ramcosta.composedestinations.spec.DestinationStyle
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.button.PrimaryButton
+import net.mullvad.mullvadvpn.compose.communication.CustomListResult
+import net.mullvad.mullvadvpn.compose.component.CustomListNameTextField
+import net.mullvad.mullvadvpn.compose.state.UpdateCustomListUiState
+import net.mullvad.mullvadvpn.compose.test.EDIT_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG
+import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.viewmodel.EditCustomListNameDialogSideEffect
+import net.mullvad.mullvadvpn.viewmodel.EditCustomListNameDialogViewModel
+import org.koin.androidx.compose.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Preview
+@Composable
+private fun PreviewEditCustomListNameDialog() {
+ AppTheme { EditCustomListNameDialog(UpdateCustomListUiState()) }
+}
+
+@Composable
+@Destination(style = DestinationStyle.Dialog::class)
+fun EditCustomListName(
+ backNavigator: ResultBackNavigator<CustomListResult.Renamed>,
+ customListId: String,
+ initialName: String
+) {
+ val vm: EditCustomListNameDialogViewModel =
+ koinViewModel(parameters = { parametersOf(customListId, initialName) })
+ LaunchedEffectCollect(vm.uiSideEffect) { sideEffect ->
+ when (sideEffect) {
+ is EditCustomListNameDialogSideEffect.ReturnWithResult -> {
+ backNavigator.navigateBack(result = sideEffect.result)
+ }
+ }
+ }
+
+ val state by vm.uiState.collectAsStateWithLifecycle()
+ EditCustomListNameDialog(
+ state = state,
+ updateName = vm::updateCustomListName,
+ onInputChanged = vm::clearError,
+ onDismiss = backNavigator::navigateBack
+ )
+}
+
+@Composable
+fun EditCustomListNameDialog(
+ state: UpdateCustomListUiState,
+ updateName: (String) -> Unit = {},
+ onInputChanged: () -> Unit = {},
+ onDismiss: () -> Unit = {}
+) {
+ val name = remember { mutableStateOf(state.name) }
+ val isValidName by remember { derivedStateOf { name.value.isNotBlank() } }
+
+ AlertDialog(
+ title = {
+ Text(
+ text = stringResource(id = R.string.update_list_name),
+ )
+ },
+ text = {
+ CustomListNameTextField(
+ name = name.value,
+ isValidName = isValidName,
+ error = state.error,
+ onSubmit = updateName,
+ onValueChanged = {
+ name.value = it
+ onInputChanged()
+ },
+ modifier = Modifier.testTag(EDIT_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG)
+ )
+ },
+ containerColor = MaterialTheme.colorScheme.background,
+ titleContentColor = MaterialTheme.colorScheme.onBackground,
+ onDismissRequest = onDismiss,
+ confirmButton = {
+ PrimaryButton(
+ text = stringResource(id = R.string.save),
+ onClick = { updateName(name.value) },
+ isEnabled = isValidName
+ )
+ },
+ dismissButton = {
+ PrimaryButton(text = stringResource(id = R.string.cancel), onClick = onDismiss)
+ }
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/UpdateCustomListUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/UpdateCustomListUiState.kt
new file mode 100644
index 0000000000..7eac74a40a
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/UpdateCustomListUiState.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.compose.state
+
+import net.mullvad.mullvadvpn.model.CustomListsError
+
+data class UpdateCustomListUiState(val name: String = "", val error: CustomListsError? = null)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt
new file mode 100644
index 0000000000..c2625e6d56
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt
@@ -0,0 +1,76 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.compose.communication.CustomListAction
+import net.mullvad.mullvadvpn.compose.communication.CustomListResult
+import net.mullvad.mullvadvpn.compose.state.UpdateCustomListUiState
+import net.mullvad.mullvadvpn.model.CustomListsError
+import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
+import net.mullvad.mullvadvpn.usecase.customlists.CustomListsException
+
+class EditCustomListNameDialogViewModel(
+ private val customListId: String,
+ private val initialName: String,
+ private val customListActionUseCase: CustomListActionUseCase
+) : ViewModel() {
+
+ private val _uiSideEffect =
+ Channel<EditCustomListNameDialogSideEffect>(1, BufferOverflow.DROP_OLDEST)
+ val uiSideEffect = _uiSideEffect.receiveAsFlow()
+
+ private val _error = MutableStateFlow<CustomListsError?>(null)
+
+ val uiState =
+ _error
+ .map { UpdateCustomListUiState(name = initialName, error = it) }
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(),
+ UpdateCustomListUiState(name = initialName)
+ )
+
+ fun updateCustomListName(name: String) {
+ viewModelScope.launch {
+ customListActionUseCase
+ .performAction(
+ CustomListAction.Rename(
+ customListId = customListId,
+ name = initialName,
+ newName = name
+ )
+ )
+ .fold(
+ onSuccess = { result ->
+ _uiSideEffect.send(
+ EditCustomListNameDialogSideEffect.ReturnWithResult(result)
+ )
+ },
+ onFailure = { exception ->
+ if (exception is CustomListsException) {
+ _error.emit(exception.error)
+ } else {
+ _error.emit(CustomListsError.OtherError)
+ }
+ }
+ )
+ }
+ }
+
+ fun clearError() {
+ viewModelScope.launch { _error.emit(null) }
+ }
+}
+
+sealed interface EditCustomListNameDialogSideEffect {
+ data class ReturnWithResult(val result: CustomListResult.Renamed) :
+ EditCustomListNameDialogSideEffect
+}