Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make CrossAxisAlignment modifier available for items within LazyList #1288

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions redwood-lazylayout-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api projects.redwoodLayoutModifiers
api projects.redwoodLazylayoutWidget
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ import app.cash.redwood.ui.Margin
@LayoutScopeMarker
public interface LazyListScope {
public fun item(
content: @Composable () -> Unit,
content: @Composable LazyItemScope.() -> Unit,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

)

public fun items(
count: Int,
itemContent: @Composable (index: Int) -> Unit,
itemContent: @Composable LazyItemScope.(index: Int) -> Unit,
)
}

public inline fun <T> LazyListScope.items(
items: List<T>,
crossinline itemContent: @Composable (item: T) -> Unit,
crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit,
): Unit = items(items.size) {
itemContent(items[it])
}

public inline fun <T> LazyListScope.itemsIndexed(
items: List<T>,
crossinline itemContent: @Composable (index: Int, item: T) -> Unit,
crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit,
): Unit = items(
items.size,
) {
Expand All @@ -52,7 +52,7 @@ public inline fun <T> LazyListScope.itemsIndexed(

public inline fun <T> LazyListScope.items(
items: Array<T>,
crossinline itemContent: @Composable (item: T) -> Unit,
crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit,
): Unit = items(
items.size,
) {
Expand All @@ -61,7 +61,7 @@ public inline fun <T> LazyListScope.items(

public inline fun <T> LazyListScope.itemsIndexed(
items: Array<T>,
crossinline itemContent: @Composable (index: Int, item: T) -> Unit,
crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit,
): Unit = items(
items.size,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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))
Comment on lines +26 to +27
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@colinrtwhite Do you have any ideas on how to convert this to horizontalAlignment/verticalAlignment without introducing a new scope? LazyItemScope is used for more than just scoping modifiers (see LazyListIntervalContent), so I'm a bit hesitant to just have two separate scopes for LazyRow and LazyColumn.

}

internal object LazyItemScopeImpl : LazyItemScope
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -43,7 +43,7 @@ internal class LazyListIntervalContent(
)
}

override fun item(content: @Composable () -> Unit) {
override fun item(content: @Composable LazyItemScope.() -> Unit) {
intervals.addInterval(
1,
LazyListInterval(
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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())
Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 app.cash.redwood.layout.api.CrossAxisAlignment
import app.cash.redwood.layout.modifier.Alignment

internal class AlignmentImpl(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a data class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's copied generated code from redwood-layout-compose. Ultimately I want to share modifier definitions with redwood-layout, so this is somewhat of a stop gap in the meantime.

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)"""
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -253,20 +254,7 @@ internal open class ViewLazyList(context: Context) : LazyList<View> {

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) {
Expand All @@ -286,11 +274,12 @@ internal open class ViewLazyList(context: Context) : LazyList<View> {
}
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be re-applied if the modifier changes. I tried listening to changes in Items#onModifierUpdated, but it was never called on change.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a bug! FYI @JakeWharton

}
}
}
Expand Down Expand Up @@ -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)
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private class LazyColumnProvider : ColumnProvider {
modifier = modifier,
placeholder = placeholder,
) {
items(items, itemContent)
items(items) { itemContent(it) }
}
}
}