1 /*
2  * Copyright 2021 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.animation.animateColorAsState
20 import androidx.compose.animation.core.animateDpAsState
21 import androidx.compose.animation.core.tween
22 import androidx.compose.foundation.BorderStroke
23 import androidx.compose.foundation.border
24 import androidx.compose.foundation.interaction.Interaction
25 import androidx.compose.foundation.interaction.InteractionSource
26 import androidx.compose.foundation.interaction.MutableInteractionSource
27 import androidx.compose.foundation.interaction.collectIsFocusedAsState
28 import androidx.compose.foundation.layout.Box
29 import androidx.compose.foundation.layout.PaddingValues
30 import androidx.compose.foundation.shape.ZeroCornerSize
31 import androidx.compose.foundation.text.BasicTextField
32 import androidx.compose.material.TextFieldDefaults.OutlinedTextFieldDecorationBox
33 import androidx.compose.runtime.Composable
34 import androidx.compose.runtime.Immutable
35 import androidx.compose.runtime.ReadOnlyComposable
36 import androidx.compose.runtime.Stable
37 import androidx.compose.runtime.State
38 import androidx.compose.runtime.getValue
39 import androidx.compose.runtime.rememberUpdatedState
40 import androidx.compose.ui.Modifier
41 import androidx.compose.ui.composed
42 import androidx.compose.ui.graphics.Color
43 import androidx.compose.ui.graphics.Shape
44 import androidx.compose.ui.graphics.SolidColor
45 import androidx.compose.ui.platform.debugInspectorInfo
46 import androidx.compose.ui.text.input.VisualTransformation
47 import androidx.compose.ui.unit.Dp
48 import androidx.compose.ui.unit.dp
49 
50 /**
51  * Represents the colors of the input text, background and content (including label, placeholder,
52  * leading and trailing icons) used in a text field in different states.
53  *
54  * See [TextFieldDefaults.textFieldColors] for the default colors used in [TextField]. See
55  * [TextFieldDefaults.outlinedTextFieldColors] for the default colors used in [OutlinedTextField].
56  */
57 @Stable
58 interface TextFieldColors {
59     /**
60      * Represents the color used for the input text of this text field.
61      *
62      * @param enabled whether the text field is enabled
63      */
textColornull64     @Composable fun textColor(enabled: Boolean): State<Color>
65 
66     /**
67      * Represents the background color for this text field.
68      *
69      * @param enabled whether the text field is enabled
70      */
71     @Composable fun backgroundColor(enabled: Boolean): State<Color>
72 
73     /**
74      * Represents the color used for the placeholder of this text field.
75      *
76      * @param enabled whether the text field is enabled
77      */
78     @Composable fun placeholderColor(enabled: Boolean): State<Color>
79 
80     /**
81      * Represents the color used for the label of this text field.
82      *
83      * @param enabled whether the text field is enabled
84      * @param error whether the text field should show error color according to the Material
85      *   specifications. If the label is being used as a placeholder, this will be false even if the
86      *   input is invalid, as the placeholder should not use the error color
87      * @param interactionSource the [InteractionSource] of this text field. Helps to determine if
88      *   the text field is in focus or not
89      */
90     @Composable
91     fun labelColor(
92         enabled: Boolean,
93         error: Boolean,
94         interactionSource: InteractionSource
95     ): State<Color>
96 
97     /**
98      * Represents the color used for the leading icon of this text field.
99      *
100      * @param enabled whether the text field is enabled
101      * @param isError whether the text field's current value is in error
102      */
103     @Deprecated(
104         message = "Use/implement overload with interactionSource parameter",
105         replaceWith = ReplaceWith("leadingIconColor(enabled, isError, interactionSource)"),
106         level = DeprecationLevel.WARNING,
107     )
108     @Composable
109     fun leadingIconColor(enabled: Boolean, isError: Boolean): State<Color>
110 
111     /**
112      * Represents the color used for the leading icon of this text field.
113      *
114      * @param enabled whether the text field is enabled
115      * @param isError whether the text field's current value is in error
116      * @param interactionSource the [InteractionSource] of this text field. Helps to determine if
117      *   the text field is in focus or not
118      */
119     @Composable
120     fun leadingIconColor(
121         enabled: Boolean,
122         isError: Boolean,
123         interactionSource: InteractionSource
124     ): State<Color> {
125         @Suppress("DEPRECATION") return leadingIconColor(enabled, isError)
126     }
127 
128     /**
129      * Represents the color used for the trailing icon of this text field.
130      *
131      * @param enabled whether the text field is enabled
132      * @param isError whether the text field's current value is in error
133      */
134     @Deprecated(
135         message = "Use/implement overload with interactionSource parameter",
136         replaceWith = ReplaceWith("trailingIconColor(enabled, isError, interactionSource)"),
137         level = DeprecationLevel.WARNING,
138     )
139     @Composable
trailingIconColornull140     fun trailingIconColor(enabled: Boolean, isError: Boolean): State<Color>
141 
142     /**
143      * Represents the color used for the trailing icon of this text field.
144      *
145      * @param enabled whether the text field is enabled
146      * @param isError whether the text field's current value is in error
147      * @param interactionSource the [InteractionSource] of this text field. Helps to determine if
148      *   the text field is in focus or not
149      */
150     @Composable
151     fun trailingIconColor(
152         enabled: Boolean,
153         isError: Boolean,
154         interactionSource: InteractionSource
155     ): State<Color> {
156         @Suppress("DEPRECATION") return trailingIconColor(enabled, isError)
157     }
158 
159     /**
160      * Represents the color used for the border indicator of this text field.
161      *
162      * @param enabled whether the text field is enabled
163      * @param isError whether the text field's current value is in error
164      * @param interactionSource the [InteractionSource] of this text field. Helps to determine if
165      *   the text field is in focus or not
166      */
167     @Composable
indicatorColornull168     fun indicatorColor(
169         enabled: Boolean,
170         isError: Boolean,
171         interactionSource: InteractionSource
172     ): State<Color>
173 
174     /**
175      * Represents the color used for the cursor of this text field.
176      *
177      * @param isError whether the text field's current value is in error
178      */
179     @Composable fun cursorColor(isError: Boolean): State<Color>
180 }
181 
182 /**
183  * Temporary experimental interface, to expose interactionSource to leadingIconColor and
184  * trailingIconColor.
185  */
186 @Deprecated(
187     message = "Empty interface; use parent TextFieldColors instead",
188     replaceWith =
189         ReplaceWith("TextFieldColors", imports = ["androidx.compose.material.TextFieldColors"])
190 )
191 @ExperimentalMaterialApi
192 interface TextFieldColorsWithIcons : TextFieldColors
193 
194 /** Contains the default values used by [TextField] and [OutlinedTextField]. */
195 @Immutable
196 object TextFieldDefaults {
197     /**
198      * The default min height applied to a [TextField] and [OutlinedTextField]. Note that you can
199      * override it by applying Modifier.heightIn directly on a text field.
200      */
201     val MinHeight = 56.dp
202 
203     /**
204      * The default min width applied to a [TextField] and [OutlinedTextField]. Note that you can
205      * override it by applying Modifier.widthIn directly on a text field.
206      */
207     val MinWidth = 280.dp
208 
209     /**
210      * The default opacity used for a [TextField]'s and [OutlinedTextField]'s leading and trailing
211      * icons color.
212      */
213     const val IconOpacity = 0.54f
214 
215     /** The default shape used for a [TextField]'s background */
216     val TextFieldShape: Shape
217         @Composable
218         @ReadOnlyComposable
219         get() =
220             MaterialTheme.shapes.small.copy(
221                 bottomEnd = ZeroCornerSize,
222                 bottomStart = ZeroCornerSize
223             )
224 
225     /** The default shape used for a [OutlinedTextField]'s background and border */
226     val OutlinedTextFieldShape: Shape
227         @Composable @ReadOnlyComposable get() = MaterialTheme.shapes.small
228 
229     /**
230      * The default thickness of the border in [OutlinedTextField] or indicator line in [TextField]
231      * in unfocused state.
232      */
233     val UnfocusedBorderThickness = 1.dp
234 
235     /**
236      * The default thickness of the border in [OutlinedTextField] or indicator line in [TextField]
237      * in focused state.
238      */
239     val FocusedBorderThickness = 2.dp
240 
241     /** The default opacity used for a [TextField]'s background color. */
242     const val BackgroundOpacity = 0.12f
243 
244     // Filled text field uses 42% opacity to meet the contrast requirements for accessibility
245     // reasons
246     /**
247      * The default opacity used for a [TextField]'s indicator line color when text field is not
248      * focused.
249      */
250     const val UnfocusedIndicatorLineOpacity = 0.42f
251 
252     /**
253      * A modifier to draw a default bottom indicator line for [TextField]. You can use this modifier
254      * if you build your custom text field using [TextFieldDecorationBox]. The [TextField] component
255      * applies it automatically.
256      *
257      * @param enabled whether the text field is enabled.
258      * @param isError whether the text field's current value is in error.
259      * @param interactionSource the [InteractionSource] of this text field. Used to determine if the
260      *   text field is in focus or not.
261      * @param colors [TextFieldColors] used to resolve colors of the text field.
262      * @param focusedIndicatorLineThickness thickness of the indicator line when text field is
263      *   focused.
264      * @param unfocusedIndicatorLineThickness thickness of the indicator line when text field is not
265      *   focused.
266      */
267     fun Modifier.indicatorLine(
268         enabled: Boolean,
269         isError: Boolean,
270         interactionSource: InteractionSource,
271         colors: TextFieldColors,
272         focusedIndicatorLineThickness: Dp = FocusedBorderThickness,
273         unfocusedIndicatorLineThickness: Dp = UnfocusedBorderThickness
274     ) =
275         composed(
276             inspectorInfo =
277                 debugInspectorInfo {
278                     name = "indicatorLine"
279                     properties["enabled"] = enabled
280                     properties["isError"] = isError
281                     properties["interactionSource"] = interactionSource
282                     properties["colors"] = colors
283                     properties["focusedIndicatorLineThickness"] = focusedIndicatorLineThickness
284                     properties["unfocusedIndicatorLineThickness"] = unfocusedIndicatorLineThickness
285                 }
286         ) {
287             val stroke =
288                 animateBorderStrokeAsState(
289                     enabled = enabled,
290                     isError = isError,
291                     interactionSource = interactionSource,
292                     colors = colors,
293                     focusedBorderThickness = focusedIndicatorLineThickness,
294                     unfocusedBorderThickness = unfocusedIndicatorLineThickness,
295                 )
296             Modifier.drawIndicatorLine(stroke.value)
297         }
298 
299     /**
300      * Composable that draws a default border stroke in [OutlinedTextField]. You can use it to draw
301      * a border stroke in your custom text field based on [OutlinedTextFieldDecorationBox]. The
302      * [OutlinedTextField] component applies it automatically.
303      *
304      * @param enabled whether the text field is enabled.
305      * @param isError whether the text field's current value is in error.
306      * @param interactionSource the [InteractionSource] of this text field. Used to determine if the
307      *   text field is in focus or not.
308      * @param colors [TextFieldColors] used to resolve colors of the text field.
309      * @param focusedBorderThickness thickness of the [OutlinedTextField]'s border when it is in
310      *   focused state.
311      * @param unfocusedBorderThickness thickness of the [OutlinedTextField]'s border when it is not
312      *   in focused state.
313      */
314     @Composable
315     fun BorderBox(
316         enabled: Boolean,
317         isError: Boolean,
318         interactionSource: InteractionSource,
319         colors: TextFieldColors,
320         shape: Shape = OutlinedTextFieldShape,
321         focusedBorderThickness: Dp = FocusedBorderThickness,
322         unfocusedBorderThickness: Dp = UnfocusedBorderThickness
323     ) {
324         val borderStroke =
325             animateBorderStrokeAsState(
326                 enabled = enabled,
327                 isError = isError,
328                 interactionSource = interactionSource,
329                 colors = colors,
330                 focusedBorderThickness = focusedBorderThickness,
331                 unfocusedBorderThickness = unfocusedBorderThickness
332             )
333         Box(Modifier.border(borderStroke.value, shape))
334     }
335 
336     /**
337      * Default content padding applied to [TextField] when there is a label.
338      *
339      * Note that when the label is present, the "top" padding (unlike rest of the paddings) is a
340      * distance between the label's last baseline and the top edge of the [TextField]. If the "top"
341      * value is smaller than the last baseline of the label, then there will be no space between the
342      * label and top edge of the [TextField].
343      */
344     fun textFieldWithLabelPadding(
345         start: Dp = TextFieldPadding,
346         end: Dp = TextFieldPadding,
347         top: Dp = FirstBaselineOffset,
348         bottom: Dp = TextFieldBottomPadding
349     ): PaddingValues = PaddingValues(start, top, end, bottom)
350 
351     /** Default content padding applied to [TextField] when the label is null. */
352     fun textFieldWithoutLabelPadding(
353         start: Dp = TextFieldPadding,
354         top: Dp = TextFieldPadding,
355         end: Dp = TextFieldPadding,
356         bottom: Dp = TextFieldPadding
357     ): PaddingValues = PaddingValues(start, top, end, bottom)
358 
359     /** Default content padding applied to [OutlinedTextField]. */
360     fun outlinedTextFieldPadding(
361         start: Dp = TextFieldPadding,
362         top: Dp = TextFieldPadding,
363         end: Dp = TextFieldPadding,
364         bottom: Dp = TextFieldPadding
365     ): PaddingValues = PaddingValues(start, top, end, bottom)
366 
367     /**
368      * Creates a [TextFieldColors] that represents the default input text, background and content
369      * (including label, placeholder, leading and trailing icons) colors used in a [TextField].
370      */
371     @Composable
372     fun textFieldColors(
373         textColor: Color = LocalContentColor.current.copy(LocalContentAlpha.current),
374         disabledTextColor: Color = textColor.copy(ContentAlpha.disabled),
375         backgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = BackgroundOpacity),
376         cursorColor: Color = MaterialTheme.colors.primary,
377         errorCursorColor: Color = MaterialTheme.colors.error,
378         focusedIndicatorColor: Color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high),
379         unfocusedIndicatorColor: Color =
380             MaterialTheme.colors.onSurface.copy(alpha = UnfocusedIndicatorLineOpacity),
381         disabledIndicatorColor: Color = unfocusedIndicatorColor.copy(alpha = ContentAlpha.disabled),
382         errorIndicatorColor: Color = MaterialTheme.colors.error,
383         leadingIconColor: Color = MaterialTheme.colors.onSurface.copy(alpha = IconOpacity),
384         disabledLeadingIconColor: Color = leadingIconColor.copy(alpha = ContentAlpha.disabled),
385         errorLeadingIconColor: Color = leadingIconColor,
386         trailingIconColor: Color = MaterialTheme.colors.onSurface.copy(alpha = IconOpacity),
387         disabledTrailingIconColor: Color = trailingIconColor.copy(alpha = ContentAlpha.disabled),
388         errorTrailingIconColor: Color = MaterialTheme.colors.error,
389         focusedLabelColor: Color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high),
390         unfocusedLabelColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
391         disabledLabelColor: Color = unfocusedLabelColor.copy(ContentAlpha.disabled),
392         errorLabelColor: Color = MaterialTheme.colors.error,
393         placeholderColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
394         disabledPlaceholderColor: Color = placeholderColor.copy(ContentAlpha.disabled)
395     ): TextFieldColors =
396         DefaultTextFieldColors(
397             textColor = textColor,
398             disabledTextColor = disabledTextColor,
399             cursorColor = cursorColor,
400             errorCursorColor = errorCursorColor,
401             focusedIndicatorColor = focusedIndicatorColor,
402             unfocusedIndicatorColor = unfocusedIndicatorColor,
403             errorIndicatorColor = errorIndicatorColor,
404             disabledIndicatorColor = disabledIndicatorColor,
405             leadingIconColor = leadingIconColor,
406             disabledLeadingIconColor = disabledLeadingIconColor,
407             errorLeadingIconColor = errorLeadingIconColor,
408             trailingIconColor = trailingIconColor,
409             disabledTrailingIconColor = disabledTrailingIconColor,
410             errorTrailingIconColor = errorTrailingIconColor,
411             backgroundColor = backgroundColor,
412             focusedLabelColor = focusedLabelColor,
413             unfocusedLabelColor = unfocusedLabelColor,
414             disabledLabelColor = disabledLabelColor,
415             errorLabelColor = errorLabelColor,
416             placeholderColor = placeholderColor,
417             disabledPlaceholderColor = disabledPlaceholderColor
418         )
419 
420     /**
421      * Creates a [TextFieldColors] that represents the default input text, background and content
422      * (including label, placeholder, leading and trailing icons) colors used in an
423      * [OutlinedTextField].
424      */
425     @Composable
426     fun outlinedTextFieldColors(
427         textColor: Color = LocalContentColor.current.copy(LocalContentAlpha.current),
428         disabledTextColor: Color = textColor.copy(ContentAlpha.disabled),
429         backgroundColor: Color = Color.Transparent,
430         cursorColor: Color = MaterialTheme.colors.primary,
431         errorCursorColor: Color = MaterialTheme.colors.error,
432         focusedBorderColor: Color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high),
433         unfocusedBorderColor: Color =
434             MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
435         disabledBorderColor: Color = unfocusedBorderColor.copy(alpha = ContentAlpha.disabled),
436         errorBorderColor: Color = MaterialTheme.colors.error,
437         leadingIconColor: Color = MaterialTheme.colors.onSurface.copy(alpha = IconOpacity),
438         disabledLeadingIconColor: Color = leadingIconColor.copy(alpha = ContentAlpha.disabled),
439         errorLeadingIconColor: Color = leadingIconColor,
440         trailingIconColor: Color = MaterialTheme.colors.onSurface.copy(alpha = IconOpacity),
441         disabledTrailingIconColor: Color = trailingIconColor.copy(alpha = ContentAlpha.disabled),
442         errorTrailingIconColor: Color = MaterialTheme.colors.error,
443         focusedLabelColor: Color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high),
444         unfocusedLabelColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
445         disabledLabelColor: Color = unfocusedLabelColor.copy(ContentAlpha.disabled),
446         errorLabelColor: Color = MaterialTheme.colors.error,
447         placeholderColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
448         disabledPlaceholderColor: Color = placeholderColor.copy(ContentAlpha.disabled)
449     ): TextFieldColors =
450         DefaultTextFieldColors(
451             textColor = textColor,
452             disabledTextColor = disabledTextColor,
453             cursorColor = cursorColor,
454             errorCursorColor = errorCursorColor,
455             focusedIndicatorColor = focusedBorderColor,
456             unfocusedIndicatorColor = unfocusedBorderColor,
457             errorIndicatorColor = errorBorderColor,
458             disabledIndicatorColor = disabledBorderColor,
459             leadingIconColor = leadingIconColor,
460             disabledLeadingIconColor = disabledLeadingIconColor,
461             errorLeadingIconColor = errorLeadingIconColor,
462             trailingIconColor = trailingIconColor,
463             disabledTrailingIconColor = disabledTrailingIconColor,
464             errorTrailingIconColor = errorTrailingIconColor,
465             backgroundColor = backgroundColor,
466             focusedLabelColor = focusedLabelColor,
467             unfocusedLabelColor = unfocusedLabelColor,
468             disabledLabelColor = disabledLabelColor,
469             errorLabelColor = errorLabelColor,
470             placeholderColor = placeholderColor,
471             disabledPlaceholderColor = disabledPlaceholderColor
472         )
473 
474     /**
475      * A decoration box used to create custom text fields based on
476      * [Material Design filled text field](https://m2.material.io/components/text-fields#filled-text-field).
477      *
478      * If your text field requires customising elements that aren't exposed by [TextField], consider
479      * using this decoration box to achieve the desired design.
480      *
481      * For example, if you need to create a dense text field, use [contentPadding] parameter to
482      * decrease the paddings around the input field. If you need to customise the bottom indicator,
483      * apply [indicatorLine] modifier to achieve that.
484      *
485      * Example of custom text field based on [TextFieldDecorationBox]:
486      *
487      * @sample androidx.compose.material.samples.CustomTextFieldBasedOnDecorationBox
488      * @param value the input [String] shown by the text field
489      * @param innerTextField input text field that this decoration box wraps. Pass the
490      *   framework-controlled composable parameter `innerTextField` from the `decorationBox` lambda
491      *   of the [BasicTextField].
492      * @param enabled the enabled state of the text field. When `false`, this decoration box will
493      *   appear visually disabled. This must be the same value that is passed to [BasicTextField].
494      * @param singleLine indicates if this is a single line or multi line text field. This must be
495      *   the same value that is passed to [BasicTextField].
496      * @param visualTransformation transforms the visual representation of the input [value]. This
497      *   must be the same value that is passed to [BasicTextField].
498      * @param interactionSource the read-only [InteractionSource] representing the stream of
499      *   [Interaction]s for this text field. You must first create and pass in your own `remember`ed
500      *   [MutableInteractionSource] instance to the [BasicTextField] for it to dispatch events. And
501      *   then pass the same instance to this decoration box to observe [Interaction]s and customize
502      *   the appearance / behavior of this text field in different states.
503      * @param isError indicates if the text field's current value is in an error state. When `true`,
504      *   this decoration box will display its contents in an error color.
505      * @param label the optional label to be displayed inside the text field container. The default
506      *   text style uses [Typography.caption] when the label is minimized and [Typography.subtitle1]
507      *   when the label is expanded.
508      * @param placeholder the optional placeholder to be displayed when the text field is in focus
509      *   and the input text is empty. The default text style for internal [Text] is
510      *   [Typography.subtitle1].
511      * @param leadingIcon the optional leading icon to be displayed at the beginning of the text
512      *   field container.
513      * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
514      *   container.
515      * @param shape the shape of the text field's container.
516      * @param colors [TextFieldColors] that will be used to resolve the colors used for this text
517      *   field in different states. See [TextFieldDefaults.textFieldColors].
518      * @param contentPadding the spacing values to apply internally between the internals of text
519      *   field and the decoration box container. You can use it to implement dense text fields or
520      *   simply to control horizontal padding. Note that the padding values may not be respected if
521      *   they are incompatible with the text field's size constraints or layout. See
522      *   [TextFieldDefaults.textFieldWithLabelPadding] and
523      *   [TextFieldDefaults.textFieldWithoutLabelPadding].
524      */
525     @Composable
526     fun TextFieldDecorationBox(
527         value: String,
528         innerTextField: @Composable () -> Unit,
529         enabled: Boolean,
530         singleLine: Boolean,
531         visualTransformation: VisualTransformation,
532         interactionSource: InteractionSource,
533         isError: Boolean = false,
534         label: @Composable (() -> Unit)? = null,
535         placeholder: @Composable (() -> Unit)? = null,
536         leadingIcon: @Composable (() -> Unit)? = null,
537         trailingIcon: @Composable (() -> Unit)? = null,
538         shape: Shape = TextFieldShape,
539         colors: TextFieldColors = textFieldColors(),
540         contentPadding: PaddingValues =
541             if (label == null) {
542                 textFieldWithoutLabelPadding()
543             } else {
544                 textFieldWithLabelPadding()
545             }
546     ) {
547         CommonDecorationBox(
548             type = TextFieldType.Filled,
549             value = value,
550             innerTextField = innerTextField,
551             visualTransformation = visualTransformation,
552             placeholder = placeholder,
553             label = label,
554             leadingIcon = leadingIcon,
555             trailingIcon = trailingIcon,
556             singleLine = singleLine,
557             enabled = enabled,
558             isError = isError,
559             interactionSource = interactionSource,
560             shape = shape,
561             colors = colors,
562             contentPadding = contentPadding,
563             border = null,
564         )
565     }
566 
567     /**
568      * A decoration box used to create custom text fields based on
569      * [Material Design outlined text field](https://m2.material.io/components/text-fields#outlined-text-field).
570      *
571      * If your text field requires customising elements that aren't exposed by [OutlinedTextField],
572      * consider using this decoration box to achieve the desired design.
573      *
574      * For example, if you need to create a dense outlined text field, use [contentPadding]
575      * parameter to decrease the paddings around the input field. If you need to change the
576      * thickness of the border, use [border] parameter to achieve that.
577      *
578      * Example of custom text field based on [OutlinedTextFieldDecorationBox]:
579      *
580      * @sample androidx.compose.material.samples.CustomOutlinedTextFieldBasedOnDecorationBox
581      * @param value the input [String] shown by the text field
582      * @param innerTextField input text field that this decoration box wraps. Pass the
583      *   framework-controlled composable parameter `innerTextField` from the `decorationBox` lambda
584      *   of the [BasicTextField].
585      * @param enabled the enabled state of the text field. When `false`, this decoration box will
586      *   appear visually disabled. This must be the same value that is passed to [BasicTextField].
587      * @param singleLine indicates if this is a single line or multi line text field. This must be
588      *   the same value that is passed to [BasicTextField].
589      * @param visualTransformation transforms the visual representation of the input [value]. This
590      *   must be the same value that is passed to [BasicTextField].
591      * @param interactionSource the read-only [InteractionSource] representing the stream of
592      *   [Interaction]s for this text field. You must first create and pass in your own `remember`ed
593      *   [MutableInteractionSource] instance to the [BasicTextField] for it to dispatch events. And
594      *   then pass the same instance to this decoration box to observe [Interaction]s and customize
595      *   the appearance / behavior of this text field in different states.
596      * @param isError indicates if the text field's current value is in an error state. When `true`,
597      *   this decoration box will display its contents in an error color.
598      * @param label the optional label to be displayed inside the text field container. The default
599      *   text style uses [Typography.caption] when the label is minimized and [Typography.subtitle1]
600      *   when the label is expanded.
601      * @param placeholder the optional placeholder to be displayed when the text field is in focus
602      *   and the input text is empty. The default text style for internal [Text] is
603      *   [Typography.subtitle1].
604      * @param leadingIcon the optional leading icon to be displayed at the beginning of the text
605      *   field container.
606      * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
607      *   container.
608      * @param shape the shape of the text field's container and border.
609      * @param colors [TextFieldColors] that will be used to resolve the colors used for this text
610      *   field in different states. See [TextFieldDefaults.outlinedTextFieldColors].
611      * @param border the border to be drawn around the text field. The cutout to fit the [label]
612      *   will be automatically added by the framework. Note that by default the color of the border
613      *   comes from the [colors].
614      * @param contentPadding the spacing values to apply internally between the internals of text
615      *   field and the decoration box container. You can use it to implement dense text fields or
616      *   simply to control horizontal padding. Note that the padding values may not be respected if
617      *   they are incompatible with the text field's size constraints or layout. See
618      *   [TextFieldDefaults.outlinedTextFieldPadding].
619      */
620     @Composable
621     fun OutlinedTextFieldDecorationBox(
622         value: String,
623         innerTextField: @Composable () -> Unit,
624         enabled: Boolean,
625         singleLine: Boolean,
626         visualTransformation: VisualTransformation,
627         interactionSource: InteractionSource,
628         isError: Boolean = false,
629         label: @Composable (() -> Unit)? = null,
630         placeholder: @Composable (() -> Unit)? = null,
631         leadingIcon: @Composable (() -> Unit)? = null,
632         trailingIcon: @Composable (() -> Unit)? = null,
633         shape: Shape = OutlinedTextFieldShape,
634         colors: TextFieldColors = outlinedTextFieldColors(),
635         contentPadding: PaddingValues = outlinedTextFieldPadding(),
636         border: @Composable () -> Unit = {
637             BorderBox(enabled, isError, interactionSource, colors, shape)
638         }
639     ) {
640         CommonDecorationBox(
641             type = TextFieldType.Outlined,
642             value = value,
643             visualTransformation = visualTransformation,
644             innerTextField = innerTextField,
645             placeholder = placeholder,
646             label = label,
647             leadingIcon = leadingIcon,
648             trailingIcon = trailingIcon,
649             singleLine = singleLine,
650             enabled = enabled,
651             isError = isError,
652             interactionSource = interactionSource,
653             shape = shape,
654             colors = colors,
655             contentPadding = contentPadding,
656             border = border,
657         )
658     }
659 
660     @Deprecated(
661         level = DeprecationLevel.HIDDEN,
662         message = "Maintained for binary compatibility. Use overload with `shape` parameter."
663     )
664     @Composable
665     @ExperimentalMaterialApi
666     fun TextFieldDecorationBox(
667         value: String,
668         innerTextField: @Composable () -> Unit,
669         enabled: Boolean,
670         singleLine: Boolean,
671         visualTransformation: VisualTransformation,
672         interactionSource: InteractionSource,
673         isError: Boolean = false,
674         label: @Composable (() -> Unit)? = null,
675         placeholder: @Composable (() -> Unit)? = null,
676         leadingIcon: @Composable (() -> Unit)? = null,
677         trailingIcon: @Composable (() -> Unit)? = null,
678         colors: TextFieldColors = textFieldColors(),
679         contentPadding: PaddingValues =
680             if (label == null) {
681                 textFieldWithoutLabelPadding()
682             } else {
683                 textFieldWithLabelPadding()
684             }
685     ) =
686         TextFieldDecorationBox(
687             value = value,
688             innerTextField = innerTextField,
689             enabled = enabled,
690             singleLine = singleLine,
691             visualTransformation = visualTransformation,
692             interactionSource = interactionSource,
693             isError = isError,
694             label = label,
695             placeholder = placeholder,
696             leadingIcon = leadingIcon,
697             trailingIcon = trailingIcon,
698             shape = TextFieldShape,
699             colors = colors,
700             contentPadding = contentPadding,
701         )
702 
703     @Deprecated(
704         level = DeprecationLevel.HIDDEN,
705         message = "Maintained for binary compatibility. Use overload with `shape` parameter."
706     )
707     @Composable
708     @ExperimentalMaterialApi
709     fun OutlinedTextFieldDecorationBox(
710         value: String,
711         innerTextField: @Composable () -> Unit,
712         enabled: Boolean,
713         singleLine: Boolean,
714         visualTransformation: VisualTransformation,
715         interactionSource: InteractionSource,
716         isError: Boolean = false,
717         label: @Composable (() -> Unit)? = null,
718         placeholder: @Composable (() -> Unit)? = null,
719         leadingIcon: @Composable (() -> Unit)? = null,
720         trailingIcon: @Composable (() -> Unit)? = null,
721         colors: TextFieldColors = outlinedTextFieldColors(),
722         contentPadding: PaddingValues = outlinedTextFieldPadding(),
723         border: @Composable () -> Unit = { BorderBox(enabled, isError, interactionSource, colors) }
724     ) =
725         OutlinedTextFieldDecorationBox(
726             value = value,
727             innerTextField = innerTextField,
728             enabled = enabled,
729             singleLine = singleLine,
730             visualTransformation = visualTransformation,
731             interactionSource = interactionSource,
732             isError = isError,
733             label = label,
734             placeholder = placeholder,
735             leadingIcon = leadingIcon,
736             trailingIcon = trailingIcon,
737             shape = OutlinedTextFieldShape,
738             colors = colors,
739             contentPadding = contentPadding,
740             border = border,
741         )
742 }
743 
744 @Immutable
745 private class DefaultTextFieldColors(
746     private val textColor: Color,
747     private val disabledTextColor: Color,
748     private val cursorColor: Color,
749     private val errorCursorColor: Color,
750     private val focusedIndicatorColor: Color,
751     private val unfocusedIndicatorColor: Color,
752     private val errorIndicatorColor: Color,
753     private val disabledIndicatorColor: Color,
754     private val leadingIconColor: Color,
755     private val disabledLeadingIconColor: Color,
756     private val errorLeadingIconColor: Color,
757     private val trailingIconColor: Color,
758     private val disabledTrailingIconColor: Color,
759     private val errorTrailingIconColor: Color,
760     private val backgroundColor: Color,
761     private val focusedLabelColor: Color,
762     private val unfocusedLabelColor: Color,
763     private val disabledLabelColor: Color,
764     private val errorLabelColor: Color,
765     private val placeholderColor: Color,
766     private val disabledPlaceholderColor: Color
767 ) : TextFieldColors {
768 
769     @Suppress("OVERRIDE_DEPRECATION") // b/407490794
770     @Composable
leadingIconColornull771     override fun leadingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
772         return rememberUpdatedState(
773             when {
774                 !enabled -> disabledLeadingIconColor
775                 isError -> errorLeadingIconColor
776                 else -> leadingIconColor
777             }
778         )
779     }
780 
781     @Composable
leadingIconColornull782     override fun leadingIconColor(
783         enabled: Boolean,
784         isError: Boolean,
785         interactionSource: InteractionSource,
786     ): State<Color> {
787         return rememberUpdatedState(
788             when {
789                 !enabled -> disabledLeadingIconColor
790                 isError -> errorLeadingIconColor
791                 else -> leadingIconColor
792             }
793         )
794     }
795 
796     @Suppress("OVERRIDE_DEPRECATION") // b/407490794
797     @Composable
trailingIconColornull798     override fun trailingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
799         return rememberUpdatedState(
800             when {
801                 !enabled -> disabledTrailingIconColor
802                 isError -> errorTrailingIconColor
803                 else -> trailingIconColor
804             }
805         )
806     }
807 
808     @Composable
trailingIconColornull809     override fun trailingIconColor(
810         enabled: Boolean,
811         isError: Boolean,
812         interactionSource: InteractionSource,
813     ): State<Color> {
814         return rememberUpdatedState(
815             when {
816                 !enabled -> disabledTrailingIconColor
817                 isError -> errorTrailingIconColor
818                 else -> trailingIconColor
819             }
820         )
821     }
822 
823     @Composable
indicatorColornull824     override fun indicatorColor(
825         enabled: Boolean,
826         isError: Boolean,
827         interactionSource: InteractionSource
828     ): State<Color> {
829         val focused by interactionSource.collectIsFocusedAsState()
830 
831         val targetValue =
832             when {
833                 !enabled -> disabledIndicatorColor
834                 isError -> errorIndicatorColor
835                 focused -> focusedIndicatorColor
836                 else -> unfocusedIndicatorColor
837             }
838         return if (enabled) {
839             animateColorAsState(targetValue, tween(durationMillis = AnimationDuration))
840         } else {
841             rememberUpdatedState(targetValue)
842         }
843     }
844 
845     @Composable
backgroundColornull846     override fun backgroundColor(enabled: Boolean): State<Color> {
847         return rememberUpdatedState(backgroundColor)
848     }
849 
850     @Composable
placeholderColornull851     override fun placeholderColor(enabled: Boolean): State<Color> {
852         return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
853     }
854 
855     @Composable
labelColornull856     override fun labelColor(
857         enabled: Boolean,
858         error: Boolean,
859         interactionSource: InteractionSource
860     ): State<Color> {
861         val focused by interactionSource.collectIsFocusedAsState()
862 
863         val targetValue =
864             when {
865                 !enabled -> disabledLabelColor
866                 error -> errorLabelColor
867                 focused -> focusedLabelColor
868                 else -> unfocusedLabelColor
869             }
870         return rememberUpdatedState(targetValue)
871     }
872 
873     @Composable
textColornull874     override fun textColor(enabled: Boolean): State<Color> {
875         return rememberUpdatedState(if (enabled) textColor else disabledTextColor)
876     }
877 
878     @Composable
cursorColornull879     override fun cursorColor(isError: Boolean): State<Color> {
880         return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
881     }
882 
equalsnull883     override fun equals(other: Any?): Boolean {
884         if (this === other) return true
885         if (other == null || this::class != other::class) return false
886 
887         other as DefaultTextFieldColors
888 
889         if (textColor != other.textColor) return false
890         if (disabledTextColor != other.disabledTextColor) return false
891         if (cursorColor != other.cursorColor) return false
892         if (errorCursorColor != other.errorCursorColor) return false
893         if (focusedIndicatorColor != other.focusedIndicatorColor) return false
894         if (unfocusedIndicatorColor != other.unfocusedIndicatorColor) return false
895         if (errorIndicatorColor != other.errorIndicatorColor) return false
896         if (disabledIndicatorColor != other.disabledIndicatorColor) return false
897         if (leadingIconColor != other.leadingIconColor) return false
898         if (disabledLeadingIconColor != other.disabledLeadingIconColor) return false
899         if (errorLeadingIconColor != other.errorLeadingIconColor) return false
900         if (trailingIconColor != other.trailingIconColor) return false
901         if (disabledTrailingIconColor != other.disabledTrailingIconColor) return false
902         if (errorTrailingIconColor != other.errorTrailingIconColor) return false
903         if (backgroundColor != other.backgroundColor) return false
904         if (focusedLabelColor != other.focusedLabelColor) return false
905         if (unfocusedLabelColor != other.unfocusedLabelColor) return false
906         if (disabledLabelColor != other.disabledLabelColor) return false
907         if (errorLabelColor != other.errorLabelColor) return false
908         if (placeholderColor != other.placeholderColor) return false
909         if (disabledPlaceholderColor != other.disabledPlaceholderColor) return false
910 
911         return true
912     }
913 
hashCodenull914     override fun hashCode(): Int {
915         var result = textColor.hashCode()
916         result = 31 * result + disabledTextColor.hashCode()
917         result = 31 * result + cursorColor.hashCode()
918         result = 31 * result + errorCursorColor.hashCode()
919         result = 31 * result + focusedIndicatorColor.hashCode()
920         result = 31 * result + unfocusedIndicatorColor.hashCode()
921         result = 31 * result + errorIndicatorColor.hashCode()
922         result = 31 * result + disabledIndicatorColor.hashCode()
923         result = 31 * result + leadingIconColor.hashCode()
924         result = 31 * result + disabledLeadingIconColor.hashCode()
925         result = 31 * result + errorLeadingIconColor.hashCode()
926         result = 31 * result + trailingIconColor.hashCode()
927         result = 31 * result + disabledTrailingIconColor.hashCode()
928         result = 31 * result + errorTrailingIconColor.hashCode()
929         result = 31 * result + backgroundColor.hashCode()
930         result = 31 * result + focusedLabelColor.hashCode()
931         result = 31 * result + unfocusedLabelColor.hashCode()
932         result = 31 * result + disabledLabelColor.hashCode()
933         result = 31 * result + errorLabelColor.hashCode()
934         result = 31 * result + placeholderColor.hashCode()
935         result = 31 * result + disabledPlaceholderColor.hashCode()
936         return result
937     }
938 }
939 
940 @Composable
animateBorderStrokeAsStatenull941 private fun animateBorderStrokeAsState(
942     enabled: Boolean,
943     isError: Boolean,
944     interactionSource: InteractionSource,
945     colors: TextFieldColors,
946     focusedBorderThickness: Dp,
947     unfocusedBorderThickness: Dp
948 ): State<BorderStroke> {
949     val focused by interactionSource.collectIsFocusedAsState()
950     val indicatorColor = colors.indicatorColor(enabled, isError, interactionSource)
951     val targetThickness = if (focused) focusedBorderThickness else unfocusedBorderThickness
952     val animatedThickness =
953         if (enabled) {
954             animateDpAsState(targetThickness, tween(durationMillis = AnimationDuration))
955         } else {
956             rememberUpdatedState(unfocusedBorderThickness)
957         }
958     return rememberUpdatedState(
959         BorderStroke(animatedThickness.value, SolidColor(indicatorColor.value))
960     )
961 }
962