Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chip longClick, doubleClick #2028

Merged
merged 15 commits into from
Feb 13, 2024
6 changes: 3 additions & 3 deletions compose-material/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ package com.google.android.horologist.compose.material {
}

public final class ChipKt {
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Chip(@StringRes int labelId, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional @StringRes Integer? secondaryLabel, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional com.google.android.horologist.images.base.paintable.Paintable? icon, optional boolean largeIcon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Chip(String label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional String? secondaryLabel, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional com.google.android.horologist.images.base.paintable.Paintable? icon, optional boolean largeIcon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Chip(String label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional String? secondaryLabel, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional boolean largeIcon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Chip(@StringRes int labelId, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional @StringRes Integer? secondaryLabel, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional com.google.android.horologist.images.base.paintable.Paintable? icon, optional boolean largeIcon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Chip(String label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional String? secondaryLabel, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional com.google.android.horologist.images.base.paintable.Paintable? icon, optional boolean largeIcon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Chip(String label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional String? secondaryLabel, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional boolean largeIcon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled);
}

public final class CompactChipKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,62 @@
* limitations under the License.
*/

@file:OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)

package com.google.android.horologist.compose.material

import androidx.annotation.StringRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
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.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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.text
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipBorder
import androidx.wear.compose.material.ChipColors
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.LocalContentAlpha
import androidx.wear.compose.material.LocalContentColor
import androidx.wear.compose.material.LocalTextStyle
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
import com.google.android.horologist.images.base.paintable.Paintable
import com.google.android.horologist.images.base.paintable.PaintableIcon
import androidx.wear.compose.material.Chip as MaterialChip

/**
* This component is an alternative to [Chip], providing the following:
* - a convenient way of providing a label and a secondary label;
Expand All @@ -54,6 +82,8 @@ public fun Chip(
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
secondaryLabel: String? = null,
iconRtlMode: IconRtlMode = IconRtlMode.Default,
icon: Paintable? = null,
Expand Down Expand Up @@ -97,6 +127,8 @@ public fun Chip(
Chip(
label = label,
onClick = onClick,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
modifier = modifier,
secondaryLabel = secondaryLabel,
icon = iconParam,
Expand All @@ -118,6 +150,8 @@ public fun Chip(
@StringRes labelId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
@StringRes secondaryLabel: Int? = null,
iconRtlMode: IconRtlMode = IconRtlMode.Default,
icon: Paintable? = null,
Expand All @@ -128,6 +162,8 @@ public fun Chip(
Chip(
label = stringResource(id = labelId),
onClick = onClick,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
modifier = modifier,
secondaryLabel = secondaryLabel?.let { stringResource(id = it) },
icon = icon,
Expand All @@ -148,6 +184,8 @@ public fun Chip(
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
secondaryLabel: String? = null,
icon: (@Composable BoxScope.() -> Unit)? = null,
largeIcon: Boolean = false,
Expand Down Expand Up @@ -194,12 +232,168 @@ public fun Chip(
Chip(
label = labelParam,
onClick = onClick,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
modifier = modifier
.fillMaxWidth(),
.fillMaxWidth()
.clearAndSetSemantics {
text = buildAnnotatedString {
append(label)
if (secondaryLabel != null) {
append(", ")
append(secondaryLabel)
}
}
role = Role.Button
if (!enabled) {
disabled()
}
},
secondaryLabel = secondaryLabelParam,
icon = icon,
colors = colors,
enabled = enabled,
contentPadding = contentPadding,
)
}

/**
* Temporary copy of Wear Compose Material Chip with support for
* onLongClick, onDoubleClick.
*/
@Composable
internal fun Chip(
label: @Composable RowScope.() -> Unit,
yschimke marked this conversation as resolved.
Show resolved Hide resolved
onClick: () -> Unit,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
secondaryLabel: (@Composable RowScope.() -> Unit)? = null,
icon: (@Composable BoxScope.() -> Unit)? = null,
colors: ChipColors = ChipDefaults.primaryChipColors(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
contentPadding: PaddingValues = ChipDefaults.ContentPadding,
shape: Shape = MaterialTheme.shapes.large,
border: ChipBorder = ChipDefaults.chipBorder(),
) {
Chip(
onClick = onClick,
colors = colors,
border = border,
modifier = modifier,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
enabled = enabled,
contentPadding = contentPadding,
shape = shape,
interactionSource = interactionSource,
role = Role.Button,
) {
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 = {
val color = colors.iconColor(enabled).value
CompositionLocalProvider(
LocalContentColor provides color,
LocalContentAlpha provides color.alpha,
) {
icon()
}
},
)
Spacer(modifier = Modifier.size(IconSpacing))
}
Column {
Row(
content = {
val color = colors.contentColor(enabled).value
CompositionLocalProvider(
LocalContentColor provides color,
LocalContentAlpha provides color.alpha,
LocalTextStyle provides MaterialTheme.typography.button,
) {
label()
}
},
)
secondaryLabel?.let {
Row(
content = {
val color = colors.secondaryContentColor(enabled).value
CompositionLocalProvider(
LocalContentColor provides color,
LocalContentAlpha provides color.alpha,
LocalTextStyle provides MaterialTheme.typography.caption2,
) {
secondaryLabel()
}
},
)
}
}
}
}
}

/**
* Temporary copy of Wear Compose Material Chip with support for
* onLongClick, onDoubleClick.
*/
@Composable
internal fun Chip(
onClick: () -> Unit,
colors: ChipColors,
border: ChipBorder,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
enabled: Boolean = true,
contentPadding: PaddingValues = ChipDefaults.ContentPadding,
shape: Shape = MaterialTheme.shapes.large,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
role: Role = Role.Button,
content: @Composable BoxScope.() -> Unit,
) {
MaterialChip(
onClick = onClick,
colors = colors,
border = border,
modifier = modifier,
enabled = enabled,
contentPadding = PaddingValues(0.dp),
shape = shape,
interactionSource = interactionSource,
role = role,
yschimke marked this conversation as resolved.
Show resolved Hide resolved
) {
Box(
modifier = Modifier
.fillMaxSize()
.combinedClickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = onClick,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
role = role,
)
.padding(contentPadding),
) {
content()
}
}
}

/**
* The default size of the spacing between an icon and a text when they are used inside a
* [Chip].
*/
internal val IconSpacing = 6.dp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.printToString
import androidx.wear.compose.material.Text
import com.google.android.horologist.screenshots.ScreenshotBaseTest
import com.google.android.horologist.screenshots.ScreenshotTestRule.Companion.screenshotTestRuleParams
import org.junit.Assert
import org.junit.Test

class CardA11yTest : ScreenshotBaseTest(
Expand Down Expand Up @@ -60,13 +62,7 @@ class CardA11yTest : ScreenshotBaseTest(
}

screenshotTestRule.interact {
println(onRoot().printToString())

val logEntries = onRoot().printToString()
.split("\n")
.map { it.trim() }

Assert.assertEquals(1, logEntries.filter { it.startsWith("Role") }.size)
onAllNodes(SemanticsMatcher.keyIsDefined(SemanticsProperties.Role)).assertCountEquals(1)
yschimke marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Image
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.printToString
import com.google.android.horologist.images.base.paintable.ImageVectorPaintable.Companion.asPaintable
import com.google.android.horologist.screenshots.ScreenshotBaseTest
import com.google.android.horologist.screenshots.ScreenshotTestRule
Expand All @@ -46,6 +51,10 @@ class ChipA11yTest : ScreenshotBaseTest(
)
}
}

screenshotTestRule.interact {
onAllNodes(keyIsDefined(SemanticsProperties.Role)).assertCountEquals(1)
}
}

@Test
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.wear.compose.material.ChipDefaults
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
import com.google.android.horologist.images.base.paintable.Paintable
import com.google.android.horologist.media.ui.components.MediaArtwork

Expand All @@ -41,7 +42,7 @@ public fun ShowPlaylistChip(
{
MediaArtwork(
modifier = Modifier.size(ChipDefaults.LargeIconSize),
contentDescription = name,
contentDescription = DECORATIVE_ELEMENT_CONTENT_DESCRIPTION,
artworkPaintable = it,
)
}
Expand Down
Loading
Loading