From ef8332c66880edc9a636eff7d1670ba6ca1364b0 Mon Sep 17 00:00:00 2001 From: Veyndan Stuart Date: Wed, 28 Jun 2023 14:23:17 +0100 Subject: [PATCH] Make `CrossAxisAlignment` modifier available for items within `LazyList` --- redwood-lazylayout-compose/build.gradle | 1 + .../redwood/lazylayout/compose/LazyDsl.kt | 12 ++--- .../lazylayout/compose/LazyItemScope.kt | 30 +++++++++++ .../compose/LazyListIntervalContent.kt | 6 +-- .../compose/LazyListItemProvider.kt | 8 ++- .../redwood/lazylayout/compose/modifier.kt | 19 +++++++ .../lazylayout/composeui/ComposeUiLazyList.kt | 1 + .../redwood/lazylayout/view/ViewLazyList.kt | 52 ++++++++++++------- ...rginAndDifferentAlignments[LTR,Column].png | 4 +- ...rginAndDifferentAlignments[RTL,Column].png | 4 +- 10 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyItemScope.kt create mode 100644 redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/modifier.kt diff --git a/redwood-lazylayout-compose/build.gradle b/redwood-lazylayout-compose/build.gradle index 481a189c60..cdec1a37cb 100644 --- a/redwood-lazylayout-compose/build.gradle +++ b/redwood-lazylayout-compose/build.gradle @@ -13,6 +13,7 @@ kotlin { sourceSets { commonMain { dependencies { + api projects.redwoodLayoutModifiers api projects.redwoodLazylayoutWidget } } diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyDsl.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyDsl.kt index e16e66c720..18a05c4307 100644 --- a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyDsl.kt +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyDsl.kt @@ -25,25 +25,25 @@ import app.cash.redwood.ui.Margin @LayoutScopeMarker public interface LazyListScope { public fun item( - content: @Composable () -> Unit, + content: @Composable LazyItemScope.() -> Unit, ) public fun items( count: Int, - itemContent: @Composable (index: Int) -> Unit, + itemContent: @Composable LazyItemScope.(index: Int) -> Unit, ) } public inline fun LazyListScope.items( items: List, - crossinline itemContent: @Composable (item: T) -> Unit, + crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit, ): Unit = items(items.size) { itemContent(items[it]) } public inline fun LazyListScope.itemsIndexed( items: List, - crossinline itemContent: @Composable (index: Int, item: T) -> Unit, + crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit, ): Unit = items( items.size, ) { @@ -52,7 +52,7 @@ public inline fun LazyListScope.itemsIndexed( public inline fun LazyListScope.items( items: Array, - crossinline itemContent: @Composable (item: T) -> Unit, + crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit, ): Unit = items( items.size, ) { @@ -61,7 +61,7 @@ public inline fun LazyListScope.items( public inline fun LazyListScope.itemsIndexed( items: Array, - crossinline itemContent: @Composable (index: Int, item: T) -> Unit, + crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit, ): Unit = items( items.size, ) { diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyItemScope.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyItemScope.kt new file mode 100644 index 0000000000..bbe3d74d48 --- /dev/null +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyItemScope.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.Stable +import app.cash.redwood.LayoutScopeMarker +import app.cash.redwood.Modifier +import app.cash.redwood.layout.api.CrossAxisAlignment + +@LayoutScopeMarker +public interface LazyItemScope { + @Stable + public fun Modifier.alignment(alignment: CrossAxisAlignment): Modifier = + then(AlignmentImpl(alignment)) +} + +internal object LazyItemScopeImpl : LazyItemScope diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListIntervalContent.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListIntervalContent.kt index f3e1e07919..d416aa8f22 100644 --- a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListIntervalContent.kt +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListIntervalContent.kt @@ -33,7 +33,7 @@ internal class LazyListIntervalContent( override fun items( count: Int, - itemContent: @Composable (index: Int) -> Unit, + itemContent: @Composable LazyItemScope.(index: Int) -> Unit, ) { intervals.addInterval( count, @@ -43,7 +43,7 @@ internal class LazyListIntervalContent( ) } - override fun item(content: @Composable () -> Unit) { + override fun item(content: @Composable LazyItemScope.() -> Unit) { intervals.addInterval( 1, LazyListInterval( @@ -54,5 +54,5 @@ internal class LazyListIntervalContent( } internal class LazyListInterval( - val item: @Composable (index: Int) -> Unit, + val item: @Composable LazyItemScope.(index: Int) -> Unit, ) : LazyLayoutIntervalContent.Interval diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListItemProvider.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListItemProvider.kt index 8debba9fea..5489c624f8 100644 --- a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListItemProvider.kt +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListItemProvider.kt @@ -27,7 +27,9 @@ import app.cash.redwood.lazylayout.compose.layout.LazyLayoutItemProvider // Copied from https://github.com/androidx/androidx/blob/a733905d282ecdba574bc5e35d6b0ebf83c82dcd/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemProvider.kt // Removed support for content types, header indices, item scope, and pinnable items. -internal interface LazyListItemProvider : LazyLayoutItemProvider +internal interface LazyListItemProvider : LazyLayoutItemProvider { + val itemScope: LazyItemScopeImpl +} @Composable internal fun rememberLazyListItemProvider( @@ -37,12 +39,14 @@ internal fun rememberLazyListItemProvider( return remember(latestContent) { LazyListItemProviderImpl( latestContent = { latestContent.value }, + itemScope = LazyItemScopeImpl, ) } } private class LazyListItemProviderImpl( private val latestContent: () -> (LazyListScope.() -> Unit), + override val itemScope: LazyItemScopeImpl, ) : LazyListItemProvider { private val listContent by derivedStateOf(referentialEqualityPolicy()) { LazyListIntervalContent(latestContent()) @@ -53,7 +57,7 @@ private class LazyListItemProviderImpl( @Composable override fun Item(index: Int) { listContent.withInterval(index) { localIndex, content -> - content.item(localIndex) + content.item(itemScope, localIndex) } } } diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/modifier.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/modifier.kt new file mode 100644 index 0000000000..006b73f8a1 --- /dev/null +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/modifier.kt @@ -0,0 +1,19 @@ +package app.cash.redwood.lazylayout.compose + +import app.cash.redwood.layout.api.CrossAxisAlignment +import app.cash.redwood.layout.modifier.Alignment + +internal class AlignmentImpl( + override val alignment: CrossAxisAlignment, +) : Alignment { + override fun equals(other: Any?): Boolean = other is Alignment + && other.alignment == alignment + + override fun hashCode(): Int { + var hash = 17 + hash = 31 * hash + alignment.hashCode() + return hash + } + + override fun toString(): String = """Alignment(alignment=$alignment)""" +} diff --git a/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiLazyList.kt b/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiLazyList.kt index 82ce9733fe..3809f832e4 100644 --- a/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiLazyList.kt +++ b/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiLazyList.kt @@ -112,6 +112,7 @@ internal class ComposeUiLazyList : val content: LazyListScope.() -> Unit = { items(items.widgets) { item -> // TODO If CrossAxisAlignment is Stretch, pass Modifier.fillParentMaxWidth() to child widget. + // TODO Apply Modifier.alignment on child widgets. item.value.invoke() } } diff --git a/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt b/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt index 4249022e8b..d28cb55734 100644 --- a/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt +++ b/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt @@ -34,6 +34,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import app.cash.redwood.Modifier import app.cash.redwood.layout.api.Constraint import app.cash.redwood.layout.api.CrossAxisAlignment +import app.cash.redwood.layout.modifier.Alignment import app.cash.redwood.lazylayout.widget.LazyList import app.cash.redwood.lazylayout.widget.RefreshableLazyList import app.cash.redwood.ui.Density @@ -253,20 +254,7 @@ internal open class ViewLazyList(context: Context) : LazyList { override fun onBindViewHolder(holder: ViewHolder, position: Int) { lastItemHeight = holder.itemView.height - val layoutParams = if (crossAxisAlignment == CrossAxisAlignment.Stretch) { - FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - } else { - FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) - } - layoutParams.apply { - gravity = when (crossAxisAlignment) { - CrossAxisAlignment.Start -> Gravity.START - CrossAxisAlignment.Center -> Gravity.CENTER - CrossAxisAlignment.End -> Gravity.END - CrossAxisAlignment.Stretch -> Gravity.START - else -> throw AssertionError() - } - } + val layoutParams = layoutParams(crossAxisAlignment) when (holder) { is ViewHolder.Placeholder -> { if (holder.container.childCount == 0) { @@ -286,11 +274,12 @@ internal open class ViewLazyList(context: Context) : LazyList { } is ViewHolder.Item -> { val index = position - items.itemsBefore - val view = items.widgets[index].value + val widget = items.widgets[index] holder.container.removeAllViews() - (view.parent as? FrameLayout)?.removeAllViews() - view.layoutParams = layoutParams - holder.container.addView(view) + (widget.value.parent as? FrameLayout)?.removeAllViews() + widget.value.layoutParams = layoutParams + holder.container.addView(widget.value) + widget.value.applyModifier(widget.modifier) } } } @@ -327,3 +316,30 @@ internal class ViewRefreshableLazyList( swipeRefreshLayout.setOnRefreshListener(onRefresh) } } + +private fun layoutParams(crossAxisAlignment: CrossAxisAlignment): FrameLayout.LayoutParams { + val layoutParams = if (crossAxisAlignment == CrossAxisAlignment.Stretch) { + FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + } else { + FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) + } + return layoutParams.apply { + gravity = when (crossAxisAlignment) { + CrossAxisAlignment.Start -> Gravity.START + CrossAxisAlignment.Center -> Gravity.CENTER + CrossAxisAlignment.End -> Gravity.END + CrossAxisAlignment.Stretch -> Gravity.START + else -> throw AssertionError() + } + } +} + +private fun View.applyModifier(parentModifier: Modifier) { + parentModifier.forEach { childModifier -> + when (childModifier) { + is Alignment -> { + this.layoutParams = layoutParams(childModifier.alignment) + } + } + } +} diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[LTR,Column].png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[LTR,Column].png index fe75964509..d19c749c40 100644 --- a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[LTR,Column].png +++ b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[LTR,Column].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c41dbf63ff2d4293e6125da31b3f4a3841b1e8638ff71dbb5ca07db6bcaac45 -size 23871 +oid sha256:438ca15e5d810c4e71a363d21af43198885094074eb2f7a3fed0dfc95be0cde1 +size 24071 diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[RTL,Column].png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[RTL,Column].png index ce787e0d5f..cd8595316b 100644 --- a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[RTL,Column].png +++ b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListTest_layoutWithMarginAndDifferentAlignments[RTL,Column].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0db4e830531f410b50e86c669525e02531d1aa8b8f7dfed3f9b5710dccb9c130 -size 23882 +oid sha256:0d888523785caa07f76138461dd372235371fb65e6d7420b89890adece41138d +size 24065