Add material 3 Icon and Icon tests into androidx
Bug: b/259530410
Test: Run androidx.wear.compose.material3 tests
Change-Id: I8e06ab15f55eab28513445106da13a9fa7b6aac6
Relnote: N/A
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 911965f..2dbfac5 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -84,6 +84,12 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
}
+ public final class IconKt {
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ }
+
public final class MaterialTheme {
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.ColorScheme getColorScheme();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.Shapes getShapes();
diff --git a/wear/compose/compose-material3/api/public_plus_experimental_current.txt b/wear/compose/compose-material3/api/public_plus_experimental_current.txt
index 911965f..2dbfac5 100644
--- a/wear/compose/compose-material3/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material3/api/public_plus_experimental_current.txt
@@ -84,6 +84,12 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
}
+ public final class IconKt {
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ }
+
public final class MaterialTheme {
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.ColorScheme getColorScheme();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.Shapes getShapes();
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 911965f..2dbfac5 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -84,6 +84,12 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
}
+ public final class IconKt {
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+ }
+
public final class MaterialTheme {
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.ColorScheme getColorScheme();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.wear.compose.material3.Shapes getShapes();
diff --git a/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/IconTest.kt b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/IconTest.kt
new file mode 100644
index 0000000..729aace
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/IconTest.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+
+class IconTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ private val testTag = "TestText"
+
+ @Test
+ fun vector_materialIconSize_dimensions() {
+ val width = 24.dp
+ val height = 24.dp
+ val vector = Icons.Filled.Menu
+ rule
+ .setContentWithThemeForSizeAssertions {
+ Icon(vector, null)
+ }
+ .assertWidthIsEqualTo(width)
+ .assertHeightIsEqualTo(height)
+ }
+
+ @Test
+ fun vector_customIconSize_dimensions() {
+ val width = 35.dp
+ val height = 83.dp
+ val vector = ImageVector.Builder(
+ defaultWidth = width, defaultHeight = height,
+ viewportWidth = width.value, viewportHeight = height.value
+ ).build()
+ rule
+ .setContentWithThemeForSizeAssertions {
+ Icon(vector, null)
+ }
+ .assertWidthIsEqualTo(width)
+ .assertHeightIsEqualTo(height)
+ }
+
+ @Test
+ fun image_noIntrinsicSize_dimensions() {
+ val width = 24.dp
+ val height = 24.dp
+ rule
+ .setContentWithThemeForSizeAssertions {
+ val image = with(LocalDensity.current) {
+ ImageBitmap(width.roundToPx(), height.roundToPx())
+ }
+
+ Icon(image, null)
+ }
+ .assertWidthIsEqualTo(width)
+ .assertHeightIsEqualTo(height)
+ }
+
+ @Test
+ fun image_withIntrinsicSize_dimensions() {
+ val width = 35.dp
+ val height = 83.dp
+
+ rule
+ .setContentWithThemeForSizeAssertions {
+ val image = with(LocalDensity.current) {
+ ImageBitmap(width.roundToPx(), height.roundToPx())
+ }
+
+ Icon(image, null)
+ }
+ .assertWidthIsEqualTo(width)
+ .assertHeightIsEqualTo(height)
+ }
+
+ @Test
+ fun painter_noIntrinsicSize_dimensions() {
+ val width = 24.dp
+ val height = 24.dp
+ val painter = ColorPainter(Color.Red)
+
+ rule
+ .setContentWithThemeForSizeAssertions {
+ Icon(painter, null)
+ }
+ .assertWidthIsEqualTo(width)
+ .assertHeightIsEqualTo(height)
+ }
+
+ @Test
+ fun painter_withIntrinsicSize_dimensions() {
+ val width = 35.dp
+ val height = 83.dp
+
+ rule
+ .setContentWithThemeForSizeAssertions {
+ val image = with(LocalDensity.current) {
+ ImageBitmap(width.roundToPx(), height.roundToPx())
+ }
+
+ val bitmapPainter = BitmapPainter(image)
+ Icon(bitmapPainter, null)
+ }
+ .assertWidthIsEqualTo(width)
+ .assertHeightIsEqualTo(height)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun iconScalesToFitSize() {
+ // Image with intrinsic size of 24dp
+ val width = 24.dp
+ val height = 24.dp
+ var expectedIntSize: IntSize? = null
+
+ rule
+ .setContentWithTheme {
+ val image: ImageBitmap
+ with(LocalDensity.current) {
+ image = createBitmapWithColor(
+ this,
+ width.roundToPx(),
+ height.roundToPx(),
+ Color.Red
+ )
+ }
+ Icon(
+ image,
+ null,
+ // Force Icon to be 50dp
+ modifier = Modifier.requiredSize(50.dp).testTag(testTag),
+ tint = Color.Unspecified
+ )
+ with(LocalDensity.current) {
+ val dimension = 50.dp.roundToPx()
+ expectedIntSize = IntSize(dimension, dimension)
+ }
+ }
+
+ rule.onNodeWithTag(testTag)
+ .captureToImage()
+ // The icon should be 50x50 and fill the whole size with red pixels
+ .assertPixels(expectedSize = expectedIntSize!!) {
+ Color.Red
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun iconUnspecifiedTintColorIgnored() {
+ val width = 35.dp
+ val height = 83.dp
+
+ rule
+ .setContentWithTheme {
+ val image: ImageBitmap
+ with(LocalDensity.current) {
+ image = createBitmapWithColor(
+ this,
+ width.roundToPx(),
+ height.roundToPx(),
+ Color.Red
+ )
+ }
+ Icon(
+ image,
+ null,
+ modifier = Modifier.testTag(testTag),
+ tint = Color.Unspecified
+ )
+ }
+
+ // With no color provided for a tint, the icon should render the original pixels
+ rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Red }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun iconSpecifiedTintColorApplied() {
+ val width = 35.dp
+ val height = 83.dp
+
+ rule
+ .setContentWithTheme {
+ val image: ImageBitmap
+ with(LocalDensity.current) {
+ image = createBitmapWithColor(
+ this,
+ width.roundToPx(),
+ height.roundToPx(),
+ Color.Red
+ )
+ }
+ Icon(
+ image,
+ null,
+ modifier = Modifier.testTag(testTag),
+ tint = Color.Blue
+ )
+ }
+
+ // With a tint color provided, all pixels should be blue
+ rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Blue }
+ }
+
+ @Test
+ fun defaultSemanticsWhenContentDescriptionProvided() {
+ rule
+ .setContent {
+ Icon(
+ bitmap = ImageBitmap(100, 100),
+ contentDescription = "qwerty",
+ modifier = Modifier.testTag(testTag)
+ )
+ }
+
+ rule.onNodeWithTag(testTag)
+ .assertContentDescriptionEquals("qwerty")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Image))
+ }
+
+ private fun createBitmapWithColor(
+ density: Density,
+ width: Int,
+ height: Int,
+ color: Color
+ ): ImageBitmap {
+ val size = Size(width.toFloat(), height.toFloat())
+ val image = ImageBitmap(width, height)
+ CanvasDrawScope().draw(
+ density,
+ LayoutDirection.Ltr,
+ Canvas(image),
+ size
+ ) {
+ drawRect(color)
+ }
+ return image
+ }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
new file mode 100644
index 0000000..f427c75
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidAndroidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+val BigTestMaxWidth = 5000.dp
+val BigTestMaxHeight = 5000.dp
+
+fun ComposeContentTestRule.setContentWithThemeForSizeAssertions(
+ parentMaxWidth: Dp = BigTestMaxWidth,
+ parentMaxHeight: Dp = BigTestMaxHeight,
+ useUnmergedTree: Boolean = false,
+ content: @Composable () -> Unit
+): SemanticsNodeInteraction {
+ setContent {
+ MaterialTheme {
+ Box {
+ Box(
+ Modifier
+ .sizeIn(
+ maxWidth = parentMaxWidth,
+ maxHeight = parentMaxHeight
+ )
+ .testTag("containerForSizeAssertion")
+ ) {
+ content()
+ }
+ }
+ }
+ }
+
+ return onNodeWithTag("containerForSizeAssertion", useUnmergedTree)
+}
+
+fun ComposeContentTestRule.setContentWithTheme(
+ modifier: Modifier = Modifier,
+ composable: @Composable BoxScope.() -> Unit
+) {
+ setContent {
+ MaterialTheme {
+ Box(modifier = modifier, content = composable)
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Icon.kt b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Icon.kt
new file mode 100644
index 0000000..2113a27
--- /dev/null
+++ b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Icon.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+
+/**
+ * Icon component that draws [imageVector] using [tint], defaulting to [LocalContentColor]. For a
+ * clickable icon, see Button.
+ *
+ * @param imageVector [ImageVector] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no
+ * tint is applied
+ */
+@Composable
+fun Icon(
+ imageVector: ImageVector,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+) {
+ Icon(
+ painter = rememberVectorPainter(imageVector),
+ contentDescription = contentDescription,
+ modifier = modifier,
+ tint = tint
+ )
+}
+
+/**
+ * Icon component that draws [bitmap] using [tint], defaulting to [LocalContentColor]. For a
+ * clickable icon, see Button.
+ *
+ * @param bitmap [ImageBitmap] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no
+ * tint is applied
+ */
+@Composable
+fun Icon(
+ bitmap: ImageBitmap,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+) {
+ val painter = remember(bitmap) { BitmapPainter(bitmap) }
+ Icon(
+ painter = painter,
+ contentDescription = contentDescription,
+ modifier = modifier,
+ tint = tint
+ )
+}
+
+/**
+ * Icon component that draws a [painter] using [tint], defaulting to [LocalContentColor]. For a
+ * clickable icon, see Button.
+ *
+ * @param painter [Painter] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [painter]. If [Color.Unspecified] is provided, then no
+ * tint is applied
+ */
+@Composable
+fun Icon(
+ painter: Painter,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+) {
+ androidx.wear.compose.materialcore.Icon(
+ painter = painter,
+ contentDescription = contentDescription,
+ tint = tint,
+ modifier = modifier
+ )
+}
\ No newline at end of file