Fix LazyColumn/Row cross axis wrap content

Sometimes it was ignoring the items which would be visible and sometimes was using the size of item which is in fact outside of the viewport

Test: new tests
Change-Id: I1c0b08ea37ccec71b5156793ee8fe3541c8fcdbd
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
index 4d6ba65..bbd257b 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.ExponentialDecay
 import androidx.compose.animation.core.ManualAnimationClock
+import androidx.compose.animation.core.snap
 import androidx.compose.foundation.animation.FlingConfig
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
@@ -1040,9 +1041,72 @@
         }
     }
 
+    @Test
+    fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
+        val items = (0..1).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        val itemSizeMinusOne = with(rule.density) { 29.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumnFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.height(itemSizeMinusOne).testTag(LazyColumnForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.width(30.dp).height(itemSizeMinusOne)
+                    } else {
+                        Modifier.width(20.dp).height(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyColumnForTag)
+            .assertWidthIsEqualTo(20.dp)
+    }
+
+    @Test
+    fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
+        val items = (0..2).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumnFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.height(itemSize * 1.75f).testTag(LazyColumnForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.width(30.dp).height(itemSize / 2)
+                    } else if (it == 1) {
+                        Modifier.width(20.dp).height(itemSize / 2)
+                    } else {
+                        Modifier.width(20.dp).height(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyColumnForTag)
+            .assertWidthIsEqualTo(30.dp)
+    }
+
     private fun SemanticsNodeInteraction.assertTopPositionIsAlmost(expected: Dp) {
         getUnclippedBoundsInRoot().top.assertIsEqualTo(expected, tolerance = 1.dp)
     }
+
+    private fun LazyListState.scrollBy(offset: Dp) {
+        runBlocking {
+            smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
+        }
+    }
 }
 
 data class NotStable(val count: Int)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
index 6540372..f4d3fd4 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.animation.core.snap
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -836,4 +837,67 @@
                 .that(redrawCount[1]).isEqualTo(2)
         }
     }
+
+    @Test
+    fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
+        val items = (0..1).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        val itemSizeMinusOne = with(rule.density) { 29.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRowFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.width(itemSizeMinusOne).testTag(LazyRowForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.height(30.dp).width(itemSizeMinusOne)
+                    } else {
+                        Modifier.height(20.dp).width(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyRowForTag)
+            .assertHeightIsEqualTo(20.dp)
+    }
+
+    @Test
+    fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
+        val items = (0..2).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRowFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.width(itemSize * 1.75f).testTag(LazyRowForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.height(30.dp).width(itemSize / 2)
+                    } else if (it == 1) {
+                        Modifier.height(20.dp).width(itemSize / 2)
+                    } else {
+                        Modifier.height(20.dp).width(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyRowForTag)
+            .assertHeightIsEqualTo(30.dp)
+    }
+
+    private fun LazyListState.scrollBy(offset: Dp) {
+        runBlocking {
+            smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
+        }
+    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 484d23b..45ea9be 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -323,6 +323,9 @@
             val minOffset = -startContentPadding
             val maxOffset = mainAxisMaxSize
 
+            // max of cross axis sizes of all visible items
+            var maxCrossAxis = 0
+
             // we had scrolled backward or we compose items in the start padding area, which means
             // items before current firstItemScrollOffset should be visible. compose them and update
             // firstItemScrollOffset
@@ -330,6 +333,7 @@
                 val previous = DataIndex(currentFirstItemIndex.value - 1)
                 val measuredItem = itemProvider.getAndMeasure(previous)
                 visibleItems.add(0, measuredItem)
+                maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
                 currentFirstItemScrollOffset += measuredItem.mainAxisSize
                 currentFirstItemIndex = previous
             }
@@ -351,11 +355,9 @@
             var index = goingForwardInitialIndex
             val maxMainAxis = maxOffset + endContentPadding
             var mainAxisUsed = -goingForwardInitialScrollOffset
-            var maxCrossAxis = 0
             while (mainAxisUsed <= maxMainAxis && index.value < itemsCount) {
                 val measuredItem = itemProvider.getAndMeasure(index)
                 mainAxisUsed += measuredItem.mainAxisSize
-                maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
 
                 if (mainAxisUsed < minOffset) {
                     // this item is offscreen and will not be placed. advance firstVisibleItemIndex
@@ -368,6 +370,7 @@
                     }
                     notUsedButComposedItems.add(measuredItem)
                 } else {
+                    maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
                     visibleItems.add(measuredItem)
                 }
 
@@ -389,6 +392,7 @@
                         itemProvider.getAndMeasure(previous)
                     }
                     visibleItems.add(0, measuredItem)
+                    maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
                     currentFirstItemScrollOffset += measuredItem.mainAxisSize
                     currentFirstItemIndex = previous
                 }