1 /*
<lambda>null2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.material
18 
19 import androidx.compose.foundation.interaction.Interaction
20 import androidx.compose.foundation.interaction.MutableInteractionSource
21 import androidx.compose.foundation.layout.defaultMinSize
22 import androidx.compose.foundation.layout.padding
23 import androidx.compose.foundation.text.BasicSecureTextField
24 import androidx.compose.foundation.text.KeyboardOptions
25 import androidx.compose.foundation.text.input.InputTransformation
26 import androidx.compose.foundation.text.input.KeyboardActionHandler
27 import androidx.compose.foundation.text.input.TextFieldState
28 import androidx.compose.foundation.text.input.TextObfuscationMode
29 import androidx.compose.material.TextFieldDefaults.indicatorLine
30 import androidx.compose.runtime.Composable
31 import androidx.compose.runtime.remember
32 import androidx.compose.ui.Modifier
33 import androidx.compose.ui.graphics.Shape
34 import androidx.compose.ui.graphics.SolidColor
35 import androidx.compose.ui.graphics.takeOrElse
36 import androidx.compose.ui.platform.LocalDensity
37 import androidx.compose.ui.semantics.semantics
38 import androidx.compose.ui.text.TextStyle
39 import androidx.compose.ui.text.input.ImeAction
40 import androidx.compose.ui.text.input.KeyboardType
41 import androidx.compose.ui.text.input.VisualTransformation
42 
43 /**
44  * [Material Design filled text field for secure
45  * content](https://m2.material.io/components/text-fields#filled-text-field)
46  *
47  * Text fields allow users to enter text into a UI. [SecureTextField] is specifically designed for
48  * password entry fields. It only supports a single line of content and comes with default settings
49  * that are appropriate for entering secure content. Additionally, some context menu actions like
50  * cut, copy, and drag are disabled for added security.
51  *
52  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
53  * when surrounded by other content and components. For an outlined version, see
54  * [OutlinedSecureTextField].
55  *
56  * Example of a password text field:
57  *
58  * @sample androidx.compose.material.samples.PasswordTextField
59  * @param state [TextFieldState] object that holds the internal editing state of this text field.
60  * @param modifier a [Modifier] for this text field.
61  * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
62  *   be neither editable nor focusable, the input of the text field will not be selectable, visually
63  *   text field will appear in the disabled UI state.
64  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
65  *   [LocalTextStyle] defined by the theme.
66  * @param label the optional label to be displayed inside the text field container. The default text
67  *   style for internal [Text] is [Typography.caption] when the text field is in focus and
68  *   [Typography.subtitle1] when the text field is not in focus.
69  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
70  *   the input text is empty. The default text style for internal [Text] is [Typography.subtitle1].
71  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
72  *   container.
73  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
74  *   container.
75  * @param isError indicates if the text field's current value is in error. If set to true, the
76  *   label, bottom indicator and trailing icon by default will be displayed in error color.
77  * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
78  *   to the [TextFieldState] made by the user. The transformation will be applied to changes made by
79  *   hardware and software keyboard events, pasting or dropping text, accessibility services, and
80  *   tests. The transformation will _not_ be applied when changing the [state] programmatically, or
81  *   when the transformation is changed. If the transformation is changed on an existing text field,
82  *   it will be applied to the next user edit. the transformation will not immediately affect the
83  *   current [state].
84  * @param textObfuscationMode the method used to obscure the input text.
85  * @param textObfuscationCharacter the character to use while obfuscating the text. It doesn't have
86  *   an effect when [textObfuscationMode] is set to [TextObfuscationMode.Visible].
87  * @param keyboardOptions software keyboard options that contains configuration such as
88  *   [KeyboardType] and [ImeAction].
89  * @param onKeyboardAction Called when the user presses the action button in the input method editor
90  *   (IME), or by pressing the enter key on a hardware keyboard. By default this parameter is null,
91  *   and would execute the default behavior for a received IME Action e.g., [ImeAction.Done] would
92  *   close the keyboard, [ImeAction.Next] would switch the focus to the next focusable item on the
93  *   screen.
94  * @param shape the shape of the text field's container
95  * @param colors [TextFieldColors] that will be used to resolve color of the text, content
96  *   (including label, placeholder, leading and trailing icons, indicator line) and background for
97  *   this text field in different states. See [TextFieldDefaults.textFieldColors]
98  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
99  *   emitting [Interaction]s for this text field. You can use this to change the text field's
100  *   appearance or preview the text field in different states. Note that if `null` is provided,
101  *   interactions will still happen internally.
102  */
103 @Composable
104 fun SecureTextField(
105     state: TextFieldState,
106     modifier: Modifier = Modifier,
107     enabled: Boolean = true,
108     textStyle: TextStyle = LocalTextStyle.current,
109     label: @Composable (() -> Unit)? = null,
110     placeholder: @Composable (() -> Unit)? = null,
111     leadingIcon: @Composable (() -> Unit)? = null,
112     trailingIcon: @Composable (() -> Unit)? = null,
113     isError: Boolean = false,
114     inputTransformation: InputTransformation? = null,
115     textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
116     textObfuscationCharacter: Char = DefaultObfuscationCharacter,
117     keyboardOptions: KeyboardOptions = SecureTextFieldKeyboardOptions,
118     onKeyboardAction: KeyboardActionHandler? = null,
119     shape: Shape = TextFieldDefaults.TextFieldShape,
120     colors: TextFieldColors = TextFieldDefaults.textFieldColors(),
121     interactionSource: MutableInteractionSource? = null,
122 ) {
123     @Suppress("NAME_SHADOWING")
124     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
125     // If color is not provided via the text style, use content color as a default
126     val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
127     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
128 
129     BasicSecureTextField(
130         state = state,
131         modifier =
132             modifier
133                 .indicatorLine(enabled, isError, interactionSource, colors)
134                 .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
135                 .defaultMinSize(
136                     minWidth = TextFieldDefaults.MinWidth,
137                     minHeight = TextFieldDefaults.MinHeight
138                 ),
139         enabled = enabled,
140         textStyle = mergedTextStyle,
141         cursorBrush = SolidColor(colors.cursorColor(isError).value),
142         inputTransformation = inputTransformation,
143         textObfuscationMode = textObfuscationMode,
144         textObfuscationCharacter = textObfuscationCharacter,
145         keyboardOptions = keyboardOptions,
146         onKeyboardAction = onKeyboardAction,
147         interactionSource = interactionSource,
148         decorator = { innerTextField ->
149             TextFieldDefaults.TextFieldDecorationBox(
150                 value = state.text.toString(),
151                 visualTransformation = VisualTransformation.None,
152                 innerTextField = innerTextField,
153                 placeholder = placeholder,
154                 label = label,
155                 leadingIcon = leadingIcon,
156                 trailingIcon = trailingIcon,
157                 singleLine = true,
158                 enabled = enabled,
159                 isError = isError,
160                 interactionSource = interactionSource,
161                 shape = shape,
162                 colors = colors,
163             )
164         }
165     )
166 }
167 
168 /**
169  * [Material Design outlined text field for secure
170  * content](https://m2.material.io/components/text-fields#outlined-text-field)
171  *
172  * Text fields allow users to enter text into a UI. [OutlinedSecureTextField] is specifically
173  * designed for password entry fields. It only supports a single line of content and comes with
174  * default settings that are appropriate for entering secure content. Additionally, some context
175  * menu actions like cut, copy, and drag are disabled for added security.
176  *
177  * Outlined text fields have less visual emphasis than filled text fields. When they appear in
178  * places like forms, where many text fields are placed together, their reduced emphasis helps
179  * simplify the layout. For a filled version, see [SecureTextField].
180  *
181  * @param state [TextFieldState] object that holds the internal editing state of this text field.
182  * @param modifier a [Modifier] for this text field
183  * @param enabled controls the enabled state of the [OutlinedTextField]. When `false`, the text
184  *   field will be neither editable nor focusable, the input of the text field will not be
185  *   selectable, visually text field will appear in the disabled UI state
186  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
187  *   [LocalTextStyle] defined by the theme
188  * @param label the optional label to be displayed inside the text field container. The default text
189  *   style for internal [Text] is [Typography.caption] when the text field is in focus and
190  *   [Typography.subtitle1] when the text field is not in focus
191  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
192  *   the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
193  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
194  *   container
195  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
196  *   container
197  * @param isError indicates if the text field's current value is in error. If set to true, the
198  *   label, bottom indicator and trailing icon by default will be displayed in error color
199  * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
200  *   to the [TextFieldState] made by the user. The transformation will be applied to changes made by
201  *   hardware and software keyboard events, pasting or dropping text, accessibility services, and
202  *   tests. The transformation will _not_ be applied when changing the [state] programmatically, or
203  *   when the transformation is changed. If the transformation is changed on an existing text field,
204  *   it will be applied to the next user edit. the transformation will not immediately affect the
205  *   current [state].
206  * @param textObfuscationMode the method used to obscure the input text.
207  * @param textObfuscationCharacter the character to use while obfuscating the text. It doesn't have
208  *   an effect when [textObfuscationMode] is set to [TextObfuscationMode.Visible].
209  * @param keyboardOptions software keyboard options that contains configuration such as
210  *   [KeyboardType] and [ImeAction]
211  * @param onKeyboardAction Called when the user presses the action button in the input method editor
212  *   (IME), or by pressing the enter key on a hardware keyboard. By default this parameter is null,
213  *   and would execute the default behavior for a received IME Action e.g., [ImeAction.Done] would
214  *   close the keyboard, [ImeAction.Next] would switch the focus to the next focusable item on the
215  *   screen.
216  * @param shape the shape of the text field's border
217  * @param colors [TextFieldColors] that will be used to resolve color of the text and content
218  *   (including label, placeholder, leading and trailing icons, border) for this text field in
219  *   different states. See [TextFieldDefaults.outlinedTextFieldColors]
220  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
221  *   emitting [Interaction]s for this text field. You can use this to change the text field's
222  *   appearance or preview the text field in different states. Note that if `null` is provided,
223  *   interactions will still happen internally.
224  */
225 @Composable
OutlinedSecureTextFieldnull226 fun OutlinedSecureTextField(
227     state: TextFieldState,
228     modifier: Modifier = Modifier,
229     enabled: Boolean = true,
230     textStyle: TextStyle = LocalTextStyle.current,
231     label: @Composable (() -> Unit)? = null,
232     placeholder: @Composable (() -> Unit)? = null,
233     leadingIcon: @Composable (() -> Unit)? = null,
234     trailingIcon: @Composable (() -> Unit)? = null,
235     isError: Boolean = false,
236     inputTransformation: InputTransformation? = null,
237     textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
238     textObfuscationCharacter: Char = DefaultObfuscationCharacter,
239     keyboardOptions: KeyboardOptions = SecureTextFieldKeyboardOptions,
240     onKeyboardAction: KeyboardActionHandler? = null,
241     shape: Shape = TextFieldDefaults.OutlinedTextFieldShape,
242     colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(),
243     interactionSource: MutableInteractionSource? = null,
244 ) {
245     @Suppress("NAME_SHADOWING")
246     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
247     // If color is not provided via the text style, use content color as a default
248     val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
249     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
250 
251     val density = LocalDensity.current
252 
253     BasicSecureTextField(
254         state = state,
255         modifier =
256             modifier
257                 .then(
258                     if (label != null) {
259                         Modifier
260                             // Merge semantics at the beginning of the modifier chain to ensure
261                             // padding is considered part of the text field.
262                             .semantics(mergeDescendants = true) {}
263                             .padding(top = with(density) { OutlinedTextFieldTopPadding.toDp() })
264                     } else {
265                         Modifier
266                     }
267                 )
268                 .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
269                 .defaultMinSize(
270                     minWidth = TextFieldDefaults.MinWidth,
271                     minHeight = TextFieldDefaults.MinHeight
272                 ),
273         enabled = enabled,
274         textStyle = mergedTextStyle,
275         cursorBrush = SolidColor(colors.cursorColor(isError).value),
276         inputTransformation = inputTransformation,
277         textObfuscationMode = textObfuscationMode,
278         textObfuscationCharacter = textObfuscationCharacter,
279         keyboardOptions = keyboardOptions,
280         onKeyboardAction = onKeyboardAction,
281         interactionSource = interactionSource,
282         decorator = { innerTextField ->
283             TextFieldDefaults.OutlinedTextFieldDecorationBox(
284                 value = state.text.toString(),
285                 visualTransformation = VisualTransformation.None,
286                 innerTextField = innerTextField,
287                 placeholder = placeholder,
288                 label = label,
289                 leadingIcon = leadingIcon,
290                 trailingIcon = trailingIcon,
291                 singleLine = true,
292                 enabled = enabled,
293                 isError = isError,
294                 interactionSource = interactionSource,
295                 shape = shape,
296                 colors = colors,
297                 border = {
298                     TextFieldDefaults.BorderBox(enabled, isError, interactionSource, colors, shape)
299                 }
300             )
301         }
302     )
303 }
304 
305 private val SecureTextFieldKeyboardOptions =
306     KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password)
307 
308 private const val DefaultObfuscationCharacter: Char = '\u2022'
309