Merge "Add Material 2 wrappers for BasicSecureTextField" into androidx-main
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index db7832e..c7fe760 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -688,6 +688,11 @@
property public final androidx.compose.material.SnackbarHostState snackbarHostState;
}
+ public final class SecureTextFieldKt {
+ method @androidx.compose.runtime.Composable public static void OutlinedSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional int textObfuscationMode, optional char textObfuscationCharacter, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @androidx.compose.runtime.Composable public static void SecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional int textObfuscationMode, optional char textObfuscationCharacter, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ }
+
@SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public interface SelectableChipColors {
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> backgroundColor(boolean enabled, boolean selected);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> contentColor(boolean enabled, boolean selected);
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index db7832e..c7fe760 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -688,6 +688,11 @@
property public final androidx.compose.material.SnackbarHostState snackbarHostState;
}
+ public final class SecureTextFieldKt {
+ method @androidx.compose.runtime.Composable public static void OutlinedSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional int textObfuscationMode, optional char textObfuscationCharacter, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @androidx.compose.runtime.Composable public static void SecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional int textObfuscationMode, optional char textObfuscationCharacter, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ }
+
@SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public interface SelectableChipColors {
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> backgroundColor(boolean enabled, boolean selected);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> contentColor(boolean enabled, boolean selected);
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
index 8b5dca9..07927ce 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldLineLimits
+import androidx.compose.foundation.text.input.TextObfuscationMode
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.ContentAlpha
@@ -33,6 +34,7 @@
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.SecureTextField
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
@@ -163,24 +165,21 @@
}
}
-// TODO: update sample with TextFieldState once we have wrappers for BasicSecureTextField
+@Sampled
@Composable
fun PasswordTextField() {
- var password by rememberSaveable { mutableStateOf("") }
var passwordHidden by rememberSaveable { mutableStateOf(true) }
- TextField(
- value = password,
- onValueChange = { password = it },
- singleLine = true,
+ SecureTextField(
+ state = rememberTextFieldState(),
label = { Text("Enter password") },
- visualTransformation =
- if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+ textObfuscationMode =
+ if (passwordHidden) TextObfuscationMode.RevealLastTyped
+ else TextObfuscationMode.Visible,
trailingIcon = {
IconButton(onClick = { passwordHidden = !passwordHidden }) {
val visibilityIcon =
if (passwordHidden) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
- // Please provide localized description for accessibility services
+ // Provide localized description for accessibility services
val description = if (passwordHidden) "Show password" else "Hide password"
Icon(imageVector = visibilityIcon, contentDescription = description)
}
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/SecureTextFieldScreenshotTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/SecureTextFieldScreenshotTest.kt
new file mode 100644
index 0000000..6ca996f
--- /dev/null
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/SecureTextFieldScreenshotTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.textfield
+
+import android.os.Build
+import androidx.compose.foundation.text.input.TextObfuscationMode
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.material.GOLDEN_MATERIAL
+import androidx.compose.material.OutlinedSecureTextField
+import androidx.compose.material.SecureTextField
+import androidx.compose.material.Text
+import androidx.compose.material.setMaterialContent
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class SecureTextFieldScreenshotTest {
+ private val TextFieldTag = "TextField"
+
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL)
+
+ @Test
+ fun secureTextField_filled_noObfuscation() {
+ rule.setMaterialContent {
+ SecureTextField(
+ state = rememberTextFieldState("password"),
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TextFieldTag),
+ textObfuscationMode = TextObfuscationMode.Visible,
+ )
+ }
+
+ assertAgainstGolden("secureTextField_filled_noObfuscation")
+ }
+
+ @Test
+ fun secureTextField_outlined_noObfuscation() {
+ rule.setMaterialContent {
+ OutlinedSecureTextField(
+ state = rememberTextFieldState("password"),
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TextFieldTag),
+ textObfuscationMode = TextObfuscationMode.Visible,
+ )
+ }
+
+ assertAgainstGolden("secureTextField_outlined_noObfuscation")
+ }
+
+ @Test
+ fun secureTextField_filled_customObfuscationCharacter() {
+ rule.setMaterialContent {
+ SecureTextField(
+ state = rememberTextFieldState("password"),
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TextFieldTag),
+ textObfuscationMode = TextObfuscationMode.Hidden,
+ textObfuscationCharacter = '*',
+ )
+ }
+
+ assertAgainstGolden("secureTextField_filled_customObfuscationCharacter")
+ }
+
+ @Test
+ fun secureTextField_outlined_customObfuscationCharacter() {
+ rule.setMaterialContent {
+ OutlinedSecureTextField(
+ state = rememberTextFieldState("password"),
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TextFieldTag),
+ textObfuscationMode = TextObfuscationMode.Hidden,
+ textObfuscationCharacter = '*',
+ )
+ }
+
+ assertAgainstGolden("secureTextField_outlined_customObfuscationCharacter")
+ }
+
+ private fun assertAgainstGolden(goldenIdentifier: String) {
+ rule
+ .onNodeWithTag(TextFieldTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, goldenIdentifier)
+ }
+}
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/SecureTextFieldTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/SecureTextFieldTest.kt
new file mode 100644
index 0000000..d3f9dca
--- /dev/null
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/SecureTextFieldTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.textfield
+
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.material.OutlinedSecureTextField
+import androidx.compose.material.SecureTextField
+import androidx.compose.material.setMaterialContent
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SecureTextFieldTest {
+ private val TextFieldTag = "TextField"
+
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun testSecureTextField_filled_textContentIsNotObfuscated() {
+ rule.setMaterialContent {
+ SecureTextField(
+ state = rememberTextFieldState("password"),
+ modifier = Modifier.testTag(TextFieldTag)
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).assertTextEquals("password")
+ }
+
+ @Test
+ fun testSecureTextField_outlined_textContentIsNotObfuscated() {
+ rule.setMaterialContent {
+ OutlinedSecureTextField(
+ state = rememberTextFieldState("password"),
+ modifier = Modifier.testTag(TextFieldTag)
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).assertTextEquals("password")
+ }
+}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
index 076cf77..ac52028 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
@@ -79,7 +79,7 @@
import kotlin.math.roundToInt
/**
- * <a href="https://material.io/components/text-fields#outlined-text-field" class="external"
+ * <a href="https://m2.material.io/components/text-fields#outlined-text-field" class="external"
* target="_blank">Material Design outlined text field</a>.
*
* Outlined text fields have less visual emphasis than filled text fields. When they appear in
@@ -89,6 +89,9 @@
* 
*
+ * If you are looking for a filled version, see [TextField]. For a text field specifically designed
+ * for passwords or other secure content, see [OutlinedSecureTextField].
+ *
* This overload of [OutlinedTextField] uses [TextFieldState] to keep track of its text content and
* position of the cursor or selection.
*
@@ -248,7 +251,7 @@
}
/**
- * <a href="https://material.io/components/text-fields#outlined-text-field" class="external"
+ * <a href="https://m2.material.io/components/text-fields#outlined-text-field" class="external"
* target="_blank">Material Design outlined text field</a>.
*
* Outlined text fields have less visual emphasis than filled text fields. When they appear in
@@ -455,7 +458,7 @@
}
/**
- * <a href="https://material.io/components/text-fields#outlined-text-field" class="external"
+ * <a href="https://m2.material.io/components/text-fields#outlined-text-field" class="external"
* target="_blank">Material Design outlined text field</a>.
*
* Outlined text fields have less visual emphasis than filled text fields. When they appear in
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SecureTextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SecureTextField.kt
new file mode 100644
index 0000000..948751b
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SecureTextField.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.BasicSecureTextField
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.KeyboardActionHandler
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.TextObfuscationMode
+import androidx.compose.material.TextFieldDefaults.indicatorLine
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.VisualTransformation
+
+/**
+ * <a href="https://m2.material.io/components/text-fields#filled-text-field" class="external"
+ * target="_blank">Material Design filled text field for secure content</a>.
+ *
+ * Text fields allow users to enter text into a UI. [SecureTextField] is specifically designed for
+ * password entry fields. It only supports a single line of content and comes with default settings
+ * that are appropriate for entering secure content. Additionally, some context menu actions like
+ * cut, copy, and drag are disabled for added security.
+ *
+ * Filled text fields have more visual emphasis than outlined text fields, making them stand out
+ * when surrounded by other content and components. For an outlined version, see
+ * [OutlinedSecureTextField].
+ *
+ * Example of a password text field:
+ *
+ * @sample androidx.compose.material.samples.PasswordTextField
+ * @param state [TextFieldState] object that holds the internal editing state of this text field.
+ * @param modifier a [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
+ * be neither editable nor focusable, the input of the text field will not be selectable, visually
+ * text field will appear in the disabled UI state.
+ * @param readOnly controls the editable state of the [TextField]. When `true`, the text field can
+ * not be modified. However, a user can focus it. Read-only text fields are usually used to
+ * display pre-filled forms that user can not edit.
+ * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
+ * [LocalTextStyle] defined by the theme.
+ * @param label the optional label to be displayed inside the text field container. The default text
+ * style for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus.
+ * @param placeholder the optional placeholder to be displayed when the text field is in focus and
+ * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1].
+ * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
+ * container.
+ * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
+ * container.
+ * @param isError indicates if the text field's current value is in error. If set to true, the
+ * label, bottom indicator and trailing icon by default will be displayed in error color.
+ * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
+ * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
+ * hardware and software keyboard events, pasting or dropping text, accessibility services, and
+ * tests. The transformation will _not_ be applied when changing the [state] programmatically, or
+ * when the transformation is changed. If the transformation is changed on an existing text field,
+ * it will be applied to the next user edit. the transformation will not immediately affect the
+ * current [state].
+ * @param textObfuscationMode the method used to obscure the input text.
+ * @param textObfuscationCharacter the character to use while obfuscating the text. It doesn't have
+ * an effect when [textObfuscationMode] is set to [TextObfuscationMode.Visible].
+ * @param keyboardOptions software keyboard options that contains configuration such as
+ * [KeyboardType] and [ImeAction].
+ * @param onKeyboardAction Called when the user presses the action button in the input method editor
+ * (IME), or by pressing the enter key on a hardware keyboard. By default this parameter is null,
+ * and would execute the default behavior for a received IME Action e.g., [ImeAction.Done] would
+ * close the keyboard, [ImeAction.Next] would switch the focus to the next focusable item on the
+ * screen.
+ * @param shape the shape of the text field's container
+ * @param colors [TextFieldColors] that will be used to resolve color of the text, content
+ * (including label, placeholder, leading and trailing icons, indicator line) and background for
+ * this text field in different states. See [TextFieldDefaults.textFieldColors]
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this text field. You can use this to change the text field's
+ * appearance or preview the text field in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ */
+@Composable
+fun SecureTextField(
+ state: TextFieldState,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
+ textStyle: TextStyle = LocalTextStyle.current,
+ label: @Composable (() -> Unit)? = null,
+ placeholder: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ isError: Boolean = false,
+ inputTransformation: InputTransformation? = null,
+ textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
+ textObfuscationCharacter: Char = DefaultObfuscationCharacter,
+ keyboardOptions: KeyboardOptions = SecureTextFieldKeyboardOptions,
+ onKeyboardAction: KeyboardActionHandler? = null,
+ shape: Shape = TextFieldDefaults.TextFieldShape,
+ colors: TextFieldColors = TextFieldDefaults.textFieldColors(),
+ interactionSource: MutableInteractionSource? = null,
+) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+ // If color is not provided via the text style, use content color as a default
+ val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
+ val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
+
+ @OptIn(ExperimentalMaterialApi::class)
+ BasicSecureTextField(
+ state = state,
+ modifier =
+ modifier
+ .indicatorLine(enabled, isError, interactionSource, colors)
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
+ .defaultMinSize(
+ minWidth = TextFieldDefaults.MinWidth,
+ minHeight = TextFieldDefaults.MinHeight
+ ),
+ enabled = enabled,
+ readOnly = readOnly,
+ textStyle = mergedTextStyle,
+ cursorBrush = SolidColor(colors.cursorColor(isError).value),
+ inputTransformation = inputTransformation,
+ textObfuscationMode = textObfuscationMode,
+ textObfuscationCharacter = textObfuscationCharacter,
+ keyboardOptions = keyboardOptions,
+ onKeyboardAction = onKeyboardAction,
+ interactionSource = interactionSource,
+ decorator = { innerTextField ->
+ TextFieldDefaults.TextFieldDecorationBox(
+ value = state.text.toString(),
+ visualTransformation = VisualTransformation.None,
+ innerTextField = innerTextField,
+ placeholder = placeholder,
+ label = label,
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon,
+ singleLine = true,
+ enabled = enabled,
+ isError = isError,
+ interactionSource = interactionSource,
+ shape = shape,
+ colors = colors,
+ )
+ }
+ )
+}
+
+/**
+ * <a href="https://m2.material.io/components/text-fields#outlined-text-field" class="external"
+ * target="_blank">Material Design outlined text field for secure content</a>.
+ *
+ * Text fields allow users to enter text into a UI. [OutlinedSecureTextField] is specifically
+ * designed for password entry fields. It only supports a single line of content and comes with
+ * default settings that are appropriate for entering secure content. Additionally, some context
+ * menu actions like cut, copy, and drag are disabled for added security.
+ *
+ * Outlined text fields have less visual emphasis than filled text fields. When they appear in
+ * places like forms, where many text fields are placed together, their reduced emphasis helps
+ * simplify the layout. For a filled version, see [SecureTextField].
+ *
+ * @param state [TextFieldState] object that holds the internal editing state of this text field.
+ * @param modifier a [Modifier] for this text field
+ * @param enabled controls the enabled state of the [OutlinedTextField]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be
+ * selectable, visually text field will appear in the disabled UI state
+ * @param readOnly controls the editable state of the [OutlinedTextField]. When `true`, the text
+ * field can not be modified, however, a user can focus it and copy text from it. Read-only text
+ * fields are usually used to display pre-filled forms that user can not edit
+ * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
+ * [LocalTextStyle] defined by the theme
+ * @param label the optional label to be displayed inside the text field container. The default text
+ * style for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
+ * @param placeholder the optional placeholder to be displayed when the text field is in focus and
+ * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
+ * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
+ * container
+ * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
+ * container
+ * @param isError indicates if the text field's current value is in error. If set to true, the
+ * label, bottom indicator and trailing icon by default will be displayed in error color
+ * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
+ * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
+ * hardware and software keyboard events, pasting or dropping text, accessibility services, and
+ * tests. The transformation will _not_ be applied when changing the [state] programmatically, or
+ * when the transformation is changed. If the transformation is changed on an existing text field,
+ * it will be applied to the next user edit. the transformation will not immediately affect the
+ * current [state].
+ * @param textObfuscationMode the method used to obscure the input text.
+ * @param textObfuscationCharacter the character to use while obfuscating the text. It doesn't have
+ * an effect when [textObfuscationMode] is set to [TextObfuscationMode.Visible].
+ * @param keyboardOptions software keyboard options that contains configuration such as
+ * [KeyboardType] and [ImeAction]
+ * @param onKeyboardAction Called when the user presses the action button in the input method editor
+ * (IME), or by pressing the enter key on a hardware keyboard. By default this parameter is null,
+ * and would execute the default behavior for a received IME Action e.g., [ImeAction.Done] would
+ * close the keyboard, [ImeAction.Next] would switch the focus to the next focusable item on the
+ * screen.
+ * @param shape the shape of the text field's border
+ * @param colors [TextFieldColors] that will be used to resolve color of the text and content
+ * (including label, placeholder, leading and trailing icons, border) for this text field in
+ * different states. See [TextFieldDefaults.outlinedTextFieldColors]
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this text field. You can use this to change the text field's
+ * appearance or preview the text field in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ */
+@Composable
+fun OutlinedSecureTextField(
+ state: TextFieldState,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
+ textStyle: TextStyle = LocalTextStyle.current,
+ label: @Composable (() -> Unit)? = null,
+ placeholder: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ isError: Boolean = false,
+ inputTransformation: InputTransformation? = null,
+ textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
+ textObfuscationCharacter: Char = DefaultObfuscationCharacter,
+ keyboardOptions: KeyboardOptions = SecureTextFieldKeyboardOptions,
+ onKeyboardAction: KeyboardActionHandler? = null,
+ shape: Shape = TextFieldDefaults.OutlinedTextFieldShape,
+ colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(),
+ interactionSource: MutableInteractionSource? = null,
+) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+ // If color is not provided via the text style, use content color as a default
+ val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
+ val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
+
+ val density = LocalDensity.current
+
+ @OptIn(ExperimentalMaterialApi::class)
+ BasicSecureTextField(
+ state = state,
+ modifier =
+ modifier
+ .then(
+ if (label != null) {
+ Modifier
+ // Merge semantics at the beginning of the modifier chain to ensure
+ // padding is considered part of the text field.
+ .semantics(mergeDescendants = true) {}
+ .padding(top = with(density) { OutlinedTextFieldTopPadding.toDp() })
+ } else {
+ Modifier
+ }
+ )
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
+ .defaultMinSize(
+ minWidth = TextFieldDefaults.MinWidth,
+ minHeight = TextFieldDefaults.MinHeight
+ ),
+ enabled = enabled,
+ readOnly = readOnly,
+ textStyle = mergedTextStyle,
+ cursorBrush = SolidColor(colors.cursorColor(isError).value),
+ inputTransformation = inputTransformation,
+ textObfuscationMode = textObfuscationMode,
+ textObfuscationCharacter = textObfuscationCharacter,
+ keyboardOptions = keyboardOptions,
+ onKeyboardAction = onKeyboardAction,
+ interactionSource = interactionSource,
+ decorator = { innerTextField ->
+ TextFieldDefaults.OutlinedTextFieldDecorationBox(
+ value = state.text.toString(),
+ visualTransformation = VisualTransformation.None,
+ innerTextField = innerTextField,
+ placeholder = placeholder,
+ label = label,
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon,
+ singleLine = true,
+ enabled = enabled,
+ isError = isError,
+ interactionSource = interactionSource,
+ shape = shape,
+ colors = colors,
+ border = {
+ TextFieldDefaults.BorderBox(enabled, isError, interactionSource, colors, shape)
+ }
+ )
+ }
+ )
+}
+
+private val SecureTextFieldKeyboardOptions =
+ KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password)
+
+private const val DefaultObfuscationCharacter: Char = '\u2022'
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
index 20fa7fe..c7a23d0 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
@@ -77,7 +77,7 @@
import kotlin.math.roundToInt
/**
- * <a href="https://material.io/components/text-fields#filled-text-field" class="external"
+ * <a href="https://m2.material.io/components/text-fields#filled-text-field" class="external"
* target="_blank">Material Design filled text field</a>.
*
* Filled text fields have more visual emphasis than outlined text fields, making them stand out
@@ -255,7 +255,7 @@
}
/**
- * <a href="https://material.io/components/text-fields#filled-text-field" class="external"
+ * <a href="https://m2.material.io/components/text-fields#filled-text-field" class="external"
* target="_blank">Material Design filled text field</a>.
*
* Filled text fields have more visual emphasis than outlined text fields, making them stand out
@@ -444,7 +444,7 @@
}
/**
- * <a href="https://material.io/components/text-fields#filled-text-field" class="external"
+ * <a href="https://m2.material.io/components/text-fields#filled-text-field" class="external"
* target="_blank">Material Design filled text field</a>.
*
* Filled text fields have more visual emphasis than outlined text fields, making them stand out
@@ -453,7 +453,8 @@
* 
*
- * If you are looking for an outlined version, see [OutlinedTextField].
+ * If you are looking for an outlined version, see [OutlinedTextField]. For a text field
+ * specifically designed for passwords or other secure content, see [SecureTextField].
*
* This overload provides access to the input text, cursor position, selection range and IME
* composition. If you only want to observe an input text change, use the TextField overload with