[go: nahoru, domu]

Merge "Add support for CompactChip." into androidx-main
diff --git a/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ChipTest.kt b/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ChipTest.kt
index addff08..48a160f 100644
--- a/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ChipTest.kt
+++ b/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ChipTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
@@ -37,6 +38,7 @@
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
@@ -45,6 +47,7 @@
 import androidx.compose.ui.test.onChildAt
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.Dp
@@ -254,6 +257,108 @@
             .assertTopPositionInRootIsEqualTo((itemBounds.height - iconBounds.height) / 2)
     }
 
+    @Test
+    fun icon_only_compact_chip_has_correct_default_width_and_height() {
+        val iconTag = "TestIcon"
+        val chipTag = "chip"
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                CompactChip(
+                    >
+                    modifier = Modifier.testTag(chipTag),
+                    icon = { CreateImage(iconTag) }
+                )
+            }
+
+        rule.onRoot().assertWidthIsEqualTo(52.dp).assertHeightIsEqualTo(32.dp)
+    }
+
+    @Test
+    fun label_only_compact_chip_has_correct_default_height() {
+        val chipTag = "chip"
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                CompactChip(
+                    >
+                    modifier = Modifier.testTag(chipTag),
+                    label = { Text("Test") }
+                )
+            }
+
+        rule.onRoot().assertHeightIsEqualTo(32.dp)
+    }
+
+    @Test
+    fun no_content_compact_chip_has_correct_default_width_and_height() {
+        val chipTag = "chip"
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                CompactChip(
+                    >
+                    modifier = Modifier.testTag(chipTag),
+                )
+            }
+
+        rule.onRoot().assertWidthIsEqualTo(52.dp).assertHeightIsEqualTo(32.dp)
+    }
+
+    @Test
+    fun icon_only_compact_chip_can_have_width_overridden() {
+        val iconTag = "TestIcon"
+        val chipTag = "chip"
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                CompactChip(
+                    >
+                    modifier = Modifier.testTag(chipTag).width(100.dp),
+                    icon = { CreateImage(iconTag) }
+                )
+            }
+
+        rule.onRoot().assertWidthIsEqualTo(100.dp)
+    }
+
+    @Test
+    fun has_icon_in_correct_location_when_compact_chip() {
+        val iconTag = "TestIcon"
+        val chipTag = "chip"
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                CompactChip(
+                    >
+                    label = { Text("Blue green orange") },
+                    icon = { CreateImage(iconTag) },
+                    modifier = Modifier.testTag(chipTag)
+                )
+            }
+        val itemBounds = rule.onNodeWithTag(chipTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(iconTag, useUnmergedTree = true)
+            .getUnclippedBoundsInRoot()
+
+        rule.onNodeWithContentDescription(iconTag, useUnmergedTree = true)
+            .assertTopPositionInRootIsEqualTo((itemBounds.height - iconBounds.height) / 2)
+    }
+
+    @Test
+    fun has_icon_in_correct_location_when_icon_only_chip() {
+        val iconTag = "TestIcon"
+        val chipTag = "chip"
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                CompactChip(
+                    >
+                    modifier = Modifier.testTag(chipTag),
+                    icon = { CreateImage(iconTag) }
+                )
+            }
+        val itemBounds = rule.onNodeWithTag(chipTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(iconTag, useUnmergedTree = true)
+            .getUnclippedBoundsInRoot()
+
+        rule.onNodeWithContentDescription(iconTag, useUnmergedTree = true)
+            .assertTopPositionInRootIsEqualTo((itemBounds.height - iconBounds.height) / 2)
+    }
+
     private fun verifyHeight(expectedHeight: Dp) {
         rule.verifyHeight(expectedHeight) {
             Chip(
@@ -281,7 +386,7 @@
 
     @Test
     fun three_slot_layout_gives_primary_enabled_colors() =
-        verifyThreeSlotColors(
+        verifySlotColors(
             TestChipColors.Primary,
             ChipStatus.Enabled,
             { MaterialTheme.colors.primary },
@@ -291,6 +396,18 @@
         )
 
     @Test
+    fun compact_chip_gives_primary_enabled_colors() =
+        verifySlotColors(
+            TestChipColors.Primary,
+            ChipStatus.Enabled,
+            { MaterialTheme.colors.primary },
+            { MaterialTheme.colors.onPrimary },
+            { MaterialTheme.colors.onPrimary },
+            { MaterialTheme.colors.onPrimary },
+            compactChip = true
+        )
+
+    @Test
     fun gives_primary_disabled_colors() =
         verifyColors(
             TestChipColors.Primary,
@@ -301,7 +418,7 @@
 
     @Test
     fun three_slot_layout_gives_primary_disabled_colors() =
-        verifyThreeSlotColors(
+        verifySlotColors(
             TestChipColors.Primary,
             ChipStatus.Disabled,
             { MaterialTheme.colors.primary },
@@ -321,7 +438,7 @@
 
     @Test
     fun three_slot_layout_gives_secondary_enabled_colors() =
-        verifyThreeSlotColors(
+        verifySlotColors(
             TestChipColors.Secondary,
             ChipStatus.Enabled,
             { MaterialTheme.colors.surface },
@@ -341,7 +458,7 @@
 
     @Test
     fun three_slot_layout_gives_secondary_disabled_colors() =
-        verifyThreeSlotColors(
+        verifySlotColors(
             TestChipColors.Secondary,
             ChipStatus.Enabled,
             { MaterialTheme.colors.surface },
@@ -351,6 +468,18 @@
         )
 
     @Test
+    fun compact_chip_gives_secondary_disabled_colors() =
+        verifySlotColors(
+            TestChipColors.Secondary,
+            ChipStatus.Enabled,
+            { MaterialTheme.colors.surface },
+            { MaterialTheme.colors.onSurface },
+            { MaterialTheme.colors.onSurface },
+            { MaterialTheme.colors.onSurface },
+            compactChip = true
+        )
+
+    @Test
     fun allows_custom_enabled_background_color_override() {
         val overrideColor = Color.Yellow
         rule.setContentWithTheme {
@@ -531,13 +660,14 @@
         }
     }
 
-    private fun verifyThreeSlotColors(
+    private fun verifySlotColors(
         testChipColors: TestChipColors,
         status: ChipStatus,
         backgroundColor: @Composable () -> Color,
         contentColor: @Composable () -> Color,
         secondaryContentColor: @Composable () -> Color,
-        iconColor: @Composable () -> Color
+        iconColor: @Composable () -> Color,
+        compactChip: Boolean = false
     ) {
         var expectedBackground = Color.Transparent
         var expectedContent = Color.Transparent
@@ -559,28 +689,43 @@
                     .fillMaxSize()
                     .background(expectedBackground)
             ) {
-                Chip(
-                    >
-                    colors = testChipColors.chipColors(),
-                    label = { actualContent = LocalContentColor.current },
-                    secondaryLabel = { actualSecondaryContent = LocalContentColor.current },
-                    icon = { actualIcon = LocalContentColor.current },
-                    enabled = status.enabled(),
-                    modifier = Modifier.testTag("test-item")
-                )
+                if (compactChip) {
+                    CompactChip(
+                        >
+                        colors = testChipColors.chipColors(),
+                        label = { actualContent = LocalContentColor.current },
+                        icon = { actualIcon = LocalContentColor.current },
+                        enabled = status.enabled(),
+                        modifier = Modifier.testTag("test-item")
+                    )
+                } else {
+                    Chip(
+                        >
+                        colors = testChipColors.chipColors(),
+                        label = { actualContent = LocalContentColor.current },
+                        secondaryLabel = { actualSecondaryContent = LocalContentColor.current },
+                        icon = { actualIcon = LocalContentColor.current },
+                        enabled = status.enabled(),
+                        modifier = Modifier.testTag("test-item")
+                    )
+                }
             }
         }
 
         if (status.enabled()) {
             assertEquals(expectedContent, actualContent)
-            assertEquals(expectedSecondaryContent, actualSecondaryContent)
+            if (! compactChip) {
+                assertEquals(expectedSecondaryContent, actualSecondaryContent)
+            }
             assertEquals(expectedIcon, actualIcon)
         } else {
             assertEquals(expectedContent.copy(alpha = expectedAlpha), actualContent)
-            assertEquals(
-                expectedSecondaryContent.copy(alpha = expectedAlpha),
-                actualSecondaryContent
-            )
+            if (! compactChip) {
+                assertEquals(
+                    expectedSecondaryContent.copy(alpha = expectedAlpha),
+                    actualSecondaryContent
+                )
+            }
             assertEquals(expectedIcon.copy(alpha = expectedAlpha), actualIcon)
         }
 
diff --git a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
index 35e949e..fce5d9c 100644
--- a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
@@ -15,8 +15,6 @@
  */
 package androidx.wear.compose.material
 
-import androidx.compose.foundation.Indication
-import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -29,9 +27,11 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material.ContentAlpha
 import androidx.compose.material.Surface
+import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -75,7 +75,6 @@
  * @param modifier Modifier to be applied to the chip
  * @param enabled Controls the enabled state of the chip. When `false`, this chip will not
  * be clickable
- * @param onClickLabel Semantic / accessibility label for the [onClick] action
  * @param contentPadding The spacing values to apply internally between the container and the
  * content
  * @param shape Defines the chip's shape. It is strongly recommended to use the default as this
@@ -84,9 +83,6 @@
  * [Interaction]s for this Chip. You can create and pass in your own remembered
  * [MutableInteractionSource] if you want to observe [Interaction]s and customize the
  * appearance / behavior of this Chip in different [Interaction]s.
- * @param indication Indication to be shown when surface is pressed. By default, indication from
- * [LocalIndication] will be used. Pass `null` to show no indication, or current value from
- * [LocalIndication] to show theme default
  * @param role The type of user interface element. Accessibility services might use this
  * to describe the element or do customizations
  */
@@ -96,11 +92,9 @@
     colors: ChipColors,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    onClickLabel: String? = null,
     contentPadding: PaddingValues = ChipDefaults.ContentPadding,
     shape: Shape = MaterialTheme.shapes.small,
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    indication: Indication? = LocalIndication.current,
     role: Role? = Role.Button,
     content: @Composable () -> Unit,
 ) {
@@ -122,10 +116,9 @@
         val contentBoxModifier = Modifier
             .clickable(
                 enabled = enabled,
-                >
                 >
                 role = role,
-                indication = indication,
+                indication = rememberRipple(),
                 interactionSource = interactionSource,
             )
             .padding(contentPadding)
@@ -148,7 +141,7 @@
 /**
  * Wear Material [Chip] that offers three slots and a specific layout for an icon, label and
  * secondaryLabel. The icon and secondaryLabel are optional. The items are laid out with the icon,
- * if provided, a the start of a row, with a column next containing the two label slots.
+ * if provided, at the start of a row, with a column next containing the two label slots.
  *
  * The [Chip] is Stadium shaped and has a max height designed to take no more than two lines of text
  * of [Typography.button] style. If no secondary label is provided then the label
@@ -167,26 +160,25 @@
  *
  * Chips can be enabled or disabled. A disabled chip will not respond to click events.
  *
- * @param label A slot for providing the chips main label. The contents are expected to be text
+ * @param label A slot for providing the chip's main label. The contents are expected to be text
  * which is "start" aligned if there is an icon preset and "start" or "center" aligned if not.
  * @param onClick Will be called when the user clicks the chip
  * @param modifier Modifier to be applied to the chip
- * @param secondaryLabel A slot for providing the chips secondary label. The contents are expected
+ * @param secondaryLabel A slot for providing the chip's secondary label. The contents are expected
  * to be text which is "start" aligned if there is an icon preset and "start" or "center" aligned if
  * not. label and secondaryLabel contents should be consistently aligned.
+ * @param icon A slot for providing the chip's icon. The contents are expected to be a horizontally
+ * and vertically aligned icon of size [ChipDefaults.IconSize] or [ChipDefaults.LargeIconSize].
  * @param colors [ChipColors] that will be used to resolve the background and content color for
  * this chip in different states. See [ChipDefaults.chipColors]. Defaults to
  * [ChipDefaults.primaryChipColors]
  * @param enabled Controls the enabled state of the chip. When `false`, this chip will not
  * be clickable
- * @param onClickLabel Semantic / accessibility label for the [onClick] action
  * @param interactionSource The [MutableInteractionSource] representing the stream of
  * [Interaction]s for this Chip. You can create and pass in your own remembered
  * [MutableInteractionSource] if you want to observe [Interaction]s and customize the
  * appearance / behavior of this Chip in different [Interaction]s.
- * @param indication Indication to be shown when surface is pressed. By default, indication from
- * [LocalIndication] will be used. Pass `null` to show no indication, or current value from
- * [LocalIndication] to show theme default
+
  * @param contentPadding The spacing values to apply internally between the container and the
  * content
  */
@@ -199,9 +191,7 @@
     icon: (@Composable () -> Unit)? = null,
     colors: ChipColors = ChipDefaults.primaryChipColors(),
     enabled: Boolean = true,
-    onClickLabel: String? = null,
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    indication: Indication? = LocalIndication.current,
     contentPadding: PaddingValues = ChipDefaults.contentPadding(icon != null),
 ) {
     Chip(
@@ -209,9 +199,7 @@
         colors = colors,
         modifier = modifier,
         enabled = enabled,
-        >
         interactionSource = interactionSource,
-        indication = indication,
         contentPadding = contentPadding
     ) {
         Row(
@@ -248,6 +236,94 @@
 }
 
 /**
+ * A compact Wear Material Chip that offers two slots and a specific layout for an icon and label.
+ * Both the icon and label are optional however it is expected that at least one will be provided.
+ *
+ * The [CompactChip] is Stadium shaped and has a max height designed to take no more than one line
+ * of text of [Typography.button] style and/or one 24x24 icon. The default max height is
+ * [ChipDefaults.CompactChipHeight].
+ *
+ * If a icon is provided then the labels should be "start" aligned, e.g. left aligned in ltr so that
+ * the text starts next to the icon.
+ *
+ * The items are laid out as follows.
+ *
+ * 1. If a label is provided then the chip will be laid out with the optional icon at the start of a
+ * row followed by the label with a default max height of [ChipDefaults.CompactChipHeight].
+ *
+ * 2. If only an icon is provided it will be laid out vertically and horizontally centered with a
+ * default height of [ChipDefaults.CompactChipHeight] and the default width of
+ * [ChipDefaults.IconOnlyCompactChipWidth]
+ *
+ * The [CompactChip] can have different styles with configurable content colors, background colors
+ * including gradients, these are provided by [ChipColors] implementations.
+ *
+ * The recommended set of [ChipColors] styles can be obtained from [ChipDefaults], e.g.
+ * [ChipDefaults.primaryChipColors] to get a color scheme for a primary [Chip] which by default
+ * will have a solid background of [Colors.primary] and content color of
+ * [Colors.onPrimary].
+ *
+ * Chips can be enabled or disabled. A disabled chip will not respond to click events.
+ *
+ * @param onClick Will be called when the user clicks the chip
+ * @param modifier Modifier to be applied to the chip
+ * @param label A slot for providing the chip's main label. The contents are expected to be text
+ * which is "start" aligned if there is an icon preset and "start" or "center" aligned if not.
+ * @param icon A slot for providing the chip's icon. The contents are expected to be a horizontally
+ * and vertically aligned icon of size [ChipDefaults.IconSize] or [ChipDefaults.LargeIconSize].
+ * @param colors [ChipColors] that will be used to resolve the background and content color for
+ * this chip in different states. See [ChipDefaults.chipColors]. Defaults to
+ * [ChipDefaults.primaryChipColors]
+ * @param enabled Controls the enabled state of the chip. When `false`, this chip will not
+ * be clickable
+ * @param interactionSource The [MutableInteractionSource] representing the stream of
+ * [Interaction]s for this Chip. You can create and pass in your own remembered
+ * [MutableInteractionSource] if you want to observe [Interaction]s and customize the
+ * appearance / behavior of this Chip in different [Interaction]s.
+ * @param contentPadding The spacing values to apply internally between the container and the
+ * content
+ */
+@Composable
+fun CompactChip(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    label: (@Composable () -> Unit)? = null,
+    icon: (@Composable () -> Unit)? = null,
+    colors: ChipColors = ChipDefaults.primaryChipColors(),
+    enabled: Boolean = true,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    contentPadding: PaddingValues = ChipDefaults.contentPadding(icon != null),
+) {
+    if (label != null) {
+        Chip(
+            label = label,
+            >
+            modifier = modifier.height(ChipDefaults.CompactChipHeight),
+            icon = icon,
+            colors = colors,
+            enabled = enabled,
+            interactionSource = interactionSource,
+            contentPadding = contentPadding
+        )
+    } else {
+        Chip(
+            >
+            modifier = modifier
+                .height(ChipDefaults.CompactChipHeight)
+                .width(ChipDefaults.IconOnlyCompactChipWidth),
+            colors = colors,
+            enabled = enabled,
+            interactionSource = interactionSource,
+            contentPadding = contentPadding
+        ) {
+            if (icon != null) {
+                icon()
+            }
+        }
+    }
+}
+
+/**
  * Represents the background and content colors used in a chip in different states.
  *
  * See [ChipDefaults.primaryChipColors] for the default colors used in a primary styled [Chip].
@@ -377,6 +453,18 @@
     internal val Height = 52.dp
 
     /**
+     * The height applied for the [CompactChip].
+     * Note that you can override it by applying Modifier.height directly on [CompactChip].
+     */
+    internal val CompactChipHeight = 32.dp
+
+    /**
+     * The default width applied for the [CompactChip] when it has no label provided.
+     * Note that you can override it by applying Modifier.width directly on [CompactChip].
+     */
+    internal val IconOnlyCompactChipWidth = 52.dp
+
+    /**
      * The default size of the icon when used inside a [Chip].
      */
     public val IconSize = 24.dp