Skip to content

Commit

Permalink
Migrate LazyList implementations away from subtypes
Browse files Browse the repository at this point in the history
When widgets switch to abstract classes, this will no longer be allowed.
  • Loading branch information
JakeWharton committed Oct 23, 2024
1 parent 814a4db commit 2fe6d99
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 55 deletions.
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 @@ -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

0 comments on commit 2fe6d99

Please sign in to comment.