From 2fe6d99d858724e8293474d6a4beabd5d1e4b7f5 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Wed, 23 Oct 2024 15:12:33 -0400 Subject: [PATCH] Migrate LazyList implementations away from subtypes When widgets switch to abstract classes, this will no longer be allowed. --- .../lazylayout/composeui/ComposeUiLazyList.kt | 33 +++++-- ...RedwoodTreehouseLazyLayoutWidgetFactory.kt | 6 +- .../redwood/lazylayout/dom/HTMLLazyList.kt | 25 ++++- .../lazylayout/uiview/UIViewLazyList.kt | 94 +++++++++++-------- .../redwood/lazylayout/view/ViewLazyList.kt | 26 ++++- 5 files changed, 129 insertions(+), 55 deletions(-) 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 c8d5b34bf8..4e7e05fcbc 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 @@ -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) @@ -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 } @@ -119,7 +117,7 @@ internal class ComposeUiLazyList : this.scrollItemIndex = scrollItemIndex } - override fun pullRefreshContentColor(pullRefreshContentColor: UInt) { + fun pullRefreshContentColor(pullRefreshContentColor: UInt) { this.pullRefreshContentColor = Color(pullRefreshContentColor.toLong()) } @@ -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) +} diff --git a/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt b/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt index ad3903aa0f..95c38846cc 100644 --- a/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt +++ b/redwood-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt @@ -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() } diff --git a/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt b/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt index b5e4aa1e3d..08376750a6 100644 --- a/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt +++ b/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt @@ -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 { +internal class HTMLLazyList(document: Document) : LazyList() { 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() { override fun insertRows(index: Int, count: Int) { @@ -162,8 +162,25 @@ internal open class HTMLLazyList(document: Document) : LazyList { internal class HTMLRefreshableLazyList( document: Document, -) : HTMLLazyList(document), - RefreshableLazyList { +) : RefreshableLazyList() { + 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) { } diff --git a/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt b/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt index b9988d1acc..87b8fb6b43 100644 --- a/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt +++ b/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt @@ -65,7 +65,7 @@ import platform.UIKit.item import platform.darwin.NSInteger import platform.darwin.NSObject -internal open class UIViewLazyList : +internal class UIViewLazyList : LazyList, ChangeListener { internal var tableView: UITableView? = object : UITableView( @@ -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? = object : LazyListUpdateProcessor() { override fun createPlaceholder(original: UIView) = SizeOnlyPlaceholder(original) @@ -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 } } @@ -381,28 +412,29 @@ internal class LazyListContainerCell( } internal class UIViewRefreshableLazyList : - UIViewLazyList(), - RefreshableLazyList { + RefreshableLazyList, + 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) { @@ -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) } } 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 7f0349e2a2..be3f3c167e 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 @@ -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, ChangeListener { private val adapter = LazyContentItemListAdapter() @@ -285,16 +285,21 @@ internal open class ViewLazyList private constructor( internal class ViewRefreshableLazyList( context: Context, -) : ViewLazyList(context), - RefreshableLazyList { +) : RefreshableLazyList, + 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) } @@ -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")