diff --git a/redwood-lazylayout-compose/api/redwood-lazylayout-compose.api b/redwood-lazylayout-compose/api/redwood-lazylayout-compose.api index 39631244a9..a7e6316a68 100644 --- a/redwood-lazylayout-compose/api/redwood-lazylayout-compose.api +++ b/redwood-lazylayout-compose/api/redwood-lazylayout-compose.api @@ -52,8 +52,8 @@ public final class app/cash/redwood/lazylayout/compose/RefreshableLazyListKt { public final class app/cash/redwood/lazylayout/compose/ScrollOptimizedLoadingStrategy : app/cash/redwood/lazylayout/compose/LoadingStrategy { public static final field $stable I public fun ()V - public fun (IIIIZ)V - public synthetic fun (IIIIZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (IIII)V + public synthetic fun (IIIIILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getFirstIndex ()I public final fun getLastIndex ()I public final fun getProgrammaticScrollIndex ()Lapp/cash/redwood/lazylayout/api/ScrollItemIndex; diff --git a/redwood-lazylayout-compose/api/redwood-lazylayout-compose.klib.api b/redwood-lazylayout-compose/api/redwood-lazylayout-compose.klib.api index e57559a386..5568b7b52d 100644 --- a/redwood-lazylayout-compose/api/redwood-lazylayout-compose.klib.api +++ b/redwood-lazylayout-compose/api/redwood-lazylayout-compose.klib.api @@ -25,7 +25,7 @@ abstract interface app.cash.redwood.lazylayout.compose/LoadingStrategy { // app. } final class app.cash.redwood.lazylayout.compose/ScrollOptimizedLoadingStrategy : app.cash.redwood.lazylayout.compose/LoadingStrategy { // app.cash.redwood.lazylayout.compose/ScrollOptimizedLoadingStrategy|null[0] - constructor (kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Boolean = ...) // app.cash.redwood.lazylayout.compose/ScrollOptimizedLoadingStrategy.|(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] + constructor (kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ...) // app.cash.redwood.lazylayout.compose/ScrollOptimizedLoadingStrategy.|(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final var firstIndex // app.cash.redwood.lazylayout.compose/ScrollOptimizedLoadingStrategy.firstIndex|{}firstIndex[0] final fun (): kotlin/Int // app.cash.redwood.lazylayout.compose/ScrollOptimizedLoadingStrategy.firstIndex.|(){}[0] diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/ScrollOptimizedLoadingStrategy.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/ScrollOptimizedLoadingStrategy.kt index 5f0f2cf0e2..56f8a019f8 100644 --- a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/ScrollOptimizedLoadingStrategy.kt +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/ScrollOptimizedLoadingStrategy.kt @@ -42,7 +42,6 @@ public class ScrollOptimizedLoadingStrategy( private val scrollInProgressPreloadItemCount: Int = SCROLL_IN_PROGRESS_PRELOAD_ITEM_COUNT, private val primaryPreloadItemCount: Int = PRIMARY_PRELOAD_ITEM_COUNT, private val secondaryPreloadItemCount: Int = SECONDARY_PRELOAD_ITEM_COUNT, - private val preloadItems: Boolean = true, ) : LoadingStrategy { /** * Update this to trigger a programmatic scroll. This may be updated multiple times, including @@ -92,11 +91,6 @@ public class ScrollOptimizedLoadingStrategy( // Expand the range depending on scroll direction. when { - // Ignore preloads. - !preloadItems -> { - // No-op - } - isScrollingDown -> { begin -= scrollInProgressPreloadItemCount end += primaryPreloadItemCount diff --git a/redwood-lazylayout-compose/src/commonTest/kotlin/app/cash/redwood/lazylayout/compose/LazyListTest.kt b/redwood-lazylayout-compose/src/commonTest/kotlin/app/cash/redwood/lazylayout/compose/LazyListTest.kt index 1c4502abf8..09a40e7c2e 100644 --- a/redwood-lazylayout-compose/src/commonTest/kotlin/app/cash/redwood/lazylayout/compose/LazyListTest.kt +++ b/redwood-lazylayout-compose/src/commonTest/kotlin/app/cash/redwood/lazylayout/compose/LazyListTest.kt @@ -116,9 +116,7 @@ class LazyListTest { TestSchemaTester { var index5ComposeCount = 0 setContent { - val lazyListState = rememberLazyListState( - ScrollOptimizedLoadingStrategy(preloadItems = false), - ) + val lazyListState = rememberLazyListState(DirectLoadingStrategy()) LazyColumn( state = lazyListState, placeholder = { Text("Placeholder") }, @@ -149,21 +147,12 @@ class LazyListTest { awaitSnapshot() assertThat(index5ComposeCount).isEqualTo(1) - // Even when it's scrolled off-screen. + // It isn't recomposed when it's out of the loaded range. lazyList.onViewportChanged(6, 10) awaitSnapshot() assertThat(index5ComposeCount).isEqualTo(1) - // The item at index 5 remains composed because (6, 10) is contiguous with (4, 8). - lazyList.onViewportChanged(4, 8) - awaitSnapshot() - assertThat(index5ComposeCount).isEqualTo(1) - - // Scrolling to a non-contiguous range causes a recomposition. - lazyList.onViewportChanged(9, 13) - awaitSnapshot() - assertThat(index5ComposeCount).isEqualTo(1) - + // When it's scrolled back into range, it's recomposed. lazyList.onViewportChanged(4, 8) awaitSnapshot() assertThat(index5ComposeCount).isEqualTo(2) diff --git a/redwood-lazylayout-testing/api/redwood-lazylayout-testing.api b/redwood-lazylayout-testing/api/redwood-lazylayout-testing.api index d687bb2aae..84beda8450 100644 --- a/redwood-lazylayout-testing/api/redwood-lazylayout-testing.api +++ b/redwood-lazylayout-testing/api/redwood-lazylayout-testing.api @@ -1,3 +1,11 @@ +public final class app/cash/redwood/lazylayout/compose/DirectLoadingStrategy : app/cash/redwood/lazylayout/compose/LoadingStrategy { + public fun ()V + public fun getFirstIndex ()I + public fun loadRange (I)Lkotlin/ranges/IntRange; + public fun onUserScroll (II)V + public fun scrollTo (I)V +} + public final class app/cash/redwood/lazylayout/testing/LazyListValue : app/cash/redwood/testing/WidgetValue { public synthetic fun (Lapp/cash/redwood/Modifier;ZLkotlin/jvm/functions/Function2;IIIILapp/cash/redwood/ui/Margin;ILapp/cash/redwood/lazylayout/api/ScrollItemIndex;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lapp/cash/redwood/Modifier;ZLkotlin/jvm/functions/Function2;IIIILapp/cash/redwood/ui/Margin;ILapp/cash/redwood/lazylayout/api/ScrollItemIndex;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/redwood-lazylayout-testing/api/redwood-lazylayout-testing.klib.api b/redwood-lazylayout-testing/api/redwood-lazylayout-testing.klib.api index bb3889ec03..fd797b2200 100644 --- a/redwood-lazylayout-testing/api/redwood-lazylayout-testing.klib.api +++ b/redwood-lazylayout-testing/api/redwood-lazylayout-testing.klib.api @@ -6,6 +6,17 @@ // - Show declarations: true // Library unique name: +final class app.cash.redwood.lazylayout.compose/DirectLoadingStrategy : app.cash.redwood.lazylayout.compose/LoadingStrategy { // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy|null[0] + constructor () // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy.|(){}[0] + + final val firstIndex // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy.firstIndex|{}firstIndex[0] + final fun (): kotlin/Int // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy.firstIndex.|(){}[0] + + final fun loadRange(kotlin/Int): kotlin.ranges/IntRange // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy.loadRange|loadRange(kotlin.Int){}[0] + final fun onUserScroll(kotlin/Int, kotlin/Int) // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy.onUserScroll|onUserScroll(kotlin.Int;kotlin.Int){}[0] + final fun scrollTo(kotlin/Int) // app.cash.redwood.lazylayout.compose/DirectLoadingStrategy.scrollTo|scrollTo(kotlin.Int){}[0] +} + final class app.cash.redwood.lazylayout.testing/LazyListValue : app.cash.redwood.testing/WidgetValue { // app.cash.redwood.lazylayout.testing/LazyListValue|null[0] constructor (app.cash.redwood/Modifier = ..., kotlin/Boolean, kotlin/Function2, kotlin/Int, kotlin/Int, app.cash.redwood.layout.api/Constraint, app.cash.redwood.layout.api/Constraint, app.cash.redwood.ui/Margin, app.cash.redwood.layout.api/CrossAxisAlignment, app.cash.redwood.lazylayout.api/ScrollItemIndex, kotlin.collections/List = ..., kotlin.collections/List = ...) // app.cash.redwood.lazylayout.testing/LazyListValue.|(app.cash.redwood.Modifier;kotlin.Boolean;kotlin.Function2;kotlin.Int;kotlin.Int;app.cash.redwood.layout.api.Constraint;app.cash.redwood.layout.api.Constraint;app.cash.redwood.ui.Margin;app.cash.redwood.layout.api.CrossAxisAlignment;app.cash.redwood.lazylayout.api.ScrollItemIndex;kotlin.collections.List;kotlin.collections.List){}[0] diff --git a/redwood-lazylayout-testing/build.gradle b/redwood-lazylayout-testing/build.gradle index 231fb26c0b..876f22f7bd 100644 --- a/redwood-lazylayout-testing/build.gradle +++ b/redwood-lazylayout-testing/build.gradle @@ -11,6 +11,7 @@ kotlin { sourceSets { commonMain { dependencies { + api projects.redwoodLazylayoutCompose api projects.redwoodLazylayoutWidget } } diff --git a/redwood-lazylayout-testing/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/DirectLoadingStrategy.kt b/redwood-lazylayout-testing/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/DirectLoadingStrategy.kt new file mode 100644 index 0000000000..912748a2ed --- /dev/null +++ b/redwood-lazylayout-testing/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/DirectLoadingStrategy.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * 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 app.cash.redwood.lazylayout.compose + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +/** + * A loading strategy that's appropriate for tests because it's simple and predictable. + * + * This is not suitable for production use it must show a placeholder before an item is loaded. + */ +public class DirectLoadingStrategy : LoadingStrategy { + private var loadRange: IntRange by mutableStateOf(0..0) + + public override val firstIndex: Int + get() = loadRange.first + + override fun scrollTo(firstIndex: Int) { + require(firstIndex >= 0) + loadRange = firstIndex..firstIndex + (loadRange.last - loadRange.first) + } + + override fun onUserScroll(firstIndex: Int, lastIndex: Int) { + loadRange = firstIndex..lastIndex + } + + override fun loadRange(itemCount: Int): IntRange = + loadRange.first.coerceIn(0, itemCount - 1)..loadRange.last.coerceIn(0, itemCount - 1) +}