Merge "Fix expandedItem's behavior when they have buttons." into androidx-main
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedBoxTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedBoxTest.kt
index cc730a6..55ac19f 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedBoxTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedBoxTest.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
+import androidx.compose.testutils.assertDoesNotContainColor
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.LayoutCoordinates
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/ExpandableTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/ExpandableTest.kt
new file mode 100644
index 0000000..b79b17d
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/ExpandableTest.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.foundation
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+
+class ExpandableTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun initially_collapsed() =
+ verifyExpandable(
+ setupState = { rememberExpandableState(initiallyExpanded = false) },
+ bitmapAssert = {
+ assertDoesContainColor(COLLAPSED_COLOR)
+ }
+ )
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun initially_expanded() =
+ verifyExpandable(
+ setupState = { rememberExpandableState(initiallyExpanded = true) },
+ bitmapAssert = {
+ assertDoesContainColor(EXPANDED_COLOR)
+ }
+ )
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun expand() =
+ verifyExpandable(
+ setupState = { rememberExpandableState(initiallyExpanded = false) },
+ bitmapAssert = {
+ assertDoesContainColor(EXPANDED_COLOR)
+ }
+ ) { state ->
+ state.expanded = true
+ waitForIdle()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun collapse() =
+ verifyExpandable(
+ setupState = { rememberExpandableState(initiallyExpanded = true) },
+ bitmapAssert = {
+ assertDoesContainColor(COLLAPSED_COLOR)
+ }
+ ) { state ->
+ state.expanded = false
+ waitForIdle()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun collapsed_click() = verifyClick(false)
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun expanded_click() = verifyClick(true)
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun verifyClick(initiallyExpanded: Boolean) {
+ val clicked = mutableListOf<Boolean>()
+ verifyExpandable(
+ setupState = { rememberExpandableState(initiallyExpanded = initiallyExpanded) },
+ bitmapAssert = {
+ assertEquals(listOf(initiallyExpanded), clicked)
+ },
+ expandableContent = { expanded ->
+ Box(modifier = Modifier.fillMaxSize().clickable {
+ clicked.add(expanded)
+ })
+ }
+ ) { _ ->
+ onNodeWithTag(TEST_TAG).performClick()
+ waitForIdle()
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun verifyExpandable(
+ setupState: @Composable () -> ExpandableState,
+ bitmapAssert: ImageBitmap.() -> Unit,
+ expandableContent: @Composable (Boolean) -> Unit = { },
+ act: ComposeTestRule.(ExpandableState) -> Unit = { }
+ ) {
+ // Arrange - set up the content for the test including expandable content
+ var slcState: ScalingLazyListState? = null
+ var state: ExpandableState? = null
+ rule.setContent {
+ state = setupState()
+ Box(
+ Modifier
+ .testTag(TEST_TAG)
+ .size(100.dp)) {
+ ScalingLazyColumn(
+ state = rememberScalingLazyListState().also { slcState = it },
+ // We can only test expandableItem inside a ScalingLazyColumn, but we can make
+ // it behave mostly as it wasn't there.
+ scalingParams = ScalingLazyColumnDefaults
+ .scalingParams(edgeScale = 1f, edgeAlpha = 1f),
+ autoCentering = null,
+ verticalArrangement = Arrangement.spacedBy(space = 0.dp),
+ ) {
+ expandableItem(state!!) { expanded ->
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ .background(
+ if (expanded) EXPANDED_COLOR else COLLAPSED_COLOR
+ )
+ ) {
+ expandableContent(expanded)
+ }
+ }
+ }
+ }
+ }
+ rule.waitUntil { slcState?.initialized?.value ?: false }
+
+ // Act - exercise the expandable if required for the test.
+ with(rule) {
+ act(state!!)
+ }
+
+ // Assert - verify the object under test worked correctly
+ rule.onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .apply { bitmapAssert() }
+ }
+
+ private val EXPANDED_COLOR = Color.Red
+ private val COLLAPSED_COLOR = Color.Green
+}
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
index 0da21e5..f781f3a 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
@@ -35,12 +35,25 @@
/**
* Checks whether [expectedColor] does not exist in current [ImageBitmap]
- */
+ *
fun ImageBitmap.assertDoesNotContainColor(expectedColor: Color) {
val histogram = histogram()
if (histogram.containsKey(expectedColor)) {
throw AssertionError("Expected color $expectedColor exists in current bitmap")
}
+}*/
+
+/**
+ * Checks whether [expectedColor] exist in current [ImageBitmap], covering at least the given ratio
+ * of the image
+ */
+fun ImageBitmap.assertDoesContainColor(expectedColor: Color, expectedRatio: Float = 0.75f) {
+ val histogram = histogram()
+ val ratio = (histogram.getOrDefault(expectedColor, 0L)).toFloat() / (width * height)
+ if (ratio < expectedRatio) {
+ throw AssertionError("Expected color $expectedColor with ratio $expectedRatio." +
+ " Actual ratio = $ratio")
+ }
}
private fun ImageBitmap.histogram(): MutableMap<Color, Long> {
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/Expandable.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/Expandable.kt
index db910b1..20ebcae 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/Expandable.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/Expandable.kt
@@ -199,8 +199,14 @@
val off1 = (width - placeables[1].width) / 2
layout(width, height) {
- placeables[0].placeWithLayer(off0, 0) { alpha = 1 - progress }
- placeables[1].placeWithLayer(off1, 0) { alpha = progress }
+ if (progress < 1f) {
+ placeables[0].placeWithLayer(off0, 0, zIndex = 1 - progress) {
+ alpha = 1 - progress
+ }
+ }
+ if (progress > 0f) {
+ placeables[1].placeWithLayer(off1, 0, zIndex = progress) { alpha = progress }
+ }
}
}
}
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt
index 2d24e25..a676141 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt
@@ -26,6 +26,7 @@
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.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
@@ -45,6 +46,7 @@
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.CompactChip
+import androidx.wear.compose.material.ListHeader
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
@@ -117,6 +119,7 @@
@Composable
fun ExpandableText() {
val state = rememberExpandableState()
+ val state2 = rememberExpandableState()
ContainingScalingLazyColumn {
expandableItem(state) { expanded ->
@@ -132,6 +135,22 @@
)
}
expandButton(state, outline = false)
+
+ demoSeparator()
+ item {
+ ListHeader {
+ Text("Inline expandable.")
+ }
+ }
+ expandableItem(state2) { expanded ->
+ Row(verticalAlignment = CenterVertically) {
+ Text(if (expanded) "Expanded" else "Collapsed")
+ Spacer(Modifier.width(10.dp))
+ Button(onClick = { state2.expanded = !expanded }) {
+ Text(if (expanded) "-" else "+")
+ }
+ }
+ }
}
}