BIT-1103 Adding the ability to delete totp text (#562)

This commit is contained in:
Oleg Semenenko 2024-01-10 11:34:55 -06:00 committed by Álison Fernandes
parent 3c00a41d84
commit 9c023bca9e
7 changed files with 93 additions and 0 deletions

View file

@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.components.model.IconResource
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the [value] is empty.
* @param leadingIconResource the optional resource for the leading icon on the text field.
* @param trailingIconContent the content for the trailing icon in the text field.
* @param hint optional hint text that will appear below the text input.
* @param singleLine when `true`, this text field becomes a single line that horizontally scrolls
* instead of wrapping onto multiple lines.
@ -51,6 +52,7 @@ fun BitwardenTextField(
modifier: Modifier = Modifier,
placeholder: String? = null,
leadingIconResource: IconResource? = null,
trailingIconContent: (@Composable () -> Unit)? = null,
hint: String? = null,
singleLine: Boolean = true,
readOnly: Boolean = false,
@ -88,6 +90,9 @@ fun BitwardenTextField(
)
}
},
trailingIcon = trailingIconContent?.let {
trailingIconContent
},
placeholder = placeholder?.let {
{ Text(text = it) }
},

View file

@ -25,6 +25,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
* @param readOnly `true` if the input should be read-only and not accept user interactions.
* @param singleLine when `true`, this text field becomes a single line that horizontally scrolls
* instead of wrapping onto multiple lines.
* @param trailingIconContent the content for the trailing icon in the text field.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* next to the text field. This lambda extends [RowScope],
* providing flexibility in the layout definition.
@ -38,6 +39,7 @@ fun BitwardenTextFieldWithActions(
readOnly: Boolean = false,
singleLine: Boolean = true,
keyboardType: KeyboardType = KeyboardType.Text,
trailingIconContent: (@Composable () -> Unit)? = null,
actions: @Composable RowScope.() -> Unit = {},
) {
Row(
@ -53,6 +55,7 @@ fun BitwardenTextFieldWithActions(
singleLine = singleLine,
onValueChange = onValueChange,
keyboardType = keyboardType,
trailingIconContent = trailingIconContent,
)
BitwardenRowOfActions(actions)
}

View file

@ -130,6 +130,16 @@ fun LazyListScope.vaultAddEditLoginItems(
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.totp),
value = loginState.totp,
trailingIconContent = {
IconButton(
onClick = loginItemTypeHandlers.onClearTotpKeyClick,
) {
Icon(
painter = painterResource(id = R.drawable.ic_close),
contentDescription = stringResource(id = R.string.delete),
)
}
},
onValueChange = {},
readOnly = true,
singleLine = true,

View file

@ -382,6 +382,10 @@ class VaultAddEditViewModel @Inject constructor(
is VaultAddEditAction.ItemType.LoginType.CopyTotpKeyClick -> {
handleLoginCopyTotpKeyText(action)
}
is VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick -> {
handleLoginClearTotpKey()
}
}
}
@ -455,6 +459,12 @@ class VaultAddEditViewModel @Inject constructor(
clipboardManager.setText(text = action.totpKey)
}
private fun handleLoginClearTotpKey() {
updateLoginContent { loginType ->
loginType.copy(totp = null)
}
}
private fun handleLoginUriSettingsClick() {
viewModelScope.launch {
sendEvent(
@ -1450,6 +1460,11 @@ sealed class VaultAddEditAction {
*/
data class CopyTotpKeyClick(val totpKey: String) : LoginType()
/**
* Represents the action to clear the totp code.
*/
data object ClearTotpKeyClick : LoginType()
/**
* Represents the action to open the username generator.
*/

View file

@ -18,6 +18,8 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
* @property onOpenPasswordGeneratorClick Handles the action when the password generator
* button is clicked.
* @property onSetupTotpClick Handles the action when the setup TOTP button is clicked.
* @property onCopyTotpKeyClick Handles the action when the copy TOTP text button is clicked.
* @property onClearTotpKeyClick Handles the action when the clear TOTP text button is clicked.
* @property onUriSettingsClick Handles the action when the URI settings button is clicked.
* @property onAddNewUriClick Handles the action when the add new URI button is clicked.
*/
@ -31,6 +33,7 @@ data class VaultAddEditLoginTypeHandlers(
val onOpenPasswordGeneratorClick: () -> Unit,
val onSetupTotpClick: (Boolean) -> Unit,
val onCopyTotpKeyClick: (String) -> Unit,
val onClearTotpKeyClick: () -> Unit,
val onUriSettingsClick: () -> Unit,
val onAddNewUriClick: () -> Unit,
) {
@ -94,6 +97,11 @@ data class VaultAddEditLoginTypeHandlers(
),
)
},
onClearTotpKeyClick = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick,
)
},
)
}
}

View file

@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.FakePermissionManager
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.util.isProgressBar
import com.x8bit.bitwarden.ui.util.onAllNodesWithContentDescriptionAfterScroll
import com.x8bit.bitwarden.ui.util.onAllNodesWithTextAfterScroll
import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll
@ -457,6 +458,25 @@ class VaultAddEditScreenTest : BaseComposeTest() {
.assertDoesNotExist()
}
@Suppress("MaxLineLength")
@Test
fun `in ItemType_Login state the totp text field click on trailing icon should call ClearTotpKeyClick`() {
mutableStateFlow.update { currentState ->
updateLoginType(currentState) { copy(totp = "TestCode") }
}
composeTestRule
.onAllNodesWithContentDescriptionAfterScroll("Delete")
.onFirst()
.performClick()
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick,
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in ItemType_Login state clicking the copy totp code button should trigger CopyTotpKeyClick`() {

View file

@ -674,6 +674,38 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `ClearTotpKeyClick call should clear the totp code`() {
val viewModel = createAddVaultItemViewModel(
savedStateHandle = createSavedStateHandleWithState(
state = createVaultAddItemState(
typeContentViewState = createLoginTypeContentViewState(
totpCode = "testCode",
),
),
vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID),
),
)
val expectedState = loginInitialState.copy(
viewState = VaultAddEditState.ViewState.Content(
common = createCommonContentViewState(),
type = createLoginTypeContentViewState(
totpCode = null,
),
),
)
viewModel.actionChannel.trySend(
VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick,
)
assertEquals(
expectedState,
viewModel.stateFlow.value,
)
}
@Test
fun `TotpCodeReceive should update totp code in state`() = runTest {
val viewModel = createAddVaultItemViewModel()