mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
PM-10617 modify pw strength indicator to show min chars if required. (#3793)
This commit is contained in:
parent
a0a5070ac7
commit
0d6aeee870
10 changed files with 184 additions and 5 deletions
|
@ -160,6 +160,7 @@ fun CompleteRegistrationScreen(
|
|||
modifier = Modifier.standardHorizontalMargin(),
|
||||
nextButtonEnabled = state.hasValidMasterPassword,
|
||||
callToActionText = state.callToActionText(),
|
||||
minimumPasswordLength = state.minimumPasswordLength,
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
|
@ -175,6 +176,7 @@ private fun CompleteRegistrationContent(
|
|||
passwordHintInput: String,
|
||||
isCheckDataBreachesToggled: Boolean,
|
||||
nextButtonEnabled: Boolean,
|
||||
minimumPasswordLength: Int,
|
||||
callToActionText: String,
|
||||
handler: CompleteRegistrationHandler,
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -212,8 +214,9 @@ private fun CompleteRegistrationContent(
|
|||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
PasswordStrengthIndicator(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
state = passwordStrengthState,
|
||||
currentCharacterCount = passwordInput.length,
|
||||
minimumCharacterCount = minimumPasswordLength,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenPasswordField(
|
||||
|
@ -266,7 +269,6 @@ private fun CompleteRegistrationContentHeader(
|
|||
modifier: Modifier = Modifier,
|
||||
configuration: Configuration = LocalConfiguration.current,
|
||||
) {
|
||||
|
||||
if (configuration.isPortrait) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
|
@ -319,7 +321,7 @@ private fun OrderedHeaderContent() {
|
|||
|
||||
@PreviewScreenSizes
|
||||
@Composable
|
||||
private fun CompleteRegistrationContentPreview() {
|
||||
private fun CompleteRegistrationContent_preview() {
|
||||
BitwardenTheme {
|
||||
CompleteRegistrationContent(
|
||||
passwordInput = "tortor",
|
||||
|
@ -342,6 +344,7 @@ private fun CompleteRegistrationContentPreview() {
|
|||
callToActionText = "Next",
|
||||
nextButtonEnabled = true,
|
||||
modifier = Modifier.standardHorizontalMargin(),
|
||||
minimumPasswordLength = 12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ class CompleteRegistrationViewModel @Inject constructor(
|
|||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
onBoardingEnabled = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow),
|
||||
minimumPasswordLength = MIN_PASSWORD_LENGTH,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
@ -340,6 +341,7 @@ data class CompleteRegistrationState(
|
|||
val dialog: CompleteRegistrationDialog?,
|
||||
val passwordStrengthState: PasswordStrengthState,
|
||||
val onBoardingEnabled: Boolean,
|
||||
val minimumPasswordLength: Int,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,25 +1,36 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.completeregistration
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
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.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
|
||||
|
||||
/**
|
||||
|
@ -30,6 +41,8 @@ import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
|
|||
fun PasswordStrengthIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
state: PasswordStrengthState,
|
||||
currentCharacterCount: Int,
|
||||
minimumCharacterCount: Int? = null,
|
||||
) {
|
||||
val widthPercent by animateFloatAsState(
|
||||
targetValue = when (state) {
|
||||
|
@ -85,10 +98,65 @@ fun PasswordStrengthIndicator(
|
|||
)
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
minimumCharacterCount?.let { minCount ->
|
||||
MinimumCharacterCount(
|
||||
minimumRequirementMet = currentCharacterCount >= minCount,
|
||||
minimumCharacterCount = minCount,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = label(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = indicatorColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MinimumCharacterCount(
|
||||
modifier: Modifier = Modifier,
|
||||
minimumRequirementMet: Boolean,
|
||||
minimumCharacterCount: Int,
|
||||
) {
|
||||
val nonMaterialColors = LocalNonMaterialColors.current
|
||||
val characterCountColor by animateColorAsState(
|
||||
targetValue = if (minimumRequirementMet) {
|
||||
nonMaterialColors.passwordStrong
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceDim
|
||||
},
|
||||
label = "minmumCharacterCountColor",
|
||||
)
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = if (minimumRequirementMet) {
|
||||
R.drawable.ic_plain_checkmark
|
||||
} else {
|
||||
R.drawable.ic_circle
|
||||
},
|
||||
label = "iconForMinimumCharacterCount",
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(id = it),
|
||||
contentDescription = null,
|
||||
tint = characterCountColor,
|
||||
modifier = Modifier.size(12.dp),
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Text(
|
||||
text = label(),
|
||||
text = stringResource(R.string.minimum_characters, minimumCharacterCount),
|
||||
color = characterCountColor,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = indicatorColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -104,3 +172,38 @@ enum class PasswordStrengthState {
|
|||
GOOD,
|
||||
STRONG,
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PasswordStrengthIndicatorPreview_minCharMet() {
|
||||
BitwardenTheme {
|
||||
PasswordStrengthIndicator(
|
||||
state = PasswordStrengthState.WEAK_3,
|
||||
currentCharacterCount = 12,
|
||||
minimumCharacterCount = 12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PasswordStrengthIndicatorPreview_minCharNotMet() {
|
||||
BitwardenTheme {
|
||||
PasswordStrengthIndicator(
|
||||
state = PasswordStrengthState.WEAK_3,
|
||||
currentCharacterCount = 11,
|
||||
minimumCharacterCount = 12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PasswordStrengthIndicatorPreview_noMinChar() {
|
||||
BitwardenTheme {
|
||||
PasswordStrengthIndicator(
|
||||
state = PasswordStrengthState.WEAK_3,
|
||||
currentCharacterCount = 12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,6 +228,7 @@ fun CreateAccountScreen(
|
|||
PasswordStrengthIndicator(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
state = state.passwordStrengthState,
|
||||
currentCharacterCount = state.passwordInput.length,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenPasswordField(
|
||||
|
|
|
@ -256,6 +256,7 @@ private fun ExportVaultScreenContent(
|
|||
PasswordStrengthIndicator(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
state = state.passwordStrengthState,
|
||||
currentCharacterCount = state.passwordInput.length,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
|
|
11
app/src/main/res/drawable/ic_circle.xml
Normal file
11
app/src/main/res/drawable/ic_circle.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M6,6m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#DBD9DD"/>
|
||||
</vector>
|
13
app/src/main/res/drawable/ic_plain_checkmark.xml
Normal file
13
app/src/main/res/drawable/ic_plain_checkmark.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="10dp"
|
||||
android:height="8dp"
|
||||
android:viewportWidth="10"
|
||||
android:viewportHeight="8">
|
||||
<path
|
||||
android:pathData="M1,4.429L3.88,7L9,1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017E45"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -350,6 +350,7 @@ class CompleteRegistrationScreenTest : BaseComposeTest() {
|
|||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
onBoardingEnabled = false,
|
||||
minimumPasswordLength = 12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -539,6 +539,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
|||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
onBoardingEnabled = false,
|
||||
minimumPasswordLength = 12,
|
||||
)
|
||||
private val VALID_INPUT_STATE = CompleteRegistrationState(
|
||||
userEmail = EMAIL,
|
||||
|
@ -551,6 +552,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
|||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.GOOD,
|
||||
onBoardingEnabled = false,
|
||||
minimumPasswordLength = 12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.completeregistration
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import org.junit.Test
|
||||
|
||||
class PasswordStrengthIndicatorTest : BaseComposeTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `PasswordStrengthIndicator with minimum character count met displays minimum character count`() {
|
||||
composeTestRule.setContent {
|
||||
PasswordStrengthIndicator(
|
||||
state = PasswordStrengthState.WEAK_3,
|
||||
currentCharacterCount = 12,
|
||||
minimumCharacterCount = 12,
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("characters", substring = true)
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `PasswordStrengthIndicator with no minimum character count met does not minimum character count`() {
|
||||
composeTestRule.setContent {
|
||||
PasswordStrengthIndicator(
|
||||
state = PasswordStrengthState.WEAK_3,
|
||||
currentCharacterCount = 12,
|
||||
minimumCharacterCount = null,
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("characters", substring = true)
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue