Add optional "hint" to BitwardenTextField and BitwardenPasswordField (#202)

This commit is contained in:
Brian Yencho 2023-11-03 09:35:15 -05:00 committed by Álison Fernandes
parent 49d71627c7
commit d10e678bb3
4 changed files with 108 additions and 80 deletions

View file

@ -212,6 +212,7 @@ fun CreateAccountScreen(
showPassword = showPassword, showPassword = showPassword,
showPasswordChange = { showPassword = it }, showPasswordChange = { showPassword = it },
value = state.passwordInput, value = state.passwordInput,
hint = state.passwordLengthLabel(),
onValueChange = remember(viewModel) { onValueChange = remember(viewModel) {
{ viewModel.trySendAction(PasswordInputChange(it)) } { viewModel.trySendAction(PasswordInputChange(it)) }
}, },
@ -219,13 +220,6 @@ fun CreateAccountScreen(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
Spacer(modifier = Modifier.height(4.dp))
Text(
text = state.passwordLengthLabel(),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
PasswordStrengthIndicator( PasswordStrengthIndicator(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
@ -251,18 +245,11 @@ fun CreateAccountScreen(
onValueChange = remember(viewModel) { onValueChange = remember(viewModel) {
{ viewModel.trySendAction(PasswordHintChange(it)) } { viewModel.trySendAction(PasswordHintChange(it)) }
}, },
hint = stringResource(id = R.string.master_password_hint_description),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.master_password_hint_description),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier
.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
BitwardenSwitch( BitwardenSwitch(
label = stringResource(id = R.string.check_known_data_breaches_for_this_password), label = stringResource(id = R.string.check_known_data_breaches_for_this_password),

View file

@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -101,6 +100,7 @@ fun EnvironmentScreen(
label = stringResource(id = R.string.server_url), label = stringResource(id = R.string.server_url),
value = state.serverUrl, value = state.serverUrl,
placeholder = "ex. https://bitwarden.company.com", placeholder = "ex. https://bitwarden.company.com",
hint = stringResource(id = R.string.self_hosted_environment_footer),
onValueChange = remember(viewModel) { onValueChange = remember(viewModel) {
{ viewModel.trySendAction(EnvironmentAction.ServerUrlChange(it)) } { viewModel.trySendAction(EnvironmentAction.ServerUrlChange(it)) }
}, },
@ -110,17 +110,6 @@ fun EnvironmentScreen(
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.self_hosted_environment_footer),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
BitwardenListHeaderText( BitwardenListHeaderText(
@ -180,22 +169,12 @@ fun EnvironmentScreen(
onValueChange = remember(viewModel) { onValueChange = remember(viewModel) {
{ viewModel.trySendAction(EnvironmentAction.IconsServerUrlChange(it)) } { viewModel.trySendAction(EnvironmentAction.IconsServerUrlChange(it)) }
}, },
hint = stringResource(id = R.string.custom_environment_footer),
keyboardType = KeyboardType.Uri, keyboardType = KeyboardType.Uri,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.custom_environment_footer),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
)
} }
} }
} }

View file

@ -1,5 +1,10 @@
package com.x8bit.bitwarden.ui.platform.components package com.x8bit.bitwarden.ui.platform.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -18,6 +23,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
/** /**
@ -31,6 +37,7 @@ import com.x8bit.bitwarden.R
* @param showPasswordChange Lambda that is called when user request show/hide be toggled. * @param showPasswordChange Lambda that is called when user request show/hide be toggled.
* @param onValueChange Callback that is triggered when the password changes. * @param onValueChange Callback that is triggered when the password changes.
* @param modifier Modifier for the composable. * @param modifier Modifier for the composable.
* @param hint optional hint text that will appear below the text input.
*/ */
@Composable @Composable
fun BitwardenPasswordField( fun BitwardenPasswordField(
@ -40,40 +47,59 @@ fun BitwardenPasswordField(
showPasswordChange: (Boolean) -> Unit, showPasswordChange: (Boolean) -> Unit,
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
hint: String? = null,
) { ) {
OutlinedTextField( Column(
modifier = modifier, modifier = modifier,
textStyle = MaterialTheme.typography.bodyLarge, ) {
label = { Text(text = label) }, OutlinedTextField(
value = value, modifier = Modifier.fillMaxWidth(),
onValueChange = onValueChange, textStyle = MaterialTheme.typography.bodyLarge,
visualTransformation = if (showPassword) { label = { Text(text = label) },
VisualTransformation.None value = value,
} else { onValueChange = onValueChange,
PasswordVisualTransformation() visualTransformation = if (showPassword) {
}, VisualTransformation.None
singleLine = true, } else {
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), PasswordVisualTransformation()
trailingIcon = { },
IconButton( singleLine = true,
onClick = { showPasswordChange.invoke(!showPassword) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
) { trailingIcon = {
if (showPassword) { IconButton(
Icon( onClick = { showPasswordChange.invoke(!showPassword) },
painter = painterResource(id = R.drawable.ic_visibility_off), ) {
contentDescription = stringResource(id = R.string.hide), if (showPassword) {
tint = MaterialTheme.colorScheme.onSurfaceVariant, Icon(
) painter = painterResource(id = R.drawable.ic_visibility_off),
} else { contentDescription = stringResource(id = R.string.hide),
Icon( tint = MaterialTheme.colorScheme.onSurfaceVariant,
painter = painterResource(id = R.drawable.ic_visibility), )
contentDescription = stringResource(id = R.string.show), } else {
tint = MaterialTheme.colorScheme.onSurfaceVariant, Icon(
) painter = painterResource(id = R.drawable.ic_visibility),
contentDescription = stringResource(id = R.string.show),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
} }
} },
}, )
)
hint?.let {
Spacer(
modifier = Modifier.height(4.dp),
)
Text(
text = hint,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
}
} }
/** /**
@ -84,6 +110,7 @@ fun BitwardenPasswordField(
* @param value Current next on the text field. * @param value Current next on the text field.
* @param onValueChange Callback that is triggered when the password changes. * @param onValueChange Callback that is triggered when the password changes.
* @param modifier Modifier for the composable. * @param modifier Modifier for the composable.
* @param hint optional hint text that will appear below the text input.
* @param initialShowPassword The initial state of the show/hide password control. A value of * @param initialShowPassword The initial state of the show/hide password control. A value of
* `false` (the default) indicates that that password should begin in the hidden state. * `false` (the default) indicates that that password should begin in the hidden state.
*/ */
@ -93,6 +120,7 @@ fun BitwardenPasswordField(
value: String, value: String,
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
hint: String? = null,
initialShowPassword: Boolean = false, initialShowPassword: Boolean = false,
) { ) {
var showPassword by rememberSaveable { mutableStateOf(initialShowPassword) } var showPassword by rememberSaveable { mutableStateOf(initialShowPassword) }
@ -103,6 +131,7 @@ fun BitwardenPasswordField(
showPassword = showPassword, showPassword = showPassword,
showPasswordChange = { showPassword = !showPassword }, showPasswordChange = { showPassword = !showPassword },
onValueChange = onValueChange, onValueChange = onValueChange,
hint = hint,
) )
} }
@ -114,6 +143,7 @@ private fun BitwardenPasswordField_preview_withInput_hidePassword() {
value = "Password", value = "Password",
onValueChange = {}, onValueChange = {},
initialShowPassword = false, initialShowPassword = false,
hint = "Hint",
) )
} }
@ -125,6 +155,7 @@ private fun BitwardenPasswordField_preview_withInput_showPassword() {
value = "Password", value = "Password",
onValueChange = {}, onValueChange = {},
initialShowPassword = true, initialShowPassword = true,
hint = "Hint",
) )
} }
@ -136,6 +167,7 @@ private fun BitwardenPasswordField_preview_withoutInput_hidePassword() {
value = "", value = "",
onValueChange = {}, onValueChange = {},
initialShowPassword = false, initialShowPassword = false,
hint = "Hint",
) )
} }
@ -147,5 +179,6 @@ private fun BitwardenPasswordField_preview_withoutInput_showPassword() {
value = "", value = "",
onValueChange = {}, onValueChange = {},
initialShowPassword = true, initialShowPassword = true,
hint = "Hint",
) )
} }

View file

@ -1,12 +1,19 @@
package com.x8bit.bitwarden.ui.platform.components package com.x8bit.bitwarden.ui.platform.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
/** /**
* Component that allows the user to input text. This composable will manage the state of * Component that allows the user to input text. This composable will manage the state of
@ -17,6 +24,7 @@ import androidx.compose.ui.tooling.preview.Preview
* @param onValueChange callback that is triggered when the input of the text field changes. * @param onValueChange callback that is triggered when the input of the text field changes.
* @param placeholder the optional placeholder to be displayed when the text field is in focus and * @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the [value] is empty. * the [value] is empty.
* @param hint optional hint text that will appear below the text input.
* @param readOnly `true` if the input should be read-only and not accept user interactions. * @param readOnly `true` if the input should be read-only and not accept user interactions.
* @param keyboardType the preferred type of keyboard input. * @param keyboardType the preferred type of keyboard input.
*/ */
@ -27,21 +35,40 @@ fun BitwardenTextField(
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
placeholder: String? = null, placeholder: String? = null,
hint: String? = null,
readOnly: Boolean = false, readOnly: Boolean = false,
keyboardType: KeyboardType = KeyboardType.Text, keyboardType: KeyboardType = KeyboardType.Text,
) { ) {
OutlinedTextField( Column(
modifier = modifier, modifier = modifier,
label = { Text(text = label) }, ) {
value = value, OutlinedTextField(
placeholder = placeholder?.let { modifier = Modifier.fillMaxWidth(),
{ Text(text = it) } label = { Text(text = label) },
}, value = value,
onValueChange = onValueChange, placeholder = placeholder?.let {
singleLine = true, { Text(text = it) }
readOnly = readOnly, },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType), onValueChange = onValueChange,
) singleLine = true,
readOnly = readOnly,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
)
hint?.let {
Spacer(
modifier = Modifier.height(4.dp),
)
Text(
text = hint,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
}
} }
@Preview @Preview
@ -51,6 +78,7 @@ private fun BitwardenTextField_preview_withInput() {
label = "Label", label = "Label",
value = "Input", value = "Input",
onValueChange = {}, onValueChange = {},
hint = "Hint",
) )
} }
@ -61,5 +89,6 @@ private fun BitwardenTextField_preview_withoutInput() {
label = "Label", label = "Label",
value = "", value = "",
onValueChange = {}, onValueChange = {},
hint = "Hint",
) )
} }