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

Migrate LazyList implementations away from subtypes #2404

Merged
merged 1 commit into from
Oct 23, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ final class app.cash.redwood.lazylayout.composeui/ComposeUiRedwoodLazyLayoutWidg

final val app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiLazyList$stableprop // app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiLazyList$stableprop|#static{}app_cash_redwood_lazylayout_composeui_ComposeUiLazyList$stableprop[0]
final val app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRedwoodLazyLayoutWidgetFactory$stableprop // app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRedwoodLazyLayoutWidgetFactory$stableprop|#static{}app_cash_redwood_lazylayout_composeui_ComposeUiRedwoodLazyLayoutWidgetFactory$stableprop[0]
final val app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRefreshableLazyList$stableprop // app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRefreshableLazyList$stableprop|#static{}app_cash_redwood_lazylayout_composeui_ComposeUiRefreshableLazyList$stableprop[0]

final fun app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiLazyList$stableprop_getter(): kotlin/Int // app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiLazyList$stableprop_getter|app_cash_redwood_lazylayout_composeui_ComposeUiLazyList$stableprop_getter(){}[0]
final fun app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRedwoodLazyLayoutWidgetFactory$stableprop_getter(): kotlin/Int // app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRedwoodLazyLayoutWidgetFactory$stableprop_getter|app_cash_redwood_lazylayout_composeui_ComposeUiRedwoodLazyLayoutWidgetFactory$stableprop_getter(){}[0]
final fun app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRefreshableLazyList$stableprop_getter(): kotlin/Int // app.cash.redwood.lazylayout.composeui/app_cash_redwood_lazylayout_composeui_ComposeUiRefreshableLazyList$stableprop_getter|app_cash_redwood_lazylayout_composeui_ComposeUiRefreshableLazyList$stableprop_getter(){}[0]
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ import app.cash.redwood.ui.toPlatformDp
import app.cash.redwood.widget.compose.ComposeWidgetChildren

@OptIn(ExperimentalMaterialApi::class)
internal class ComposeUiLazyList :
LazyList<@Composable () -> Unit>,
RefreshableLazyList<@Composable () -> Unit> {
internal class ComposeUiLazyList : LazyList<@Composable () -> Unit> {
private var isVertical by mutableStateOf(false)
private var onViewportChanged: ((firstVisibleItemIndex: Int, lastVisibleItemIndex: Int) -> Unit)? by mutableStateOf(null)
private var itemsBefore by mutableIntStateOf(0)
Expand Down Expand Up @@ -91,11 +89,11 @@ internal class ComposeUiLazyList :
this.itemsAfter = itemsAfter
}

override fun refreshing(refreshing: Boolean) {
fun refreshing(refreshing: Boolean) {
this.isRefreshing = refreshing
}

override fun onRefresh(onRefresh: (() -> Unit)?) {
fun onRefresh(onRefresh: (() -> Unit)?) {
this.onRefresh = onRefresh
}

Expand All @@ -119,7 +117,7 @@ internal class ComposeUiLazyList :
this.scrollItemIndex = scrollItemIndex
}

override fun pullRefreshContentColor(pullRefreshContentColor: UInt) {
fun pullRefreshContentColor(pullRefreshContentColor: UInt) {
this.pullRefreshContentColor = Color(pullRefreshContentColor.toLong())
}

Expand Down Expand Up @@ -207,3 +205,26 @@ internal class ComposeUiLazyList :
}
}
}

internal class ComposeUiRefreshableLazyList : RefreshableLazyList<@Composable () -> Unit> {
private val delegate = ComposeUiLazyList()

override val value get() = delegate.value
override var modifier by delegate::modifier

override val placeholder get() = delegate.placeholder
override val items get() = delegate.items

override fun isVertical(isVertical: Boolean) = delegate.isVertical(isVertical)
override fun onViewportChanged(onViewportChanged: (Int, Int) -> Unit) = delegate.onViewportChanged(onViewportChanged)
override fun itemsBefore(itemsBefore: Int) = delegate.itemsBefore(itemsBefore)
override fun itemsAfter(itemsAfter: Int) = delegate.itemsAfter(itemsAfter)
override fun refreshing(refreshing: Boolean) = delegate.refreshing(refreshing)
override fun onRefresh(onRefresh: (() -> Unit)?) = delegate.onRefresh(onRefresh)
override fun width(width: Constraint) = delegate.width(width)
override fun height(height: Constraint) = delegate.height(height)
override fun margin(margin: Margin) = delegate.margin(margin)
override fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) = delegate.crossAxisAlignment(crossAxisAlignment)
override fun scrollItemIndex(scrollItemIndex: ScrollItemIndex) = delegate.scrollItemIndex(scrollItemIndex)
override fun pullRefreshContentColor(pullRefreshContentColor: UInt) = delegate.pullRefreshContentColor(pullRefreshContentColor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import app.cash.redwood.lazylayout.widget.RedwoodLazyLayoutWidgetFactory
import app.cash.redwood.lazylayout.widget.RefreshableLazyList

public class ComposeUiRedwoodLazyLayoutWidgetFactory : RedwoodLazyLayoutWidgetFactory<@Composable () -> Unit> {
override fun LazyList(): LazyList<@Composable () -> Unit> = ComposeUiLazyList()
override fun LazyList(): LazyList<@Composable () -> Unit> =
ComposeUiLazyList()

override fun RefreshableLazyList(): RefreshableLazyList<@Composable () -> Unit> = ComposeUiLazyList()
override fun RefreshableLazyList(): RefreshableLazyList<@Composable () -> Unit> =
ComposeUiRefreshableLazyList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.get

internal open class HTMLLazyList(document: Document) : LazyList<HTMLElement> {
internal class HTMLLazyList(document: Document) : LazyList<HTMLElement> {
override var modifier: Modifier = Modifier

final override val value = document.createElement("div") as HTMLDivElement
override val value = document.createElement("div") as HTMLDivElement

private val processor = object : LazyListUpdateProcessor<HTMLElement, HTMLElement>() {
override fun insertRows(index: Int, count: Int) {
Expand Down Expand Up @@ -86,7 +86,7 @@ internal open class HTMLLazyList(document: Document) : LazyList<HTMLElement> {
value.style.display = "flex"
}

final override val items: Widget.Children<HTMLElement> = object : Widget.Children<HTMLElement> by processor.items {
override val items: Widget.Children<HTMLElement> = object : Widget.Children<HTMLElement> by processor.items {
override fun insert(index: Int, widget: Widget<HTMLElement>) {
processor.items.insert(index, widget)
intersectionObserver.observe(widget.value)
Expand All @@ -108,7 +108,7 @@ internal open class HTMLLazyList(document: Document) : LazyList<HTMLElement> {
}
}

final override val placeholder = processor.placeholder
override val placeholder = processor.placeholder

override fun width(width: Constraint) {
value.style.width = width.toCss()
Expand Down Expand Up @@ -162,8 +162,25 @@ internal open class HTMLLazyList(document: Document) : LazyList<HTMLElement> {

internal class HTMLRefreshableLazyList(
document: Document,
) : HTMLLazyList(document),
RefreshableLazyList<HTMLElement> {
) : RefreshableLazyList<HTMLElement> {
private val delegate = HTMLLazyList(document)

override val value get() = delegate.value
override var modifier by delegate::modifier

override val placeholder get() = delegate.placeholder
override val items get() = delegate.items

override fun isVertical(isVertical: Boolean) = delegate.isVertical(isVertical)
override fun onViewportChanged(onViewportChanged: (Int, Int) -> Unit) = delegate.onViewportChanged(onViewportChanged)
override fun itemsAfter(itemsAfter: Int) = delegate.itemsAfter(itemsAfter)
override fun itemsBefore(itemsBefore: Int) = delegate.itemsBefore(itemsBefore)
override fun width(width: Constraint) = delegate.width(width)
override fun height(height: Constraint) = delegate.height(height)
override fun margin(margin: Margin) = delegate.margin(margin)
override fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) = delegate.crossAxisAlignment(crossAxisAlignment)
override fun scrollItemIndex(scrollItemIndex: ScrollItemIndex) = delegate.scrollItemIndex(scrollItemIndex)

override fun refreshing(refreshing: Boolean) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import platform.UIKit.item
import platform.darwin.NSInteger
import platform.darwin.NSObject

internal open class UIViewLazyList :
internal class UIViewLazyList :
LazyList<UIView>,
ChangeListener {
internal var tableView: UITableView? = object : UITableView(
Expand All @@ -91,6 +91,36 @@ internal open class UIViewLazyList :
override val value: UIView
get() = tableView ?: error("detached")

private var onRefresh: (() -> Unit)? = null

private var refreshControl: UIRefreshControl? = null

fun requireRefreshControl(): UIRefreshControl {
val result = refreshControl
if (result != null) return result

return UIRefreshControl()
.apply {
setEventHandler(UIControlEventValueChanged) {
onRefresh?.invoke()
}
}
.also { this.refreshControl = it }
}

fun onRefresh(onRefresh: (() -> Unit)?) {
val tableView = this.tableView ?: error("detached")
this.onRefresh = onRefresh

if (onRefresh != null) {
if (tableView.refreshControl != refreshControl) {
tableView.refreshControl = refreshControl
}
} else {
refreshControl?.removeFromSuperview()
}
}

private var updateProcessor: LazyListUpdateProcessor<LazyListContainerCell, UIView>? = object : LazyListUpdateProcessor<LazyListContainerCell, UIView>() {
override fun createPlaceholder(original: UIView) = SizeOnlyPlaceholder(original)

Expand Down Expand Up @@ -292,11 +322,12 @@ internal open class UIViewLazyList :
scrollProcessor.onEndChanges()
}

protected open fun detach() {
fun detach() {
// Break reference cycles.
this.tableView = null
this.updateProcessor = null
this.scrollProcessor = null
tableView = null
updateProcessor = null
scrollProcessor = null
refreshControl = null
}
}

Expand Down Expand Up @@ -381,28 +412,29 @@ internal class LazyListContainerCell(
}

internal class UIViewRefreshableLazyList :
UIViewLazyList(),
RefreshableLazyList<UIView> {
RefreshableLazyList<UIView>,
ChangeListener {
private val delegate = UIViewLazyList()

private var onRefresh: (() -> Unit)? = null
override val value get() = delegate.value
override var modifier by delegate::modifier

private var refreshControl: UIRefreshControl? = null
override val placeholder get() = delegate.placeholder
override val items get() = delegate.items

private fun requireRefreshControl(): UIRefreshControl {
val result = refreshControl
if (result != null) return result

return UIRefreshControl()
.apply {
setEventHandler(UIControlEventValueChanged) {
onRefresh?.invoke()
}
}
.also { this.refreshControl = it }
}
override fun isVertical(isVertical: Boolean) = delegate.isVertical(isVertical)
override fun onViewportChanged(onViewportChanged: (Int, Int) -> Unit) = delegate.onViewportChanged(onViewportChanged)
override fun itemsBefore(itemsBefore: Int) = delegate.itemsBefore(itemsBefore)
override fun itemsAfter(itemsAfter: Int) = delegate.itemsAfter(itemsAfter)
override fun width(width: Constraint) = delegate.width(width)
override fun height(height: Constraint) = delegate.height(height)
override fun margin(margin: Margin) = delegate.margin(margin)
override fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) = delegate.crossAxisAlignment(crossAxisAlignment)
override fun scrollItemIndex(scrollItemIndex: ScrollItemIndex) = delegate.scrollItemIndex(scrollItemIndex)
override fun onEndChanges() = delegate.onEndChanges()

override fun refreshing(refreshing: Boolean) {
val refreshControl = requireRefreshControl()
val refreshControl = delegate.requireRefreshControl()

if (refreshing != refreshControl.refreshing) {
if (refreshing) {
Expand All @@ -414,25 +446,11 @@ internal class UIViewRefreshableLazyList :
}

override fun onRefresh(onRefresh: (() -> Unit)?) {
val tableView = this.tableView ?: error("detached")
this.onRefresh = onRefresh

if (onRefresh != null) {
if (tableView.refreshControl != refreshControl) {
tableView.refreshControl = refreshControl
}
} else {
refreshControl?.removeFromSuperview()
}
delegate.onRefresh(onRefresh)
}

override fun pullRefreshContentColor(pullRefreshContentColor: UInt) {
requireRefreshControl().tintColor = UIColor(pullRefreshContentColor)
}

override fun detach() {
super.detach()
refreshControl = null // Break a reference cycle.
delegate.requireRefreshControl().tintColor = UIColor(pullRefreshContentColor)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ import app.cash.redwood.widget.Widget

private const val VIEW_TYPE_ITEM = 1

internal open class ViewLazyList private constructor(
internal val recyclerView: RecyclerView,
internal class ViewLazyList private constructor(
val recyclerView: RecyclerView,
) : LazyList<View>,
ChangeListener {
private val adapter = LazyContentItemListAdapter()
Expand Down Expand Up @@ -285,16 +285,21 @@ internal open class ViewLazyList private constructor(

internal class ViewRefreshableLazyList(
context: Context,
) : ViewLazyList(context),
RefreshableLazyList<View> {
) : RefreshableLazyList<View>,
ChangeListener {
private val delegate = ViewLazyList(context)

private val swipeRefreshLayout = SwipeRefreshLayout(context)

override val value: View get() = swipeRefreshLayout
override var modifier by delegate::modifier

override val placeholder get() = delegate.placeholder
override val items get() = delegate.items

init {
swipeRefreshLayout.apply {
addView(recyclerView)
addView(delegate.recyclerView)
// TODO Dynamically update width and height of RefreshableViewLazyList when set
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
Expand All @@ -312,6 +317,17 @@ internal class ViewRefreshableLazyList(
override fun pullRefreshContentColor(@ColorInt pullRefreshContentColor: UInt) {
swipeRefreshLayout.setColorSchemeColors(pullRefreshContentColor.toInt())
}

override fun isVertical(isVertical: Boolean) = delegate.isVertical(isVertical)
override fun onViewportChanged(onViewportChanged: (Int, Int) -> Unit) = delegate.onViewportChanged(onViewportChanged)
override fun itemsBefore(itemsBefore: Int) = delegate.itemsBefore(itemsBefore)
override fun itemsAfter(itemsAfter: Int) = delegate.itemsAfter(itemsAfter)
override fun width(width: Constraint) = delegate.width(width)
override fun height(height: Constraint) = delegate.height(height)
override fun margin(margin: Margin) = delegate.margin(margin)
override fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) = delegate.crossAxisAlignment(crossAxisAlignment)
override fun scrollItemIndex(scrollItemIndex: ScrollItemIndex) = delegate.scrollItemIndex(scrollItemIndex)
override fun onEndChanges() = delegate.onEndChanges()
}

@SuppressLint("ViewConstructor")
Expand Down