mirror of
https://github.com/bitwarden/android.git
synced 2026-03-22 12:32:53 -05:00
PM-17838 - Add help button for authenticator key (#4697)
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components.field
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
@@ -26,14 +30,19 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.focus.onFocusEvent
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.semantics.CustomAccessibilityAction
|
||||
import androidx.compose.ui.semantics.customActions
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
@@ -41,11 +50,13 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.nullableTestTag
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toPx
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenMenuItemColors
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenCutCopyTextToolbar
|
||||
@@ -53,6 +64,7 @@ import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTe
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TextToolbarType
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
|
||||
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenRowOfActions
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -192,6 +204,7 @@ fun BitwardenTextField(
|
||||
supportingContent: (@Composable ColumnScope.() -> Unit)?,
|
||||
cardStyle: CardStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
tooltip: TooltipData? = null,
|
||||
supportingContentPadding: PaddingValues = PaddingValues(vertical = 12.dp, horizontal = 16.dp),
|
||||
placeholder: String? = null,
|
||||
leadingIconResource: IconResource? = null,
|
||||
@@ -255,12 +268,52 @@ fun BitwardenTextField(
|
||||
paddingTop = 6.dp,
|
||||
paddingBottom = 0.dp,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
customActions = listOfNotNull(
|
||||
tooltip?.let {
|
||||
CustomAccessibilityAction(
|
||||
label = it.contentDescription,
|
||||
action = {
|
||||
it.onClick()
|
||||
true
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
) {
|
||||
var focused by remember { mutableStateOf(false) }
|
||||
|
||||
TextField(
|
||||
colors = bitwardenTextFieldColors(),
|
||||
enabled = enabled,
|
||||
label = label?.let { { Text(text = it) } },
|
||||
label = label?.let {
|
||||
{
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = it)
|
||||
tooltip?.let {
|
||||
val targetSize = if (textFieldValue.text.isEmpty() || focused) {
|
||||
16.dp
|
||||
} else {
|
||||
12.dp
|
||||
}
|
||||
val size by animateDpAsState(
|
||||
targetValue = targetSize,
|
||||
label = "${it.contentDescription}_animation",
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle_small,
|
||||
contentDescription = it.contentDescription,
|
||||
onClick = it.onClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
modifier = Modifier.size(size),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
value = textFieldValue,
|
||||
leadingIcon = leadingIconResource?.let { iconResource ->
|
||||
{
|
||||
@@ -298,7 +351,10 @@ fun BitwardenTextField(
|
||||
visualTransformation = visualTransformation,
|
||||
modifier = Modifier
|
||||
.nullableTestTag(tag = textFieldTestTag)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState ->
|
||||
focused = focusState.isFocused
|
||||
},
|
||||
)
|
||||
supportingContent
|
||||
?.let { content ->
|
||||
|
||||
@@ -64,7 +64,11 @@ fun BitwardenClickableText(
|
||||
Icon(
|
||||
painter = leadingIcon,
|
||||
contentDescription = null,
|
||||
tint = color,
|
||||
tint = if (isEnabled) {
|
||||
color
|
||||
} else {
|
||||
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
|
||||
},
|
||||
modifier = Modifier.size(size = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(width = 8.dp))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -14,6 +13,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -23,7 +23,6 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.coachmark.CoachMarkActionText
|
||||
import com.x8bit.bitwarden.ui.platform.components.coachmark.CoachMarkScope
|
||||
@@ -34,8 +33,8 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
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.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.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
|
||||
|
||||
@@ -281,7 +280,6 @@ private fun CoachMarkScope<AddEditItemCoachMark>.PasswordRow(
|
||||
label = stringResource(id = R.string.check_password_for_data_breaches),
|
||||
style = BitwardenTheme.typography.labelMedium,
|
||||
onClick = loginItemTypeHandlers.onPasswordCheckerClick,
|
||||
leadingIcon = painterResource(id = R.drawable.ic_camera_small),
|
||||
innerPadding = PaddingValues(all = 16.dp),
|
||||
cornerSize = 0.dp,
|
||||
modifier = Modifier
|
||||
@@ -364,69 +362,51 @@ private fun TotpRow(
|
||||
onTotpSetupClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (totpKey != null) {
|
||||
if (canViewTotp) {
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.authenticator_key),
|
||||
value = totpKey,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_clear,
|
||||
contentDescription = stringResource(id = R.string.delete),
|
||||
onClick = loginItemTypeHandlers.onClearTotpKeyClick,
|
||||
)
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_copy,
|
||||
contentDescription = stringResource(id = R.string.copy_totp),
|
||||
onClick = { loginItemTypeHandlers.onCopyTotpKeyClick(totpKey) },
|
||||
)
|
||||
},
|
||||
supportingContentPadding = PaddingValues(),
|
||||
supportingContent = {
|
||||
BitwardenClickableText(
|
||||
label = stringResource(id = R.string.set_up_authenticator_key),
|
||||
onClick = onTotpSetupClick,
|
||||
leadingIcon = painterResource(id = R.drawable.ic_plus_small),
|
||||
style = BitwardenTheme.typography.labelMedium,
|
||||
innerPadding = PaddingValues(all = 16.dp),
|
||||
cornerSize = 0.dp,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
},
|
||||
textFieldTestTag = "LoginTotpEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
)
|
||||
} else {
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.authenticator_key),
|
||||
value = totpKey,
|
||||
cardStyle = CardStyle.Full,
|
||||
textFieldTestTag = "LoginTotpEntry",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
enabled = false,
|
||||
singleLine = true,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(modifier = modifier) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(id = R.string.setup_totp),
|
||||
icon = rememberVectorPainter(id = R.drawable.ic_light_bulb),
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.authenticator_key),
|
||||
value = totpKey.orEmpty(),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
actions = {
|
||||
totpKey?.let {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_clear,
|
||||
contentDescription = stringResource(id = R.string.delete),
|
||||
onClick = loginItemTypeHandlers.onClearTotpKeyClick,
|
||||
)
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_copy,
|
||||
contentDescription = stringResource(id = R.string.copy_totp),
|
||||
onClick = { loginItemTypeHandlers.onCopyTotpKeyClick(totpKey) },
|
||||
)
|
||||
}
|
||||
},
|
||||
tooltip = TooltipData(
|
||||
onClick = loginItemTypeHandlers.onAuthenticatorHelpToolTipClick,
|
||||
contentDescription = stringResource(id = R.string.authenticator_key_help),
|
||||
),
|
||||
supportingContentPadding = PaddingValues(),
|
||||
supportingContent = {
|
||||
BitwardenClickableText(
|
||||
label = stringResource(id = R.string.set_up_authenticator_key),
|
||||
onClick = onTotpSetupClick,
|
||||
leadingIcon = painterResource(id = R.drawable.ic_camera_small),
|
||||
style = BitwardenTheme.typography.labelMedium,
|
||||
innerPadding = PaddingValues(all = 16.dp),
|
||||
isEnabled = canViewTotp,
|
||||
cornerSize = 0.dp,
|
||||
modifier = Modifier
|
||||
.testTag("SetupTotpButton")
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.testTag("SetupTotpButton"),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
textFieldTestTag = "LoginTotpEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.focusProperties { canFocus = false },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -130,6 +130,12 @@ fun VaultAddEditScreen(
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddEditEvent.NavigateToAuthenticatorKeyTooltipUri -> {
|
||||
intentManager.launchUri(
|
||||
"https://bitwarden.com/help/integrated-authenticator".toUri(),
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddEditEvent.CompleteFido2Registration -> {
|
||||
fido2CompletionManager.completeFido2Registration(event.result)
|
||||
}
|
||||
|
||||
@@ -991,6 +991,10 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
VaultAddEditAction.ItemType.LoginType.StartLearnAboutLogins -> {
|
||||
handleStartLearnAboutLogins()
|
||||
}
|
||||
|
||||
VaultAddEditAction.ItemType.LoginType.AuthenticatorHelpToolTipClick -> {
|
||||
handleAuthenticatorHelpToolTipClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1823,6 +1827,10 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
|
||||
getRequestAndRegisterCredential()
|
||||
}
|
||||
|
||||
private fun handleAuthenticatorHelpToolTipClick() {
|
||||
sendEvent(VaultAddEditEvent.NavigateToAuthenticatorKeyTooltipUri)
|
||||
}
|
||||
//endregion Internal Type Handlers
|
||||
|
||||
//region Utility Functions
|
||||
@@ -2649,6 +2657,11 @@ sealed class VaultAddEditEvent {
|
||||
* Start the coach mark guided tour of the add login content.
|
||||
*/
|
||||
data object StartAddLoginItemCoachMarkTour : VaultAddEditEvent()
|
||||
|
||||
/**
|
||||
* Navigate the user to the tooltip URI for Authenticator key help.
|
||||
*/
|
||||
data object NavigateToAuthenticatorKeyTooltipUri : VaultAddEditEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2969,6 +2982,11 @@ sealed class VaultAddEditAction {
|
||||
* User has dismissed the learn about logins card.
|
||||
*/
|
||||
data object LearnAboutLoginsDismissed : LoginType()
|
||||
|
||||
/**
|
||||
* User has clicked the call to action on the authenticator help tooltip.
|
||||
*/
|
||||
data object AuthenticatorHelpToolTipClick : LoginType()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,8 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
* clicked.
|
||||
* @property onClearFido2CredentialClick Handles the action when the clear Fido2 credential button
|
||||
* is clicked.
|
||||
* @property onAuthenticatorHelpToolTipClick Handles the action when the authenticator help tooltip
|
||||
* is clicked.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
data class VaultAddEditLoginTypeHandlers(
|
||||
@@ -44,6 +46,7 @@ data class VaultAddEditLoginTypeHandlers(
|
||||
val onClearFido2CredentialClick: () -> Unit,
|
||||
val onStartLoginCoachMarkTour: () -> Unit,
|
||||
val onDismissLearnAboutLoginsCard: () -> Unit,
|
||||
val onAuthenticatorHelpToolTipClick: () -> Unit,
|
||||
) {
|
||||
@Suppress("UndocumentedPublicClass")
|
||||
companion object {
|
||||
@@ -103,6 +106,11 @@ data class VaultAddEditLoginTypeHandlers(
|
||||
onAddNewUriClick = {
|
||||
viewModel.trySendAction(VaultAddEditAction.ItemType.LoginType.AddNewUriClick)
|
||||
},
|
||||
onAuthenticatorHelpToolTipClick = {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.AuthenticatorHelpToolTipClick,
|
||||
)
|
||||
},
|
||||
onCopyTotpKeyClick = { totpKey ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.CopyTotpKeyClick(
|
||||
|
||||
@@ -13,9 +13,6 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -44,8 +41,10 @@ import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.CustomAccessibilityAction
|
||||
import androidx.compose.ui.semantics.customActions
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -55,9 +54,9 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalBitwardenColorScheme
|
||||
@@ -87,7 +86,7 @@ fun QrCodeScanScreen(
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val onEnterCodeManuallyClick = remember(viewModel) {
|
||||
val onEnterKeyManuallyClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(QrCodeScanAction.ManualEntryTextClick) }
|
||||
}
|
||||
|
||||
@@ -135,11 +134,11 @@ fun QrCodeScanScreen(
|
||||
|
||||
if (LocalConfiguration.current.isPortrait) {
|
||||
PortraitQRCodeContent(
|
||||
onEnterCodeManuallyClick = onEnterCodeManuallyClick,
|
||||
onEnterKeyManuallyClick = onEnterKeyManuallyClick,
|
||||
)
|
||||
} else {
|
||||
LandscapeQRCodeContent(
|
||||
onEnterCodeManuallyClick = onEnterCodeManuallyClick,
|
||||
onEnterKeyManuallyClick = onEnterKeyManuallyClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -148,7 +147,7 @@ fun QrCodeScanScreen(
|
||||
|
||||
@Composable
|
||||
private fun PortraitQRCodeContent(
|
||||
onEnterCodeManuallyClick: () -> Unit,
|
||||
onEnterKeyManuallyClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
@@ -178,8 +177,8 @@ private fun PortraitQRCodeContent(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
BottomClickableText(
|
||||
onEnterCodeManuallyClick = onEnterCodeManuallyClick,
|
||||
EnterKeyManuallyText(
|
||||
onEnterKeyManuallyClick = onEnterKeyManuallyClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
@@ -188,7 +187,7 @@ private fun PortraitQRCodeContent(
|
||||
|
||||
@Composable
|
||||
private fun LandscapeQRCodeContent(
|
||||
onEnterCodeManuallyClick: () -> Unit,
|
||||
onEnterKeyManuallyClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
@@ -218,8 +217,8 @@ private fun LandscapeQRCodeContent(
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
)
|
||||
|
||||
BottomClickableText(
|
||||
onEnterCodeManuallyClick = onEnterCodeManuallyClick,
|
||||
EnterKeyManuallyText(
|
||||
onEnterKeyManuallyClick = onEnterKeyManuallyClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -403,28 +402,33 @@ private fun QrCodeSquare(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun BottomClickableText(
|
||||
onEnterCodeManuallyClick: () -> Unit,
|
||||
private fun EnterKeyManuallyText(
|
||||
onEnterKeyManuallyClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
text = stringResource(id = R.string.cannot_scan_qr_code),
|
||||
color = Color.White,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
)
|
||||
BitwardenClickableText(
|
||||
label = stringResource(id = R.string.enter_key_manually),
|
||||
style = BitwardenTheme.typography.labelLarge,
|
||||
innerPadding = PaddingValues(vertical = 4.dp, horizontal = 12.dp),
|
||||
onClick = onEnterCodeManuallyClick,
|
||||
modifier = Modifier.testTag("EnterKeyManuallyButton"),
|
||||
)
|
||||
val enterKeyManuallyString = stringResource(R.string.enter_key_manually)
|
||||
val annotatedLinkString = R.string.cannot_scan_qr_code_enter_key_manually.toAnnotatedString {
|
||||
when (it) {
|
||||
"enterKeyManually" -> onEnterKeyManuallyClick()
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = annotatedLinkString,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = modifier
|
||||
.semantics {
|
||||
customActions = listOf(
|
||||
CustomAccessibilityAction(
|
||||
label = enterKeyManuallyString,
|
||||
action = {
|
||||
onEnterKeyManuallyClick()
|
||||
true
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -709,8 +709,9 @@ Scanning will happen automatically.</string>
|
||||
<string name="cannot_add_authenticator_key">Cannot add authenticator key?</string>
|
||||
<string name="cannot_add_authenticator_key_scan_qr_code">Cannot add authenticator key? <annotation link="scanQrCode">Scan QR Code</annotation></string>
|
||||
<string name="scan_qr_code">Scan QR Code</string>
|
||||
<string name="cannot_scan_qr_code">Cannot scan QR Code?</string>
|
||||
<string name="cannot_scan_qr_code_enter_key_manually">Cannot scan QR code? <annotation link="enterKeyManually">Enter key manually</annotation></string>
|
||||
<string name="authenticator_key_scanner">Authenticator key</string>
|
||||
<string name="authenticator_key_help">Authenticator key help</string>
|
||||
<string name="enter_key_manually">Enter key manually</string>
|
||||
<string name="add_totp">Add TOTP</string>
|
||||
<string name="setup_totp">Set up TOTP</string>
|
||||
|
||||
@@ -154,6 +154,16 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToAuthenticatorKeyTooltipUri Event should invoke IntentManager`() {
|
||||
mutableEventFlow.tryEmit(VaultAddEditEvent.NavigateToAuthenticatorKeyTooltipUri)
|
||||
verify {
|
||||
intentManager.launchUri(
|
||||
"https://bitwarden.com/help/integrated-authenticator".toUri(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToQrCodeScan event should invoke NavigateToQrCodeScan`() {
|
||||
mutableEventFlow.tryEmit(VaultAddEditEvent.NavigateToQrCodeScan)
|
||||
@@ -1020,10 +1030,6 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) { copy(totp = null) }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Authenticator key")
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@@ -1090,13 +1096,13 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Login state SetupTOTP button should be present based on state`() {
|
||||
fun `in ItemType_Login state Set up authenticator key button should always be present`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) { copy(totp = null) }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Set up TOTP")
|
||||
.onNodeWithTextAfterScroll(text = "Set up authenticator key")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
@@ -1104,8 +1110,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Set up TOTP")
|
||||
.assertIsNotDisplayed()
|
||||
.onNodeWithText(text = "Set up authenticator key")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@@ -1114,7 +1120,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fakePermissionManager.checkPermissionResult = true
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Set up TOTP")
|
||||
.onNodeWithTextAfterScroll(text = "Set up authenticator key")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
@@ -1131,7 +1137,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fakePermissionManager.getPermissionsResult = true
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Set up TOTP")
|
||||
.onNodeWithTextAfterScroll(text = "Set up authenticator key")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
@@ -1145,12 +1151,12 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login state clicking Set up TOTP button with a negative result should send false`() {
|
||||
fun `in ItemType_Login state clicking Set up authenticator key button with a negative result should send false`() {
|
||||
fakePermissionManager.checkPermissionResult = false
|
||||
fakePermissionManager.getPermissionsResult = false
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Set up TOTP")
|
||||
.onNodeWithTextAfterScroll(text = "Set up authenticator key")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
@@ -1164,7 +1170,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login state the SetupTOTP button should be visible but disabled based on state`() {
|
||||
fun `in ItemType_Login state the SetupAuthenticatorKey button should be visible but disabled based on state`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) {
|
||||
copy(
|
||||
@@ -1175,7 +1181,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Set up TOTP")
|
||||
.onNodeWithTextAfterScroll(text = "Set up authenticator key")
|
||||
.assertIsDisplayed()
|
||||
.assertIsNotEnabled()
|
||||
}
|
||||
@@ -1193,7 +1199,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Authenticator key")
|
||||
.onNodeWithTextAfterScroll(text = "Set up authenticator key")
|
||||
.assertIsDisplayed()
|
||||
.assertIsNotEnabled()
|
||||
|
||||
@@ -1202,6 +1208,19 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
composeTestRule.assertScrollableNodeDoesNotExist("Camera")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Clicking the Authenticator key tooltip sends AuthenticatorHelpToolTipClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Authenticator key help")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.AuthenticatorHelpToolTipClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Login state changing URI text field should trigger UriValueChange`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
|
||||
@@ -2421,6 +2421,19 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Authenticator TooltipCLick emits NavigateToAuthenticatorKeyTooltipUri`() = runTest {
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.AuthenticatorHelpToolTipClick,
|
||||
)
|
||||
assertEquals(
|
||||
VaultAddEditEvent.NavigateToAuthenticatorKeyTooltipUri,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ClearTotpKeyClick call should clear the totp code`() {
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.camera.core.ImageProxy
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.util.performCustomAccessibilityAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util.FakeQrCodeAnalyzer
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@@ -89,17 +90,23 @@ class QrCodeScanScreenTest : BaseComposeTest() {
|
||||
@Config(qualifiers = "land")
|
||||
@Test
|
||||
fun `clicking on manual text should send ManualEntryTextClick in landscape mode`() = runTest {
|
||||
// TODO Update the tests once clickable text issue is resolved (BIT-1357)
|
||||
composeTestRule
|
||||
.onNodeWithText("Enter key manually", substring = true)
|
||||
.assertExists()
|
||||
.onNodeWithText(text = "Cannot scan QR code? Enter key manually")
|
||||
.performCustomAccessibilityAction(label = "Enter key manually")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(QrCodeScanAction.ManualEntryTextClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on manual text should send ManualEntryTextClick`() = runTest {
|
||||
// TODO Update the tests once clickable text issue is resolved (BIT-1357)
|
||||
composeTestRule
|
||||
.onNodeWithText("Enter key manually", substring = true)
|
||||
.assertExists()
|
||||
.onNodeWithText(text = "Cannot scan QR code? Enter key manually")
|
||||
.performCustomAccessibilityAction(label = "Enter key manually")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(QrCodeScanAction.ManualEntryTextClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user