blob: 05d429f457abfe64a1b4013aad19005753295651 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.wear.compose.materialcore
import androidx.annotation.RestrictTo
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Indication
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.paint
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
/**
* Base level [Chip] that offers a single slot to take any content.
*
* Is used as the container for more opinionated [Chip] components that take specific content such
* as icons and labels.
*
* Chips can be enabled or disabled. A disabled chip will not respond to click events.
*
* For more information, see the
* [Chips](https://developer.android.com/training/wearables/components/chips)
* guide.
*
* @param onClick Will be called when the user clicks the chip
* @param background Resolves the background for this chip in different states.
* @param border Resolves the border for this chip in different states.
* @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 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
* shape is a key characteristic of the Wear Material Theme
* @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 role The type of user interface element. Accessibility services might use this
* to describe the element or do customizations
* @param ripple Ripple used for this chip
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
fun Chip(
onClick: () -> Unit,
background: @Composable (enabled: Boolean) -> State<Painter>,
border: @Composable (enabled: Boolean) -> State<BorderStroke?>?,
modifier: Modifier,
enabled: Boolean,
contentPadding: PaddingValues,
shape: Shape,
interactionSource: MutableInteractionSource,
role: Role?,
ripple: Indication,
content: @Composable RowScope.() -> Unit,
) {
val borderStroke = border(enabled)?.value
Row(
modifier = modifier
.then(
if (borderStroke != null) Modifier.border(
border = borderStroke,
shape = shape
) else Modifier
)
.clip(shape = shape)
.width(intrinsicSize = IntrinsicSize.Max)
.paint(
painter = background(enabled).value,
contentScale = ContentScale.Crop
)
.clickable(
enabled = enabled,
onClick = onClick,
role = role,
indication = ripple,
interactionSource = interactionSource,
)
.padding(contentPadding),
content = content
)
}
/**
* 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, at the start of a row, with a column next containing the two label slots.
*
* The [Chip] has a max height designed to take no more than two lines of text
* If no secondary label is provided then the label
* can be two lines of text. The label and secondary label should be consistently aligned.
*
* 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 [Chip] can have different styles with configurable content colors and painted background
* including gradients.
*
* Chips can be enabled or disabled. A disabled chip will not respond to click events.
*
* For more information, see the
* [Chips](https://developer.android.com/training/wearables/components/chips)
* guide.
*
* @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 background Resolves the background for this chip in different states.
* @param modifier Modifier to be applied to the chip
* @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.
* @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.
* @param shape Defines the chip's shape. It is strongly recommended to use the default as this
* shape is a key characteristic of the Wear Material Theme.
* @param border Resolves the chip border in different states.
* @param defaultIconSpacing Spacing between icon and label, if both are provided.
* @param role Role semantics that accessibility services can use to provide more
* context to users.
* @param ripple Ripple used for this chip
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
fun Chip(
label: @Composable RowScope.() -> Unit,
onClick: () -> Unit,
background: @Composable (enabled: Boolean) -> State<Painter>,
modifier: Modifier,
secondaryLabel: (@Composable RowScope.() -> Unit)?,
icon: (@Composable BoxScope.() -> Unit)?,
enabled: Boolean,
interactionSource: MutableInteractionSource,
contentPadding: PaddingValues,
shape: Shape,
border: @Composable (enabled: Boolean) -> State<BorderStroke?>?,
defaultIconSpacing: Dp,
role: Role?,
ripple: Indication
) {
Chip(
modifier = modifier,
onClick = onClick,
background = background,
border = border,
enabled = enabled,
contentPadding = contentPadding,
shape = shape,
interactionSource = interactionSource,
role = role,
ripple = ripple
) {
Row(
verticalAlignment = Alignment.CenterVertically,
// Fill the container height but not its width as chips have fixed size height but we
// want them to be able to fit their content
modifier = Modifier.fillMaxHeight()
) {
if (icon != null) {
Box(
modifier = Modifier.wrapContentSize(align = Alignment.Center),
content = icon
)
Spacer(modifier = Modifier.size(defaultIconSpacing))
}
Column {
Row(content = label)
secondaryLabel?.let {
Row(content = secondaryLabel)
}
}
}
}
}
/**
* 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] has a max height designed to take no more than one line
* of text and/or one icon. This includes a visible chip height of 32.dp and
* 8.dp of padding above and below the chip in order to meet accessibility guidelines that
* request a minimum of 48.dp height and width of tappable area.
*
* 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.
*
* 2. If only an icon is provided it will be laid out vertically and horizontally centered
* and the default width of [defaultIconOnlyCompactChipWidth]
*
* If neither icon nor label is provided then the chip will displayed like an icon only chip but
* with no contents and [background] color.
*
* The [CompactChip] can have different styles with configurable content colors and backgrounds
* including gradients.
*
* Chips can be enabled or disabled. A disabled chip will not respond to click events.
*
* For more information, see the
* [Chips](https://developer.android.com/training/wearables/components/chips)
* guide.
*
* @param onClick Will be called when the user clicks the chip
* @param background Resolves the background for this chip in different states.
* @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 "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.
* @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
* @param shape Defines the chip's shape. It is strongly recommended to use the default as this
* shape is a key characteristic of the Wear Material Theme
* @param border Resolves the border for this chip in different states.
* @param defaultIconOnlyCompactChipWidth The default width of the compact chip if there is no label
* @param defaultCompactChipTapTargetPadding Default padding to increase the tap target
* @param defaultIconSpacing Spacing between icon and label, if both are provided
* @param role Role semantics that accessibility services can use to provide more
* context to users.
* @param ripple Ripple used for this chip
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
fun CompactChip(
onClick: () -> Unit,
background: @Composable (enabled: Boolean) -> State<Painter>,
modifier: Modifier,
label: (@Composable RowScope.() -> Unit)?,
icon: (@Composable BoxScope.() -> Unit)?,
enabled: Boolean,
interactionSource: MutableInteractionSource,
contentPadding: PaddingValues,
shape: Shape,
border: @Composable (enabled: Boolean) -> State<BorderStroke?>?,
defaultIconOnlyCompactChipWidth: Dp,
defaultCompactChipTapTargetPadding: PaddingValues,
defaultIconSpacing: Dp,
role: Role?,
ripple: Indication
) {
if (label != null) {
Chip(
modifier = modifier
.padding(defaultCompactChipTapTargetPadding),
label = label,
onClick = onClick,
background = background,
secondaryLabel = null,
icon = icon,
enabled = enabled,
interactionSource = interactionSource,
contentPadding = contentPadding,
shape = shape,
border = border,
defaultIconSpacing = defaultIconSpacing,
role = role,
ripple = ripple
)
} else {
// Icon only compact chips have their own layout with a specific width and center aligned
// content. We use the base simple single slot Chip under the covers.
Chip(
modifier = modifier
.width(defaultIconOnlyCompactChipWidth)
.padding(defaultCompactChipTapTargetPadding),
onClick = onClick,
background = background,
border = border,
enabled = enabled,
contentPadding = contentPadding,
shape = shape,
interactionSource = interactionSource,
role = role,
ripple = ripple
) {
// Use a box to fill and center align the icon into the single slot of the Chip
Box(modifier = Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.Center)) {
if (icon != null) {
icon()
}
}
}
}
}