Skip to content

Commit

Permalink
Give TreehouseView a generic for its Widget type (#1468)
Browse files Browse the repository at this point in the history
  • Loading branch information
veyndan authored Sep 8, 2023
1 parent 3880d86 commit 7dcf7c1
Show file tree
Hide file tree
Showing 14 changed files with 41 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
@Composable
public fun <A : AppService> TreehouseContent(
treehouseApp: TreehouseApp<A>,
widgetSystem: WidgetSystem,
widgetSystem: WidgetSystem<@Composable () -> Unit>,
codeListener: CodeListener = CodeListener(),
contentSource: TreehouseContentSource<A>,
) {
Expand All @@ -61,12 +61,12 @@ public fun <A : AppService> TreehouseContent(
)

val treehouseView = remember(widgetSystem) {
object : TreehouseView {
object : TreehouseView<@Composable () -> Unit> {
override val children = ComposeWidgetChildren()
override val uiConfiguration = MutableStateFlow(uiConfiguration)
override val widgetSystem = widgetSystem
override val readyForContent = true
override var readyForContentChangeListener: ReadyForContentChangeListener? = null
override var readyForContentChangeListener: ReadyForContentChangeListener<@Composable () -> Unit>? = null
override var saveCallback: TreehouseView.SaveCallback? = null
override val stateSnapshotId = StateSnapshot.Id(null)
override fun reset() = children.remove(0, children.widgets.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public typealias TreehouseWidgetView = TreehouseLayout
@SuppressLint("ViewConstructor")
public class TreehouseLayout(
context: Context,
override val widgetSystem: WidgetSystem,
) : RedwoodLayout(context), TreehouseView {
override var readyForContentChangeListener: ReadyForContentChangeListener? = null
override val widgetSystem: WidgetSystem<View>,
) : RedwoodLayout(context), TreehouseView<View> {
override var readyForContentChangeListener: ReadyForContentChangeListener<View>? = null
set(value) {
check(value == null || field == null) { "View already bound to a listener" }
field = value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class TreehouseLayoutTest {
val activity = Robolectric.buildActivity(Activity::class.java).resume().visible().get()
val parent = activity.findViewById<ViewGroup>(android.R.id.content)
val layout = TreehouseLayout(context, throwingWidgetSystem)
val listener = CountingReadyForContentChangeListener()
val listener = CountingReadyForContentChangeListener<View>()

layout.readyForContentChangeListener = listener
assertThat(listener.count).isEqualTo(0)
Expand Down Expand Up @@ -145,5 +145,5 @@ class TreehouseLayoutTest {
}

private val throwingWidgetSystem =
WidgetSystem { _, _ -> throw UnsupportedOperationException() }
WidgetSystem<View> { _, _ -> throw UnsupportedOperationException() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import app.cash.redwood.protocol.SnapshotChangeList
import app.cash.redwood.protocol.widget.ProtocolBridge
import app.cash.redwood.protocol.widget.ProtocolMismatchHandler
import app.cash.redwood.protocol.widget.ProtocolNode
import app.cash.redwood.widget.Widget
import kotlinx.serialization.json.Json

/**
Expand All @@ -38,12 +37,12 @@ public class ChangeListRenderer<W : Any>(

@Suppress("UNCHECKED_CAST")
public fun render(
view: TreehouseView,
view: TreehouseView<W>,
changeList: SnapshotChangeList,
) {
view.reset()
val bridge = ProtocolBridge(
container = view.children as Widget.Children<W>,
container = view.children,
factory = view.widgetSystem.widgetFactory(
json,
ProtocolMismatchHandler.Throwing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ public open class CodeListener {
* Invoked when the initial code is still loading. This can be used to signal a loading state
* in the UI before there is anything to display.
*/
public open fun onInitialCodeLoading(view: TreehouseView) {}
public open fun onInitialCodeLoading(view: TreehouseView<*>) {}

/**
* Invoked each time new code is loaded. This is called after the view's old children have
* been cleared but before the children of the new code have been added.
*
* @param initial true if this is the first code loaded for this view's current content.
*/
public open fun onCodeLoaded(view: TreehouseView, initial: Boolean) {}
public open fun onCodeLoaded(view: TreehouseView<*>, initial: Boolean) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public interface Content {
*
* This function may only be invoked on [TreehouseDispatchers.ui].
*/
public fun bind(view: TreehouseView)
public fun bind(view: TreehouseView<*>)

/**
* Suspends until content is available; either it is already in the view or it is preloaded and a call to [bind]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import okio.Closeable
*
* Returns a closeable that unbinds from the content and stops tracking the ready state.
*/
public fun Content.bindWhenReady(view: TreehouseView): Closeable {
val listener = ReadyForContentChangeListener {
public fun <W : Any> Content.bindWhenReady(view: TreehouseView<W>): Closeable {
val listener = ReadyForContentChangeListener<W> {
if (view.readyForContent) {
bind(view)
} else {
Expand All @@ -45,8 +45,8 @@ public fun Content.bindWhenReady(view: TreehouseView): Closeable {
}
}

public fun <A : AppService> TreehouseContentSource<A>.bindWhenReady(
view: TreehouseView,
public fun <A : AppService, W : Any> TreehouseContentSource<A>.bindWhenReady(
view: TreehouseView<W>,
app: TreehouseApp<A>,
codeListener: CodeListener = CodeListener(),
): Closeable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private sealed interface ViewState {
) : ViewState

class Bound(
val view: TreehouseView,
val view: TreehouseView<*>,
) : ViewState
}

Expand Down Expand Up @@ -102,7 +102,7 @@ internal class TreehouseAppContent<A : AppService>(
stateFlow.value = State(nextViewState, nextCodeState)
}

override fun bind(view: TreehouseView) {
override fun bind(view: TreehouseView<*>) {
treehouseApp.dispatchers.checkUi()
val previousState = stateFlow.value
val previousViewState = previousState.viewState
Expand Down Expand Up @@ -260,7 +260,7 @@ private class ViewContentCodeBinding<A : AppService>(
private val bindingScope = CoroutineScope(SupervisorJob(appScope.coroutineContext.job))

/** Only accessed on [TreehouseDispatchers.ui]. Null before [initView] and after [cancel]. */
private var viewOrNull: TreehouseView? = null
private var viewOrNull: TreehouseView<*>? = null

/** Only accessed on [TreehouseDispatchers.ui]. Null before [initView] and after [cancel]. */
private var bridgeOrNull: ProtocolBridge<*>? = null
Expand All @@ -282,7 +282,7 @@ private class ViewContentCodeBinding<A : AppService>(

private var initViewCalled: Boolean = false

fun initView(view: TreehouseView) {
fun initView(view: TreehouseView<*>) {
app.dispatchers.checkUi()

require(!initViewCalled)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.serialization.json.Json

@ObjCName("TreehouseView", exact = true)
public interface TreehouseView {
public val children: Widget.Children<*>
public interface TreehouseView<W : Any> {
public val children: Widget.Children<W>
public val uiConfiguration: StateFlow<UiConfiguration>
public val widgetSystem: WidgetSystem
public val widgetSystem: WidgetSystem<W>
public val readyForContent: Boolean
public var readyForContentChangeListener: ReadyForContentChangeListener?
public var readyForContentChangeListener: ReadyForContentChangeListener<W>?
public var saveCallback: SaveCallback?
public val stateSnapshotId: StateSnapshot.Id

/** Invoked when new code is loaded. This should at minimum clear all [children]. */
public fun reset()

@ObjCName("TreehouseViewReadyForContentChangeListener", exact = true)
public fun interface ReadyForContentChangeListener {
public fun interface ReadyForContentChangeListener<W : Any> {
/** Called when [TreehouseView.readyForContent] has changed. */
public fun onReadyForContentChanged(view: TreehouseView)
public fun onReadyForContentChanged(view: TreehouseView<W>)
}

@ObjCName("TreehouseViewSaveCallback", exact = true)
Expand All @@ -49,11 +49,11 @@ public interface TreehouseView {
}

@ObjCName("TreehouseViewWidgetSystem", exact = true)
public fun interface WidgetSystem {
public fun interface WidgetSystem<W : Any> {
/** Returns a widget factory for encoding and decoding changes to the contents of [view]. */
public fun widgetFactory(
json: Json,
protocolMismatchHandler: ProtocolMismatchHandler,
): ProtocolNode.Factory<*>
): ProtocolNode.Factory<W>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ package app.cash.redwood.treehouse

import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener

class CountingReadyForContentChangeListener : ReadyForContentChangeListener {
class CountingReadyForContentChangeListener<W : Any> : ReadyForContentChangeListener<W> {
var count = 0

override fun onReadyForContentChanged(view: TreehouseView) {
override fun onReadyForContentChanged(view: TreehouseView<W>) {
count++
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public typealias TreehouseUIKitView = TreehouseUIView

@ObjCName("TreehouseUIView", exact = true)
public class TreehouseUIView private constructor(
override val widgetSystem: WidgetSystem,
override val widgetSystem: WidgetSystem<UIView>,
view: UIView,
) : TreehouseView, RedwoodUIView(view) {
) : TreehouseView<UIView>, RedwoodUIView(view) {
override var saveCallback: TreehouseView.SaveCallback? = null
override var stateSnapshotId: StateSnapshot.Id = StateSnapshot.Id(null)

override var readyForContentChangeListener: ReadyForContentChangeListener? = null
override var readyForContentChangeListener: ReadyForContentChangeListener<UIView>? = null
set(value) {
check(value == null || field == null) { "View already bound to a listener" }
field = value
Expand All @@ -48,7 +48,7 @@ public class TreehouseUIView private constructor(
override val readyForContent: Boolean
get() = view.superview != null

public constructor(widgetSystem: WidgetSystem) : this(widgetSystem, RootUiView())
public constructor(widgetSystem: WidgetSystem<UIView>) : this(widgetSystem, RootUiView())

init {
(view as RootUiView).treehouseView = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class TreehouseUIViewTest {
@Test fun attachAndDetachSendsStateChange() {
val parent = UIView()
val layout = TreehouseUIView(throwingWidgetSystem)
val listener = CountingReadyForContentChangeListener()
val listener = CountingReadyForContentChangeListener<UIView>()

layout.readyForContentChangeListener = listener
assertThat(listener.count).isEqualTo(0)
Expand Down Expand Up @@ -138,5 +138,5 @@ class TreehouseUIViewTest {
}

private val throwingWidgetSystem =
WidgetSystem { _, _ -> throw UnsupportedOperationException() }
WidgetSystem<UIView> { _, _ -> throw UnsupportedOperationException() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fun exposedTypes(
treehouseUIView: TreehouseUIView,
uiViewRedwoodLayoutWidgetFactory: UIViewRedwoodLayoutWidgetFactory,
uiViewRedwoodLazyLayoutWidgetFactory: UIViewRedwoodLazyLayoutWidgetFactory,
widgetSystem: WidgetSystem,
widgetSystem: WidgetSystem<*>,
widgetFactories: EmojiSearchWidgetFactories<*>,
) {
throw AssertionError()
Expand All @@ -54,5 +54,5 @@ fun modifier(): Modifier = Modifier

fun <A : AppService> bindWhenReady(
content: Content,
view: TreehouseView,
view: TreehouseView<*>,
): Closeable = content.bindWhenReady(view)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.example.redwood.testing.android.views

import android.os.Bundle
import android.view.View
import androidx.activity.ComponentActivity
import app.cash.redwood.compose.AndroidUiDispatcher.Companion.Main
import app.cash.redwood.layout.view.ViewRedwoodLayoutWidgetFactory
Expand Down Expand Up @@ -50,7 +51,7 @@ class TestAppActivity : ComponentActivity() {
val treehouseApp = createTreehouseApp()
val treehouseContentSource = TreehouseContentSource(TestAppPresenter::launch)

val widgetSystem = object : TreehouseView.WidgetSystem {
val widgetSystem = object : TreehouseView.WidgetSystem<View> {
override fun widgetFactory(
json: Json,
protocolMismatchHandler: ProtocolMismatchHandler,
Expand Down

0 comments on commit 7dcf7c1

Please sign in to comment.