1 /*
<lambda>null2  * Copyright 2022 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.material3
18 
19 import androidx.compose.animation.core.Animatable
20 import androidx.compose.animation.core.VectorConverter
21 import androidx.compose.foundation.BorderStroke
22 import androidx.compose.foundation.interaction.DragInteraction
23 import androidx.compose.foundation.interaction.FocusInteraction
24 import androidx.compose.foundation.interaction.HoverInteraction
25 import androidx.compose.foundation.interaction.Interaction
26 import androidx.compose.foundation.interaction.InteractionSource
27 import androidx.compose.foundation.interaction.MutableInteractionSource
28 import androidx.compose.foundation.interaction.PressInteraction
29 import androidx.compose.foundation.layout.Column
30 import androidx.compose.foundation.layout.ColumnScope
31 import androidx.compose.material3.internal.animateElevation
32 import androidx.compose.material3.tokens.ElevatedCardTokens
33 import androidx.compose.material3.tokens.FilledCardTokens
34 import androidx.compose.material3.tokens.OutlinedCardTokens
35 import androidx.compose.runtime.Composable
36 import androidx.compose.runtime.Immutable
37 import androidx.compose.runtime.LaunchedEffect
38 import androidx.compose.runtime.Stable
39 import androidx.compose.runtime.State
40 import androidx.compose.runtime.mutableStateListOf
41 import androidx.compose.runtime.mutableStateOf
42 import androidx.compose.runtime.remember
43 import androidx.compose.ui.Modifier
44 import androidx.compose.ui.geometry.Offset
45 import androidx.compose.ui.graphics.Color
46 import androidx.compose.ui.graphics.Shape
47 import androidx.compose.ui.graphics.compositeOver
48 import androidx.compose.ui.graphics.takeOrElse
49 import androidx.compose.ui.unit.Dp
50 
51 /**
52  * [Material Design filled card](https://m3.material.io/components/cards/overview)
53  *
54  * Cards contain contain content and actions that relate information about a subject. Filled cards
55  * provide subtle separation from the background. This has less emphasis than elevated or outlined
56  * cards.
57  *
58  * This Card does not handle input events - see the other Card overloads if you want a clickable or
59  * selectable Card.
60  *
61  * ![Filled card
62  * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-card.png)
63  *
64  * Card sample:
65  *
66  * @sample androidx.compose.material3.samples.CardSample
67  * @param modifier the [Modifier] to be applied to this card
68  * @param shape defines the shape of this card's container, border (when [border] is not null), and
69  *   shadow (when using [elevation])
70  * @param colors [CardColors] that will be used to resolve the colors used for this card in
71  *   different states. See [CardDefaults.cardColors].
72  * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
73  *   This controls the size of the shadow below the card. Additionally, when the container color is
74  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
75  *   also: [Surface].
76  * @param border the border to draw around the container of this card
77  * @param content The content displayed on the card
78  */
79 @Composable
80 fun Card(
81     modifier: Modifier = Modifier,
82     shape: Shape = CardDefaults.shape,
83     colors: CardColors = CardDefaults.cardColors(),
84     elevation: CardElevation = CardDefaults.cardElevation(),
85     border: BorderStroke? = null,
86     content: @Composable ColumnScope.() -> Unit
87 ) {
88     Surface(
89         modifier = modifier,
90         shape = shape,
91         color = colors.containerColor(enabled = true),
92         contentColor = colors.contentColor(enabled = true),
93         shadowElevation = elevation.shadowElevation(enabled = true, interactionSource = null).value,
94         border = border,
95     ) {
96         Column(content = content)
97     }
98 }
99 
100 /**
101  * [Material Design filled card](https://m3.material.io/components/cards/overview)
102  *
103  * Cards contain contain content and actions that relate information about a subject. Filled cards
104  * provide subtle separation from the background. This has less emphasis than elevated or outlined
105  * cards.
106  *
107  * This Card handles click events, calling its [onClick] lambda.
108  *
109  * ![Filled card
110  * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-card.png)
111  *
112  * Clickable card sample:
113  *
114  * @sample androidx.compose.material3.samples.ClickableCardSample
115  * @param onClick called when this card is clicked
116  * @param modifier the [Modifier] to be applied to this card
117  * @param enabled controls the enabled state of this card. When `false`, this component will not
118  *   respond to user input, and it will appear visually disabled and disabled to accessibility
119  *   services.
120  * @param shape defines the shape of this card's container, border (when [border] is not null), and
121  *   shadow (when using [elevation])
122  * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
123  *   different states. See [CardDefaults.cardColors].
124  * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
125  *   This controls the size of the shadow below the card. Additionally, when the container color is
126  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
127  *   also: [Surface].
128  * @param border the border to draw around the container of this card
129  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
130  *   emitting [Interaction]s for this card. You can use this to change the card's appearance or
131  *   preview the card in different states. Note that if `null` is provided, interactions will still
132  *   happen internally.
133  * @param content The content displayed on the card
134  */
135 @Composable
Cardnull136 fun Card(
137     onClick: () -> Unit,
138     modifier: Modifier = Modifier,
139     enabled: Boolean = true,
140     shape: Shape = CardDefaults.shape,
141     colors: CardColors = CardDefaults.cardColors(),
142     elevation: CardElevation = CardDefaults.cardElevation(),
143     border: BorderStroke? = null,
144     interactionSource: MutableInteractionSource? = null,
145     content: @Composable ColumnScope.() -> Unit
146 ) {
147     @Suppress("NAME_SHADOWING")
148     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
149     Surface(
150         onClick = onClick,
151         modifier = modifier,
152         enabled = enabled,
153         shape = shape,
154         color = colors.containerColor(enabled),
155         contentColor = colors.contentColor(enabled),
156         shadowElevation = elevation.shadowElevation(enabled, interactionSource).value,
157         border = border,
158         interactionSource = interactionSource,
159     ) {
160         Column(content = content)
161     }
162 }
163 
164 /**
165  * [Material Design elevated card](https://m3.material.io/components/cards/overview)
166  *
167  * Elevated cards contain content and actions that relate information about a subject. They have a
168  * drop shadow, providing more separation from the background than filled cards, but less than
169  * outlined cards.
170  *
171  * This ElevatedCard does not handle input events - see the other ElevatedCard overloads if you want
172  * a clickable or selectable ElevatedCard.
173  *
174  * ![Elevated card
175  * image](https://developer.android.com/images/reference/androidx/compose/material3/elevated-card.png)
176  *
177  * Elevated card sample:
178  *
179  * @sample androidx.compose.material3.samples.ElevatedCardSample
180  * @param modifier the [Modifier] to be applied to this card
181  * @param shape defines the shape of this card's container and shadow (when using [elevation])
182  * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
183  *   different states. See [CardDefaults.elevatedCardElevation].
184  * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
185  *   This controls the size of the shadow below the card. Additionally, when the container color is
186  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
187  *   also: [Surface].
188  * @param content The content displayed on the card
189  */
190 @Composable
ElevatedCardnull191 fun ElevatedCard(
192     modifier: Modifier = Modifier,
193     shape: Shape = CardDefaults.elevatedShape,
194     colors: CardColors = CardDefaults.elevatedCardColors(),
195     elevation: CardElevation = CardDefaults.elevatedCardElevation(),
196     content: @Composable ColumnScope.() -> Unit
197 ) =
198     Card(
199         modifier = modifier,
200         shape = shape,
201         border = null,
202         elevation = elevation,
203         colors = colors,
204         content = content
205     )
206 
207 /**
208  * [Material Design elevated card](https://m3.material.io/components/cards/overview)
209  *
210  * Elevated cards contain content and actions that relate information about a subject. They have a
211  * drop shadow, providing more separation from the background than filled cards, but less than
212  * outlined cards.
213  *
214  * This ElevatedCard handles click events, calling its [onClick] lambda.
215  *
216  * ![Elevated card
217  * image](https://developer.android.com/images/reference/androidx/compose/material3/elevated-card.png)
218  *
219  * Clickable elevated card sample:
220  *
221  * @sample androidx.compose.material3.samples.ClickableElevatedCardSample
222  * @param onClick called when this card is clicked
223  * @param modifier the [Modifier] to be applied to this card
224  * @param enabled controls the enabled state of this card. When `false`, this component will not
225  *   respond to user input, and it will appear visually disabled and disabled to accessibility
226  *   services.
227  * @param shape defines the shape of this card's container and shadow (when using [elevation])
228  * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
229  *   different states. See [CardDefaults.elevatedCardElevation].
230  * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
231  *   This controls the size of the shadow below the card. Additionally, when the container color is
232  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
233  *   also: [Surface].
234  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
235  *   emitting [Interaction]s for this card. You can use this to change the card's appearance or
236  *   preview the card in different states. Note that if `null` is provided, interactions will still
237  *   happen internally.
238  * @param content The content displayed on the card
239  */
240 @Composable
241 fun ElevatedCard(
242     onClick: () -> Unit,
243     modifier: Modifier = Modifier,
244     enabled: Boolean = true,
245     shape: Shape = CardDefaults.elevatedShape,
246     colors: CardColors = CardDefaults.elevatedCardColors(),
247     elevation: CardElevation = CardDefaults.elevatedCardElevation(),
248     interactionSource: MutableInteractionSource? = null,
249     content: @Composable ColumnScope.() -> Unit
250 ) =
251     Card(
252         onClick = onClick,
253         modifier = modifier,
254         enabled = enabled,
255         shape = shape,
256         colors = colors,
257         elevation = elevation,
258         border = null,
259         interactionSource = interactionSource,
260         content = content
261     )
262 
263 /**
264  * [Material Design outlined card](https://m3.material.io/components/cards/overview)
265  *
266  * Outlined cards contain content and actions that relate information about a subject. They have a
267  * visual boundary around the container. This can provide greater emphasis than the other types.
268  *
269  * This OutlinedCard does not handle input events - see the other OutlinedCard overloads if you want
270  * a clickable or selectable OutlinedCard.
271  *
272  * ![Outlined card
273  * image](https://developer.android.com/images/reference/androidx/compose/material3/outlined-card.png)
274  *
275  * Outlined card sample:
276  *
277  * @sample androidx.compose.material3.samples.OutlinedCardSample
278  * @param modifier the [Modifier] to be applied to this card
279  * @param shape defines the shape of this card's container, border (when [border] is not null), and
280  *   shadow (when using [elevation])
281  * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
282  *   different states. See [CardDefaults.outlinedCardColors].
283  * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
284  *   This controls the size of the shadow below the card. Additionally, when the container color is
285  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
286  *   also: [Surface].
287  * @param border the border to draw around the container of this card
288  * @param content The content displayed on the card
289  */
290 @Composable
291 fun OutlinedCard(
292     modifier: Modifier = Modifier,
293     shape: Shape = CardDefaults.outlinedShape,
294     colors: CardColors = CardDefaults.outlinedCardColors(),
295     elevation: CardElevation = CardDefaults.outlinedCardElevation(),
296     border: BorderStroke = CardDefaults.outlinedCardBorder(),
297     content: @Composable ColumnScope.() -> Unit
298 ) =
299     Card(
300         modifier = modifier,
301         shape = shape,
302         colors = colors,
303         elevation = elevation,
304         border = border,
305         content = content
306     )
307 
308 /**
309  * [Material Design outlined card](https://m3.material.io/components/cards/overview)
310  *
311  * Outlined cards contain content and actions that relate information about a subject. They have a
312  * visual boundary around the container. This can provide greater emphasis than the other types.
313  *
314  * This OutlinedCard handles click events, calling its [onClick] lambda.
315  *
316  * ![Outlined card
317  * image](https://developer.android.com/images/reference/androidx/compose/material3/outlined-card.png)
318  *
319  * Clickable outlined card sample:
320  *
321  * @sample androidx.compose.material3.samples.ClickableOutlinedCardSample
322  * @param onClick called when this card is clicked
323  * @param modifier the [Modifier] to be applied to this card
324  * @param enabled controls the enabled state of this card. When `false`, this component will not
325  *   respond to user input, and it will appear visually disabled and disabled to accessibility
326  *   services.
327  * @param shape defines the shape of this card's container, border (when [border] is not null), and
328  *   shadow (when using [elevation])
329  * @param colors [CardColors] that will be used to resolve the color(s) used for this card in
330  *   different states. See [CardDefaults.outlinedCardColors].
331  * @param elevation [CardElevation] used to resolve the elevation for this card in different states.
332  *   This controls the size of the shadow below the card. Additionally, when the container color is
333  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay. See
334  *   also: [Surface].
335  * @param border the border to draw around the container of this card
336  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
337  *   emitting [Interaction]s for this card. You can use this to change the card's appearance or
338  *   preview the card in different states. Note that if `null` is provided, interactions will still
339  *   happen internally.
340  * @param content The content displayed on the card
341  */
342 @Composable
343 fun OutlinedCard(
344     onClick: () -> Unit,
345     modifier: Modifier = Modifier,
346     enabled: Boolean = true,
347     shape: Shape = CardDefaults.outlinedShape,
348     colors: CardColors = CardDefaults.outlinedCardColors(),
349     elevation: CardElevation = CardDefaults.outlinedCardElevation(),
350     border: BorderStroke = CardDefaults.outlinedCardBorder(enabled),
351     interactionSource: MutableInteractionSource? = null,
352     content: @Composable ColumnScope.() -> Unit
353 ) =
354     Card(
355         onClick = onClick,
356         modifier = modifier,
357         enabled = enabled,
358         shape = shape,
359         colors = colors,
360         elevation = elevation,
361         border = border,
362         interactionSource = interactionSource,
363         content = content
364     )
365 
366 /** Contains the default values used by all card types. */
367 object CardDefaults {
368     // shape Defaults
369     /** Default shape for a card. */
370     val shape: Shape
371         @Composable get() = FilledCardTokens.ContainerShape.value
372 
373     /** Default shape for an elevated card. */
374     val elevatedShape: Shape
375         @Composable get() = ElevatedCardTokens.ContainerShape.value
376 
377     /** Default shape for an outlined card. */
378     val outlinedShape: Shape
379         @Composable get() = OutlinedCardTokens.ContainerShape.value
380 
381     /**
382      * Creates a [CardElevation] that will animate between the provided values according to the
383      * Material specification for a [Card].
384      *
385      * @param defaultElevation the elevation used when the [Card] is has no other [Interaction]s.
386      * @param pressedElevation the elevation used when the [Card] is pressed.
387      * @param focusedElevation the elevation used when the [Card] is focused.
388      * @param hoveredElevation the elevation used when the [Card] is hovered.
389      * @param draggedElevation the elevation used when the [Card] is dragged.
390      * @param disabledElevation the elevation used when the [Card] is disabled.
391      */
392     @Composable
393     fun cardElevation(
394         defaultElevation: Dp = FilledCardTokens.ContainerElevation,
395         pressedElevation: Dp = FilledCardTokens.PressedContainerElevation,
396         focusedElevation: Dp = FilledCardTokens.FocusContainerElevation,
397         hoveredElevation: Dp = FilledCardTokens.HoverContainerElevation,
398         draggedElevation: Dp = FilledCardTokens.DraggedContainerElevation,
399         disabledElevation: Dp = FilledCardTokens.DisabledContainerElevation
400     ): CardElevation =
401         CardElevation(
402             defaultElevation = defaultElevation,
403             pressedElevation = pressedElevation,
404             focusedElevation = focusedElevation,
405             hoveredElevation = hoveredElevation,
406             draggedElevation = draggedElevation,
407             disabledElevation = disabledElevation
408         )
409 
410     /**
411      * Creates a [CardElevation] that will animate between the provided values according to the
412      * Material specification for an [ElevatedCard].
413      *
414      * @param defaultElevation the elevation used when the [ElevatedCard] is has no other
415      *   [Interaction]s.
416      * @param pressedElevation the elevation used when the [ElevatedCard] is pressed.
417      * @param focusedElevation the elevation used when the [ElevatedCard] is focused.
418      * @param hoveredElevation the elevation used when the [ElevatedCard] is hovered.
419      * @param draggedElevation the elevation used when the [ElevatedCard] is dragged.
420      * @param disabledElevation the elevation used when the [Card] is disabled.
421      */
422     @Composable
423     fun elevatedCardElevation(
424         defaultElevation: Dp = ElevatedCardTokens.ContainerElevation,
425         pressedElevation: Dp = ElevatedCardTokens.PressedContainerElevation,
426         focusedElevation: Dp = ElevatedCardTokens.FocusContainerElevation,
427         hoveredElevation: Dp = ElevatedCardTokens.HoverContainerElevation,
428         draggedElevation: Dp = ElevatedCardTokens.DraggedContainerElevation,
429         disabledElevation: Dp = ElevatedCardTokens.DisabledContainerElevation
430     ): CardElevation =
431         CardElevation(
432             defaultElevation = defaultElevation,
433             pressedElevation = pressedElevation,
434             focusedElevation = focusedElevation,
435             hoveredElevation = hoveredElevation,
436             draggedElevation = draggedElevation,
437             disabledElevation = disabledElevation
438         )
439 
440     /**
441      * Creates a [CardElevation] that will animate between the provided values according to the
442      * Material specification for an [OutlinedCard].
443      *
444      * @param defaultElevation the elevation used when the [OutlinedCard] is has no other
445      *   [Interaction]s.
446      * @param pressedElevation the elevation used when the [OutlinedCard] is pressed.
447      * @param focusedElevation the elevation used when the [OutlinedCard] is focused.
448      * @param hoveredElevation the elevation used when the [OutlinedCard] is hovered.
449      * @param draggedElevation the elevation used when the [OutlinedCard] is dragged.
450      */
451     @Composable
452     fun outlinedCardElevation(
453         defaultElevation: Dp = OutlinedCardTokens.ContainerElevation,
454         pressedElevation: Dp = defaultElevation,
455         focusedElevation: Dp = defaultElevation,
456         hoveredElevation: Dp = defaultElevation,
457         draggedElevation: Dp = OutlinedCardTokens.DraggedContainerElevation,
458         disabledElevation: Dp = OutlinedCardTokens.DisabledContainerElevation
459     ): CardElevation =
460         CardElevation(
461             defaultElevation = defaultElevation,
462             pressedElevation = pressedElevation,
463             focusedElevation = focusedElevation,
464             hoveredElevation = hoveredElevation,
465             draggedElevation = draggedElevation,
466             disabledElevation = disabledElevation
467         )
468 
469     /**
470      * Creates a [CardColors] that represents the default container and content colors used in a
471      * [Card].
472      */
473     @Composable fun cardColors() = MaterialTheme.colorScheme.defaultCardColors
474 
475     /**
476      * Creates a [CardColors] that represents the default container and content colors used in a
477      * [Card].
478      *
479      * @param containerColor the container color of this [Card] when enabled.
480      * @param contentColor the content color of this [Card] when enabled.
481      * @param disabledContainerColor the container color of this [Card] when not enabled.
482      * @param disabledContentColor the content color of this [Card] when not enabled.
483      */
484     @Composable
485     fun cardColors(
486         containerColor: Color = Color.Unspecified,
487         contentColor: Color = contentColorFor(containerColor),
488         disabledContainerColor: Color = Color.Unspecified,
489         disabledContentColor: Color = contentColor.copy(DisabledAlpha),
490     ): CardColors =
491         MaterialTheme.colorScheme.defaultCardColors.copy(
492             containerColor = containerColor,
493             contentColor = contentColor,
494             disabledContainerColor = disabledContainerColor,
495             disabledContentColor = disabledContentColor
496         )
497 
498     internal val ColorScheme.defaultCardColors: CardColors
499         get() {
500             return defaultCardColorsCached
501                 ?: CardColors(
502                         containerColor = fromToken(FilledCardTokens.ContainerColor),
503                         contentColor = contentColorFor(fromToken(FilledCardTokens.ContainerColor)),
504                         disabledContainerColor =
505                             fromToken(FilledCardTokens.DisabledContainerColor)
506                                 .copy(alpha = FilledCardTokens.DisabledContainerOpacity)
507                                 .compositeOver(fromToken(FilledCardTokens.ContainerColor)),
508                         disabledContentColor =
509                             contentColorFor(fromToken(FilledCardTokens.ContainerColor))
510                                 .copy(DisabledAlpha),
511                     )
512                     .also { defaultCardColorsCached = it }
513         }
514 
515     /**
516      * Creates a [CardColors] that represents the default container and content colors used in an
517      * [ElevatedCard].
518      */
519     @Composable fun elevatedCardColors() = MaterialTheme.colorScheme.defaultElevatedCardColors
520 
521     /**
522      * Creates a [CardColors] that represents the default container and content colors used in an
523      * [ElevatedCard].
524      *
525      * @param containerColor the container color of this [ElevatedCard] when enabled.
526      * @param contentColor the content color of this [ElevatedCard] when enabled.
527      * @param disabledContainerColor the container color of this [ElevatedCard] when not enabled.
528      * @param disabledContentColor the content color of this [ElevatedCard] when not enabled.
529      */
530     @Composable
531     fun elevatedCardColors(
532         containerColor: Color = Color.Unspecified,
533         contentColor: Color = contentColorFor(containerColor),
534         disabledContainerColor: Color = Color.Unspecified,
535         disabledContentColor: Color = contentColor.copy(DisabledAlpha),
536     ): CardColors =
537         MaterialTheme.colorScheme.defaultElevatedCardColors.copy(
538             containerColor = containerColor,
539             contentColor = contentColor,
540             disabledContainerColor = disabledContainerColor,
541             disabledContentColor = disabledContentColor
542         )
543 
544     internal val ColorScheme.defaultElevatedCardColors: CardColors
545         get() {
546             return defaultElevatedCardColorsCached
547                 ?: CardColors(
548                         containerColor = fromToken(ElevatedCardTokens.ContainerColor),
549                         contentColor =
550                             contentColorFor(fromToken(ElevatedCardTokens.ContainerColor)),
551                         disabledContainerColor =
552                             fromToken(ElevatedCardTokens.DisabledContainerColor)
553                                 .copy(alpha = ElevatedCardTokens.DisabledContainerOpacity)
554                                 .compositeOver(
555                                     fromToken(ElevatedCardTokens.DisabledContainerColor)
556                                 ),
557                         disabledContentColor =
558                             contentColorFor(fromToken(ElevatedCardTokens.ContainerColor))
559                                 .copy(DisabledAlpha),
560                     )
561                     .also { defaultElevatedCardColorsCached = it }
562         }
563 
564     /**
565      * Creates a [CardColors] that represents the default container and content colors used in an
566      * [OutlinedCard].
567      */
568     @Composable fun outlinedCardColors() = MaterialTheme.colorScheme.defaultOutlinedCardColors
569 
570     /**
571      * Creates a [CardColors] that represents the default container and content colors used in an
572      * [OutlinedCard].
573      *
574      * @param containerColor the container color of this [OutlinedCard] when enabled.
575      * @param contentColor the content color of this [OutlinedCard] when enabled.
576      * @param disabledContainerColor the container color of this [OutlinedCard] when not enabled.
577      * @param disabledContentColor the content color of this [OutlinedCard] when not enabled.
578      */
579     @Composable
580     fun outlinedCardColors(
581         containerColor: Color = Color.Unspecified,
582         contentColor: Color = contentColorFor(containerColor),
583         disabledContainerColor: Color = Color.Unspecified,
584         disabledContentColor: Color = contentColorFor(containerColor).copy(DisabledAlpha),
585     ): CardColors =
586         MaterialTheme.colorScheme.defaultOutlinedCardColors.copy(
587             containerColor = containerColor,
588             contentColor = contentColor,
589             disabledContainerColor = disabledContainerColor,
590             disabledContentColor = disabledContentColor
591         )
592 
593     internal val ColorScheme.defaultOutlinedCardColors: CardColors
594         get() {
595             return defaultOutlinedCardColorsCached
596                 ?: CardColors(
597                         containerColor = fromToken(OutlinedCardTokens.ContainerColor),
598                         contentColor =
599                             contentColorFor(fromToken(OutlinedCardTokens.ContainerColor)),
600                         disabledContainerColor = fromToken(OutlinedCardTokens.ContainerColor),
601                         disabledContentColor =
602                             contentColorFor(fromToken(OutlinedCardTokens.ContainerColor))
603                                 .copy(DisabledAlpha),
604                     )
605                     .also { defaultOutlinedCardColorsCached = it }
606         }
607 
608     /**
609      * Creates a [BorderStroke] that represents the default border used in [OutlinedCard].
610      *
611      * @param enabled whether the card is enabled
612      */
613     @Composable
614     fun outlinedCardBorder(enabled: Boolean = true): BorderStroke {
615         val color =
616             if (enabled) {
617                 OutlinedCardTokens.OutlineColor.value
618             } else {
619                 OutlinedCardTokens.DisabledOutlineColor.value
620                     .copy(alpha = OutlinedCardTokens.DisabledOutlineOpacity)
621                     .compositeOver(ElevatedCardTokens.ContainerColor.value)
622             }
623         return remember(color) { BorderStroke(OutlinedCardTokens.OutlineWidth, color) }
624     }
625 }
626 
627 /**
628  * Represents the elevation for a card in different states.
629  * - See [CardDefaults.cardElevation] for the default elevation used in a [Card].
630  * - See [CardDefaults.elevatedCardElevation] for the default elevation used in an [ElevatedCard].
631  * - See [CardDefaults.outlinedCardElevation] for the default elevation used in an [OutlinedCard].
632  */
633 @Immutable
634 class CardElevation
635 internal constructor(
636     private val defaultElevation: Dp,
637     private val pressedElevation: Dp,
638     private val focusedElevation: Dp,
639     private val hoveredElevation: Dp,
640     private val draggedElevation: Dp,
641     private val disabledElevation: Dp
642 ) {
643     /**
644      * Represents the shadow elevation used in a card, depending on its [enabled] state and
645      * [interactionSource].
646      *
647      * Shadow elevation is used to apply a shadow around the card to give it higher emphasis.
648      *
649      * @param enabled whether the card is enabled
650      * @param interactionSource the [InteractionSource] for this card
651      */
652     @Composable
shadowElevationnull653     internal fun shadowElevation(
654         enabled: Boolean,
655         interactionSource: InteractionSource?
656     ): State<Dp> {
657         if (interactionSource == null) {
658             return remember { mutableStateOf(defaultElevation) }
659         }
660         return animateElevation(enabled = enabled, interactionSource = interactionSource)
661     }
662 
663     @Composable
animateElevationnull664     private fun animateElevation(
665         enabled: Boolean,
666         interactionSource: InteractionSource
667     ): State<Dp> {
668         val interactions = remember { mutableStateListOf<Interaction>() }
669         LaunchedEffect(interactionSource) {
670             interactionSource.interactions.collect { interaction ->
671                 when (interaction) {
672                     is HoverInteraction.Enter -> {
673                         interactions.add(interaction)
674                     }
675                     is HoverInteraction.Exit -> {
676                         interactions.remove(interaction.enter)
677                     }
678                     is FocusInteraction.Focus -> {
679                         interactions.add(interaction)
680                     }
681                     is FocusInteraction.Unfocus -> {
682                         interactions.remove(interaction.focus)
683                     }
684                     is PressInteraction.Press -> {
685                         interactions.add(interaction)
686                     }
687                     is PressInteraction.Release -> {
688                         interactions.remove(interaction.press)
689                     }
690                     is PressInteraction.Cancel -> {
691                         interactions.remove(interaction.press)
692                     }
693                     is DragInteraction.Start -> {
694                         interactions.add(interaction)
695                     }
696                     is DragInteraction.Stop -> {
697                         interactions.remove(interaction.start)
698                     }
699                     is DragInteraction.Cancel -> {
700                         interactions.remove(interaction.start)
701                     }
702                 }
703             }
704         }
705 
706         val interaction = interactions.lastOrNull()
707 
708         val target =
709             if (!enabled) {
710                 disabledElevation
711             } else {
712                 when (interaction) {
713                     is PressInteraction.Press -> pressedElevation
714                     is HoverInteraction.Enter -> hoveredElevation
715                     is FocusInteraction.Focus -> focusedElevation
716                     is DragInteraction.Start -> draggedElevation
717                     else -> defaultElevation
718                 }
719             }
720 
721         val animatable = remember { Animatable(target, Dp.VectorConverter) }
722 
723         LaunchedEffect(target) {
724             if (animatable.targetValue != target) {
725                 if (!enabled) {
726                     // No transition when moving to a disabled state.
727                     animatable.snapTo(target)
728                 } else {
729                     val lastInteraction =
730                         when (animatable.targetValue) {
731                             pressedElevation -> PressInteraction.Press(Offset.Zero)
732                             hoveredElevation -> HoverInteraction.Enter()
733                             focusedElevation -> FocusInteraction.Focus()
734                             draggedElevation -> DragInteraction.Start()
735                             else -> null
736                         }
737                     animatable.animateElevation(
738                         from = lastInteraction,
739                         to = interaction,
740                         target = target
741                     )
742                 }
743             }
744         }
745 
746         return animatable.asState()
747     }
748 
equalsnull749     override fun equals(other: Any?): Boolean {
750         if (this === other) return true
751         if (other == null || other !is CardElevation) return false
752 
753         if (defaultElevation != other.defaultElevation) return false
754         if (pressedElevation != other.pressedElevation) return false
755         if (focusedElevation != other.focusedElevation) return false
756         if (hoveredElevation != other.hoveredElevation) return false
757         if (disabledElevation != other.disabledElevation) return false
758 
759         return true
760     }
761 
hashCodenull762     override fun hashCode(): Int {
763         var result = defaultElevation.hashCode()
764         result = 31 * result + pressedElevation.hashCode()
765         result = 31 * result + focusedElevation.hashCode()
766         result = 31 * result + hoveredElevation.hashCode()
767         result = 31 * result + disabledElevation.hashCode()
768         return result
769     }
770 }
771 
772 /**
773  * Represents the container and content colors used in a card in different states.
774  *
775  * @param containerColor the container color of this [Card] when enabled.
776  * @param contentColor the content color of this [Card] when enabled.
777  * @param disabledContainerColor the container color of this [Card] when not enabled.
778  * @param disabledContentColor the content color of this [Card] when not enabled.
779  * @constructor create an instance with arbitrary colors.
780  * - See [CardDefaults.cardColors] for the default colors used in a [Card].
781  * - See [CardDefaults.elevatedCardColors] for the default colors used in a [ElevatedCard].
782  * - See [CardDefaults.outlinedCardColors] for the default colors used in a [OutlinedCard].
783  */
784 @Immutable
785 class CardColors
786 constructor(
787     val containerColor: Color,
788     val contentColor: Color,
789     val disabledContainerColor: Color,
790     val disabledContentColor: Color,
791 ) {
792     /**
793      * Returns a copy of this CardColors, optionally overriding some of the values. This uses the
794      * Color.Unspecified to mean “use the value from the source”
795      */
copynull796     fun copy(
797         containerColor: Color = this.containerColor,
798         contentColor: Color = this.contentColor,
799         disabledContainerColor: Color = this.disabledContainerColor,
800         disabledContentColor: Color = this.disabledContentColor
801     ) =
802         CardColors(
803             containerColor.takeOrElse { this.containerColor },
<lambda>null804             contentColor.takeOrElse { this.contentColor },
<lambda>null805             disabledContainerColor.takeOrElse { this.disabledContainerColor },
<lambda>null806             disabledContentColor.takeOrElse { this.disabledContentColor },
807         )
808 
809     /**
810      * Represents the container color for this card, depending on [enabled].
811      *
812      * @param enabled whether the card is enabled
813      */
814     @Stable
containerColornull815     internal fun containerColor(enabled: Boolean): Color =
816         if (enabled) containerColor else disabledContainerColor
817 
818     /**
819      * Represents the content color for this card, depending on [enabled].
820      *
821      * @param enabled whether the card is enabled
822      */
823     @Stable
824     internal fun contentColor(enabled: Boolean) =
825         if (enabled) contentColor else disabledContentColor
826 
827     override fun equals(other: Any?): Boolean {
828         if (this === other) return true
829         if (other == null || other !is CardColors) return false
830 
831         if (containerColor != other.containerColor) return false
832         if (contentColor != other.contentColor) return false
833         if (disabledContainerColor != other.disabledContainerColor) return false
834         if (disabledContentColor != other.disabledContentColor) return false
835 
836         return true
837     }
838 
hashCodenull839     override fun hashCode(): Int {
840         var result = containerColor.hashCode()
841         result = 31 * result + contentColor.hashCode()
842         result = 31 * result + disabledContainerColor.hashCode()
843         result = 31 * result + disabledContentColor.hashCode()
844         return result
845     }
846 }
847