PM-18275: Add totp tooltip on view item screen (#4732)

This commit is contained in:
David Perez 2025-02-17 15:44:55 -06:00 committed by GitHub
parent 22931bbd38
commit 9f19a99eb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 69 additions and 0 deletions

View File

@ -80,6 +80,7 @@ import kotlinx.collections.immutable.toImmutableList
* @param value current next on the text field.
* @param modifier modifier for the composable.
* @param onValueChange callback that is triggered when the input of the text field changes.
* @param tooltip the optional tooltip to be displayed in the label.
* @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.
@ -109,6 +110,7 @@ fun BitwardenTextField(
onValueChange: (String) -> Unit,
cardStyle: CardStyle,
modifier: Modifier = Modifier,
tooltip: TooltipData? = null,
placeholder: String? = null,
leadingIconResource: IconResource? = null,
supportingText: String? = null,
@ -133,6 +135,7 @@ fun BitwardenTextField(
label = label,
value = value,
onValueChange = onValueChange,
tooltip = tooltip,
placeholder = placeholder,
leadingIconResource = leadingIconResource,
supportingContent = supportingText?.let {
@ -171,6 +174,7 @@ fun BitwardenTextField(
* @param label label for the text field.
* @param value current next on the text field.
* @param modifier modifier for the composable.
* @param tooltip the optional tooltip to be displayed in the label.
* @param onValueChange callback that is triggered when the input of the text field changes.
* @param supportingContent An optional supporting content composable that will appear below the
* text input.

View File

@ -25,6 +25,7 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.indicator.BitwardenCircularCountdownIndicator
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@ -141,6 +142,8 @@ fun VaultItemLoginContent(
totpCodeItemData = totpCodeItemData,
enabled = loginItemState.canViewTotpCode,
onCopyTotpClick = vaultLoginItemTypeHandlers.onCopyTotpCodeClick,
onAuthenticatorHelpToolTipClick = vaultLoginItemTypeHandlers
.onAuthenticatorHelpToolTipClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
@ -399,6 +402,7 @@ private fun TotpField(
totpCodeItemData: TotpCodeItemData,
enabled: Boolean,
onCopyTotpClick: () -> Unit,
onAuthenticatorHelpToolTipClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (enabled) {
@ -411,6 +415,10 @@ private fun TotpField(
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
readOnly = true,
singleLine = true,
tooltip = TooltipData(
onClick = onAuthenticatorHelpToolTipClick,
contentDescription = stringResource(id = R.string.authenticator_key_help),
),
actions = {
BitwardenCircularCountdownIndicator(
timeLeftSeconds = totpCodeItemData.timeLeftSeconds,
@ -431,6 +439,10 @@ private fun TotpField(
BitwardenTextField(
label = stringResource(id = R.string.authenticator_key),
value = "",
tooltip = TooltipData(
onClick = onAuthenticatorHelpToolTipClick,
contentDescription = stringResource(id = R.string.authenticator_key_help),
),
supportingText = stringResource(id = R.string.premium_subscription_required),
enabled = false,
singleLine = false,

View File

@ -530,6 +530,10 @@ class VaultItemViewModel @Inject constructor(
private fun handleLoginTypeActions(action: VaultItemAction.ItemType.Login) {
when (action) {
is VaultItemAction.ItemType.Login.AuthenticatorHelpToolTipClick -> {
handleAuthenticatorHelpToolTipClick()
}
is VaultItemAction.ItemType.Login.CheckForBreachClick -> {
handleCheckForBreachClick()
}
@ -564,6 +568,14 @@ class VaultItemViewModel @Inject constructor(
}
}
private fun handleAuthenticatorHelpToolTipClick() {
sendEvent(
event = VaultItemEvent.NavigateToUri(
uri = "https://bitwarden.com/help/integrated-authenticator",
),
)
}
private fun handleCheckForBreachClick() {
onLoginContent { _, login ->
val password = requireNotNull(login.passwordData?.password)
@ -1991,6 +2003,11 @@ sealed class VaultItemAction {
* Represents actions specific to the Login type.
*/
sealed class Login : ItemType() {
/**
* The user has clicked the call to action on the authenticator help tooltip.
*/
data object AuthenticatorHelpToolTipClick : Login()
/**
* The user has clicked the check for breach button.
*/

View File

@ -12,6 +12,7 @@ data class VaultLoginItemTypeHandlers(
val onCheckForBreachClick: () -> Unit,
val onCopyPasswordClick: () -> Unit,
val onCopyTotpCodeClick: () -> Unit,
val onAuthenticatorHelpToolTipClick: () -> Unit,
val onCopyUriClick: (String) -> Unit,
val onCopyUsernameClick: () -> Unit,
val onLaunchUriClick: (String) -> Unit,
@ -37,6 +38,11 @@ data class VaultLoginItemTypeHandlers(
onCopyTotpCodeClick = {
viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyTotpClick)
},
onAuthenticatorHelpToolTipClick = {
viewModel.trySendAction(
VaultItemAction.ItemType.Login.AuthenticatorHelpToolTipClick,
)
},
onCopyUriClick = {
viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUriClick(it))
},

View File

@ -1727,6 +1727,21 @@ class VaultItemScreenTest : BaseComposeTest() {
}
}
@Test
fun `in login state, on totp help tooltip click should send AuthenticatorHelpToolTipClick`() {
mutableStateFlow.update { currentState ->
currentState.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
}
composeTestRule
.onNodeWithContentDescriptionAfterScroll("Authenticator key help")
.performClick()
verify {
viewModel.trySendAction(VaultItemAction.ItemType.Login.AuthenticatorHelpToolTipClick)
}
}
@Test
fun `in login state, launch uri button should be displayed according to state`() {
val uriData = VaultItemState.ViewState.Content.ItemType.Login.UriData(

View File

@ -2001,6 +2001,21 @@ class VaultItemViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `on AuthenticatorHelpToolTipClick should emit NavigateToUri`() = runTest {
viewModel.eventFlow.test {
viewModel.trySendAction(
action = VaultItemAction.ItemType.Login.AuthenticatorHelpToolTipClick,
)
assertEquals(
VaultItemEvent.NavigateToUri(
"https://bitwarden.com/help/integrated-authenticator",
),
awaitItem(),
)
}
}
@Test
fun `on PasswordHistoryClick should show password dialog when re-prompt is required`() =
runTest {