mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2025-12-23 23:17:50 -05:00
Migrate to SecureTextField (#1191)
* Using `SecureTextField` Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me> * `OutlinedSecureTextField` doesn't support `readOnly` * fixed string conversions * - Update AddWebdavMountScreen to use enabled instead of readOnly - Ensure onKeyboardAction checks canContinue before proceeding - Update UrlLogin and EmailLogin to ensure onKeyboardAction checks canContinue before proceeding - Update InputDialogs to ensure confirmEnabled is checked before proceeding * Use get() for deriving things from a mutable state --------- Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me> Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
@@ -10,8 +10,8 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
@@ -22,10 +22,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
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
|
||||
@@ -34,7 +31,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
@@ -49,12 +45,12 @@ fun EditTextInputDialog(
|
||||
onValueEntered: (String) -> Unit = {},
|
||||
onDismiss: () -> Unit = {},
|
||||
) {
|
||||
var textValue by remember {
|
||||
mutableStateOf(TextFieldValue(
|
||||
initialValue ?: "", selection = TextRange(initialValue?.length ?: 0)
|
||||
))
|
||||
}
|
||||
val state = rememberTextFieldState(
|
||||
initialText = initialValue ?: "",
|
||||
initialSelection = TextRange(initialValue?.length ?: 0)
|
||||
)
|
||||
|
||||
val confirmEnabled = state.text != initialValue
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
@@ -67,27 +63,24 @@ fun EditTextInputDialog(
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
if (passwordField)
|
||||
PasswordTextField(
|
||||
password = textValue.text,
|
||||
password = state,
|
||||
labelText = inputLabel,
|
||||
onPasswordChange = { textValue = TextFieldValue(it) },
|
||||
modifier = Modifier.focusRequester(focusRequester)
|
||||
)
|
||||
else
|
||||
TextField(
|
||||
label = { inputLabel?.let { Text(it) } },
|
||||
value = textValue,
|
||||
onValueChange = { textValue = it },
|
||||
singleLine = true,
|
||||
state = state,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = keyboardType,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
onValueEntered(textValue.text)
|
||||
onKeyboardAction = {
|
||||
if (confirmEnabled) {
|
||||
onValueEntered(state.text.toString())
|
||||
onDismiss()
|
||||
}
|
||||
),
|
||||
},
|
||||
modifier = Modifier.focusRequester(focusRequester)
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -97,10 +90,10 @@ fun EditTextInputDialog(
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
onValueEntered(textValue.text)
|
||||
onValueEntered(state.text.toString())
|
||||
onDismiss()
|
||||
},
|
||||
enabled = textValue.text != initialValue
|
||||
enabled = confirmEnabled
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.KeyboardActionHandler
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.TextObfuscationMode
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedSecureTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -25,8 +29,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.text.HtmlCompat
|
||||
@@ -36,33 +38,28 @@ import at.bitfire.davdroid.ui.UiUtils.toAnnotatedString
|
||||
|
||||
@Composable
|
||||
fun PasswordTextField(
|
||||
password: String,
|
||||
password: TextFieldState,
|
||||
labelText: String?,
|
||||
onPasswordChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
onKeyboardAction: KeyboardActionHandler? = null,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
isError: Boolean = false
|
||||
) {
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = onPasswordChange,
|
||||
OutlinedSecureTextField(
|
||||
state = password,
|
||||
label = labelText?.let { { Text(it) } },
|
||||
leadingIcon = leadingIcon,
|
||||
isError = isError,
|
||||
singleLine = true,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
modifier = modifier.focusGroup(),
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
onKeyboardAction = onKeyboardAction,
|
||||
textObfuscationMode = if (passwordVisible) TextObfuscationMode.Visible else TextObfuscationMode.RevealLastTyped,
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
enabled = enabled,
|
||||
@@ -98,11 +95,10 @@ fun appPasswordHelpUrl(): Uri = ExternalUris.Manual.baseUrl.buildUpon()
|
||||
@Preview
|
||||
fun PasswordTextField_Sample() {
|
||||
PasswordTextField(
|
||||
password = "",
|
||||
password = rememberTextFieldState(""),
|
||||
labelText = "labelText",
|
||||
enabled = true,
|
||||
isError = false,
|
||||
onPasswordChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,11 +106,10 @@ fun PasswordTextField_Sample() {
|
||||
@Preview
|
||||
fun PasswordTextField_Sample_Filled() {
|
||||
PasswordTextField(
|
||||
password = "password",
|
||||
password = rememberTextFieldState("password"),
|
||||
labelText = "labelText",
|
||||
enabled = true,
|
||||
isError = false,
|
||||
onPasswordChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,11 +117,10 @@ fun PasswordTextField_Sample_Filled() {
|
||||
@Preview
|
||||
fun PasswordTextField_Sample_Error() {
|
||||
PasswordTextField(
|
||||
password = "password",
|
||||
password = rememberTextFieldState("password"),
|
||||
labelText = "labelText",
|
||||
enabled = true,
|
||||
isError = true,
|
||||
onPasswordChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,10 +128,9 @@ fun PasswordTextField_Sample_Error() {
|
||||
@Preview
|
||||
fun PasswordTextField_Sample_Disabled() {
|
||||
PasswordTextField(
|
||||
password = "password",
|
||||
password = rememberTextFieldState("password"),
|
||||
labelText = "labelText",
|
||||
enabled = false,
|
||||
isError = false,
|
||||
onPasswordChange = {},
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,8 @@ 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.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
@@ -69,7 +71,6 @@ object AdvancedLogin : LoginType {
|
||||
username = uiState.username,
|
||||
onSetUsername = model::setUsername,
|
||||
password = uiState.password,
|
||||
onSetPassword = model::setPassword,
|
||||
certAlias = uiState.certAlias,
|
||||
onSetCertAlias = model::setCertAlias,
|
||||
canContinue = uiState.canContinue,
|
||||
@@ -88,8 +89,7 @@ fun AdvancedLoginScreen(
|
||||
onSetUrl: (String) -> Unit = {},
|
||||
username: String,
|
||||
onSetUsername: (String) -> Unit = {},
|
||||
password: String,
|
||||
onSetPassword: (String) -> Unit = {},
|
||||
password: TextFieldState,
|
||||
certAlias: String,
|
||||
onSetCertAlias: (String) -> Unit = {},
|
||||
canContinue: Boolean,
|
||||
@@ -159,7 +159,6 @@ fun AdvancedLoginScreen(
|
||||
|
||||
PasswordTextField(
|
||||
password = password,
|
||||
onPasswordChange = onSetPassword,
|
||||
labelText = stringResource(R.string.login_password_optional),
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Password, null)
|
||||
@@ -194,7 +193,7 @@ fun AdvancedLoginScreen_Preview_Empty() {
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
url = "",
|
||||
username = "",
|
||||
password = "",
|
||||
password = rememberTextFieldState(""),
|
||||
certAlias = "",
|
||||
canContinue = false
|
||||
)
|
||||
@@ -207,7 +206,7 @@ fun AdvancedLoginScreen_Preview_AllFilled() {
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
url = "dav.example.com",
|
||||
username = "someuser",
|
||||
password = "password",
|
||||
password = rememberTextFieldState("password"),
|
||||
certAlias = "someCert",
|
||||
canContinue = true
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package at.bitfire.davdroid.ui.setup
|
||||
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -30,7 +31,7 @@ class AdvancedLoginModel @AssistedInject constructor(
|
||||
data class UiState(
|
||||
val url: String = "",
|
||||
val username: String = "",
|
||||
val password: String = "",
|
||||
val password: TextFieldState = TextFieldState(),
|
||||
val certAlias: String = ""
|
||||
) {
|
||||
|
||||
@@ -47,7 +48,7 @@ class AdvancedLoginModel @AssistedInject constructor(
|
||||
baseUri = uri,
|
||||
credentials = Credentials(
|
||||
username = username.trimToNull(),
|
||||
password = password.trimToNull()?.toSensitiveString(),
|
||||
password = password.text.trimToNull()?.toSensitiveString(),
|
||||
certificateAlias = certAlias.trimToNull()
|
||||
)
|
||||
)
|
||||
@@ -61,7 +62,7 @@ class AdvancedLoginModel @AssistedInject constructor(
|
||||
uiState = uiState.copy(
|
||||
url = initialLoginInfo.baseUri?.toString()?.removePrefix("https://") ?: "",
|
||||
username = initialLoginInfo.credentials?.username ?: "",
|
||||
password = initialLoginInfo.credentials?.password?.asString() ?: "",
|
||||
password = TextFieldState(initialLoginInfo.credentials?.password?.asString() ?: ""),
|
||||
certAlias = initialLoginInfo.credentials?.certificateAlias ?: ""
|
||||
)
|
||||
}
|
||||
@@ -74,10 +75,6 @@ class AdvancedLoginModel @AssistedInject constructor(
|
||||
uiState = uiState.copy(username = username)
|
||||
}
|
||||
|
||||
fun setPassword(password: String) {
|
||||
uiState = uiState.copy(password = password)
|
||||
}
|
||||
|
||||
fun setCertAlias(certAlias: String) {
|
||||
uiState = uiState.copy(certAlias = certAlias)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Password
|
||||
@@ -63,7 +64,6 @@ object EmailLogin : LoginType {
|
||||
email = uiState.email,
|
||||
onSetEmail = model::setEmail,
|
||||
password = uiState.password,
|
||||
onSetPassword = model::setPassword,
|
||||
canContinue = uiState.canContinue,
|
||||
onLogin = { onLogin(uiState.asLoginInfo()) }
|
||||
)
|
||||
@@ -76,8 +76,7 @@ object EmailLogin : LoginType {
|
||||
fun EmailLoginScreen(
|
||||
email: String,
|
||||
onSetEmail: (String) -> Unit = {},
|
||||
password: String,
|
||||
onSetPassword: (String) -> Unit = {},
|
||||
password: TextFieldState,
|
||||
canContinue: Boolean,
|
||||
onLogin: () -> Unit = {}
|
||||
) {
|
||||
@@ -129,7 +128,6 @@ fun EmailLoginScreen(
|
||||
|
||||
PasswordTextField(
|
||||
password = password,
|
||||
onPasswordChange = onSetPassword,
|
||||
labelText = stringResource(R.string.login_password),
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Password, null)
|
||||
@@ -138,8 +136,9 @@ fun EmailLoginScreen(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions {
|
||||
if (canContinue) onLogin()
|
||||
onKeyboardAction = {
|
||||
if (canContinue)
|
||||
onLogin()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -157,7 +156,7 @@ fun EmailLoginScreen(
|
||||
fun EmailLoginScreen_Preview() {
|
||||
EmailLoginScreen(
|
||||
email = "test@example.com",
|
||||
password = "",
|
||||
password = rememberTextFieldState(""),
|
||||
canContinue = false
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package at.bitfire.davdroid.ui.setup
|
||||
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -28,18 +29,19 @@ class EmailLoginModel @AssistedInject constructor(
|
||||
|
||||
data class UiState(
|
||||
val email: String = "",
|
||||
val password: String = ""
|
||||
val password: TextFieldState = TextFieldState()
|
||||
) {
|
||||
val uri = "mailto:$email".toURIorNull()
|
||||
|
||||
val canContinue = uri != null && password.isNotEmpty()
|
||||
val canContinue // we have to use get() because password is not immutable
|
||||
get() = uri != null && password.text.toString().isNotEmpty()
|
||||
|
||||
fun asLoginInfo(): LoginInfo {
|
||||
return LoginInfo(
|
||||
baseUri = uri,
|
||||
credentials = Credentials(
|
||||
username = email,
|
||||
password = password.toSensitiveString()
|
||||
password = password.text.toSensitiveString()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -51,7 +53,7 @@ class EmailLoginModel @AssistedInject constructor(
|
||||
init {
|
||||
uiState = uiState.copy(
|
||||
email = initialLoginInfo.credentials?.username ?: "",
|
||||
password = initialLoginInfo.credentials?.password?.asString() ?: ""
|
||||
password = TextFieldState(initialLoginInfo.credentials?.password?.asString() ?: "")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,8 +61,4 @@ class EmailLoginModel @AssistedInject constructor(
|
||||
uiState = uiState.copy(email = email)
|
||||
}
|
||||
|
||||
fun setPassword(password: String) {
|
||||
uiState = uiState.copy(password = password)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,8 +8,9 @@ import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
@@ -65,7 +66,6 @@ object UrlLogin : LoginType {
|
||||
username = uiState.username,
|
||||
onSetUsername = model::setUsername,
|
||||
password = uiState.password,
|
||||
onSetPassword = model::setPassword,
|
||||
canContinue = uiState.canContinue,
|
||||
onLogin = {
|
||||
if (uiState.canContinue)
|
||||
@@ -82,8 +82,7 @@ fun UrlLoginScreen(
|
||||
onSetUrl: (String) -> Unit = {},
|
||||
username: String,
|
||||
onSetUsername: (String) -> Unit = {},
|
||||
password: String,
|
||||
onSetPassword: (String) -> Unit = {},
|
||||
password: TextFieldState,
|
||||
canContinue: Boolean,
|
||||
onLogin: () -> Unit = {}
|
||||
) {
|
||||
@@ -151,7 +150,6 @@ fun UrlLoginScreen(
|
||||
|
||||
PasswordTextField(
|
||||
password = password,
|
||||
onPasswordChange = onSetPassword,
|
||||
labelText = stringResource(R.string.login_password),
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Password, null)
|
||||
@@ -160,8 +158,9 @@ fun UrlLoginScreen(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions {
|
||||
if (canContinue) onLogin()
|
||||
onKeyboardAction = {
|
||||
if (canContinue)
|
||||
onLogin()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -179,7 +178,7 @@ fun UrlLoginScreen_Preview() {
|
||||
UrlLoginScreen(
|
||||
url = "https://example.com",
|
||||
username = "user",
|
||||
password = "",
|
||||
password = rememberTextFieldState(""),
|
||||
canContinue = false
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package at.bitfire.davdroid.ui.setup
|
||||
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -30,7 +31,7 @@ class UrlLoginModel @AssistedInject constructor(
|
||||
data class UiState(
|
||||
val url: String = "",
|
||||
val username: String = "",
|
||||
val password: String = ""
|
||||
val password: TextFieldState = TextFieldState()
|
||||
) {
|
||||
|
||||
val urlWithPrefix =
|
||||
@@ -40,14 +41,15 @@ class UrlLoginModel @AssistedInject constructor(
|
||||
"https://$url"
|
||||
val uri = urlWithPrefix.trim().toURIorNull()
|
||||
|
||||
val canContinue = uri != null && username.isNotEmpty() && password.isNotEmpty()
|
||||
val canContinue // we have to use get() because password is not immutable
|
||||
get() = uri != null && username.isNotEmpty() && password.text.toString().isNotEmpty()
|
||||
|
||||
fun asLoginInfo(): LoginInfo =
|
||||
LoginInfo(
|
||||
baseUri = uri,
|
||||
credentials = Credentials(
|
||||
username = username.trimToNull(),
|
||||
password = password.trimToNull()?.toSensitiveString()
|
||||
password = password.text.toString().trimToNull()?.toSensitiveString()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -60,7 +62,7 @@ class UrlLoginModel @AssistedInject constructor(
|
||||
uiState = UiState(
|
||||
url = initialLoginInfo.baseUri?.toString()?.removePrefix("https://") ?: "",
|
||||
username = initialLoginInfo.credentials?.username ?: "",
|
||||
password = initialLoginInfo.credentials?.password?.asString() ?: ""
|
||||
password = TextFieldState(initialLoginInfo.credentials?.password?.asString() ?: "")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -72,8 +74,4 @@ class UrlLoginModel @AssistedInject constructor(
|
||||
uiState = uiState.copy(username = username)
|
||||
}
|
||||
|
||||
fun setPassword(password: String) {
|
||||
uiState = uiState.copy(password = password)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
package at.bitfire.davdroid.ui.webdav
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -36,7 +37,7 @@ class AddWebdavMountModel @Inject constructor(
|
||||
val displayName: String = "",
|
||||
val url: String = "",
|
||||
val username: String = "",
|
||||
val password: String = "",
|
||||
val password: TextFieldState = TextFieldState(),
|
||||
val certificateAlias: String? = null
|
||||
) {
|
||||
val urlWithPrefix =
|
||||
@@ -67,10 +68,6 @@ class AddWebdavMountModel @Inject constructor(
|
||||
uiState = uiState.copy(username = username)
|
||||
}
|
||||
|
||||
fun setPassword(password: String) {
|
||||
uiState = uiState.copy(password = password)
|
||||
}
|
||||
|
||||
fun setCertificateAlias(certAlias: String) {
|
||||
uiState = uiState.copy(certificateAlias = certAlias)
|
||||
}
|
||||
@@ -85,7 +82,7 @@ class AddWebdavMountModel @Inject constructor(
|
||||
val displayName = uiState.displayName
|
||||
val credentials = Credentials(
|
||||
username = uiState.username.trimToNull(),
|
||||
password = uiState.password.trimToNull()?.toSensitiveString(),
|
||||
password = uiState.password.text.trimToNull()?.toSensitiveString(),
|
||||
certificateAlias = uiState.certificateAlias
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ package at.bitfire.davdroid.ui.webdav
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Help
|
||||
@@ -70,7 +71,6 @@ fun AddWebdavMountScreen(
|
||||
username = uiState.username,
|
||||
onSetUsername = model::setUsername,
|
||||
password = uiState.password,
|
||||
onSetPassword = model::setPassword,
|
||||
certificateAlias = uiState.certificateAlias,
|
||||
onSetCertificateAlias = model::setCertificateAlias,
|
||||
canContinue = uiState.canContinue,
|
||||
@@ -92,8 +92,7 @@ fun AddWebDavMountScreen(
|
||||
onSetUrl: (String) -> Unit = {},
|
||||
username: String,
|
||||
onSetUsername: (String) -> Unit = {},
|
||||
password: String,
|
||||
onSetPassword: (String) -> Unit = {},
|
||||
password: TextFieldState,
|
||||
certificateAlias: String?,
|
||||
onSetCertificateAlias: (String) -> Unit = {},
|
||||
canContinue: Boolean,
|
||||
@@ -163,7 +162,7 @@ fun AddWebDavMountScreen(
|
||||
value = url,
|
||||
onValueChange = onSetUrl,
|
||||
singleLine = true,
|
||||
readOnly = isLoading,
|
||||
enabled = !isLoading,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardType = KeyboardType.Uri
|
||||
@@ -185,7 +184,7 @@ fun AddWebDavMountScreen(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Sell, null)
|
||||
},
|
||||
readOnly = isLoading,
|
||||
enabled = !isLoading,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -207,7 +206,7 @@ fun AddWebDavMountScreen(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.AccountCircle, null)
|
||||
},
|
||||
readOnly = isLoading,
|
||||
enabled = !isLoading,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardType = KeyboardType.Email
|
||||
@@ -218,18 +217,19 @@ fun AddWebDavMountScreen(
|
||||
)
|
||||
PasswordTextField(
|
||||
password = password,
|
||||
onPasswordChange = onSetPassword,
|
||||
labelText = stringResource(R.string.login_password_optional),
|
||||
readOnly = isLoading,
|
||||
enabled = !isLoading,
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Password, null)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { onAddMount() }
|
||||
),
|
||||
onKeyboardAction = {
|
||||
// can only be called when not loading
|
||||
if (canContinue)
|
||||
onAddMount()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp)
|
||||
@@ -258,7 +258,7 @@ fun AddWebDavMountScreen_Preview() {
|
||||
displayName = "Test",
|
||||
url = "https://example.com",
|
||||
username = "user",
|
||||
password = "password",
|
||||
password = rememberTextFieldState("password"),
|
||||
certificateAlias = null,
|
||||
canContinue = true
|
||||
)
|
||||
|
||||
@@ -61,8 +61,8 @@ class SensitiveString private constructor(
|
||||
fun CharArray.toSensitiveString() =
|
||||
SensitiveString(this.concatToString())
|
||||
|
||||
fun String.toSensitiveString() =
|
||||
SensitiveString(this)
|
||||
fun CharSequence.toSensitiveString() =
|
||||
SensitiveString(this.toString())
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
package at.bitfire.davdroid.util
|
||||
|
||||
import com.google.common.base.Joiner
|
||||
import com.google.common.base.Strings
|
||||
|
||||
fun String?.trimToNull() = Strings.emptyToNull(this?.trim())
|
||||
fun CharSequence?.trimToNull() = Strings.emptyToNull(this?.trim()?.toString())
|
||||
|
||||
fun String.withTrailingSlash() =
|
||||
if (this.endsWith('/'))
|
||||
|
||||
Reference in New Issue
Block a user