From 7dcf7c13580e6c10f8948fcb2d40647960ca8024 Mon Sep 17 00:00:00 2001 From: Veyndan Stuart Date: Fri, 8 Sep 2023 19:34:28 +0200 Subject: [PATCH] Give `TreehouseView` a generic for its Widget type (#1468) --- .../treehouse/composeui/TreehouseContent.kt | 6 +++--- .../cash/redwood/treehouse/TreehouseLayout.kt | 6 +++--- .../redwood/treehouse/TreehouseLayoutTest.kt | 4 ++-- .../cash/redwood/treehouse/ChangeListRenderer.kt | 5 ++--- .../app/cash/redwood/treehouse/CodeListener.kt | 4 ++-- .../kotlin/app/cash/redwood/treehouse/Content.kt | 2 +- .../app/cash/redwood/treehouse/ContentBinding.kt | 8 ++++---- .../redwood/treehouse/TreehouseAppContent.kt | 8 ++++---- .../app/cash/redwood/treehouse/TreehouseView.kt | 16 ++++++++-------- .../CountingReadyForContentChangeListener.kt | 4 ++-- .../cash/redwood/treehouse/TreehouseUIView.kt | 8 ++++---- .../redwood/treehouse/TreehouseUIViewTest.kt | 4 ++-- .../example/redwood/emojisearch/ios/exposed.kt | 4 ++-- .../testing/android/views/TestAppActivity.kt | 3 ++- 14 files changed, 41 insertions(+), 41 deletions(-) diff --git a/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt b/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt index 11768019a8..91fc39ad1d 100644 --- a/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt +++ b/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt @@ -47,7 +47,7 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable public fun TreehouseContent( treehouseApp: TreehouseApp, - widgetSystem: WidgetSystem, + widgetSystem: WidgetSystem<@Composable () -> Unit>, codeListener: CodeListener = CodeListener(), contentSource: TreehouseContentSource, ) { @@ -61,12 +61,12 @@ public fun 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) diff --git a/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseLayout.kt b/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseLayout.kt index b94160ff68..dba7bb4e3d 100644 --- a/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseLayout.kt +++ b/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseLayout.kt @@ -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, +) : RedwoodLayout(context), TreehouseView { + override var readyForContentChangeListener: ReadyForContentChangeListener? = null set(value) { check(value == null || field == null) { "View already bound to a listener" } field = value diff --git a/redwood-treehouse-host/src/androidUnitTest/kotlin/app/cash/redwood/treehouse/TreehouseLayoutTest.kt b/redwood-treehouse-host/src/androidUnitTest/kotlin/app/cash/redwood/treehouse/TreehouseLayoutTest.kt index a4789c444d..45d99956b6 100644 --- a/redwood-treehouse-host/src/androidUnitTest/kotlin/app/cash/redwood/treehouse/TreehouseLayoutTest.kt +++ b/redwood-treehouse-host/src/androidUnitTest/kotlin/app/cash/redwood/treehouse/TreehouseLayoutTest.kt @@ -62,7 +62,7 @@ class TreehouseLayoutTest { val activity = Robolectric.buildActivity(Activity::class.java).resume().visible().get() val parent = activity.findViewById(android.R.id.content) val layout = TreehouseLayout(context, throwingWidgetSystem) - val listener = CountingReadyForContentChangeListener() + val listener = CountingReadyForContentChangeListener() layout.readyForContentChangeListener = listener assertThat(listener.count).isEqualTo(0) @@ -145,5 +145,5 @@ class TreehouseLayoutTest { } private val throwingWidgetSystem = - WidgetSystem { _, _ -> throw UnsupportedOperationException() } + WidgetSystem { _, _ -> throw UnsupportedOperationException() } } diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ChangeListRenderer.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ChangeListRenderer.kt index 936eb5abf9..1d95ca8f81 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ChangeListRenderer.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ChangeListRenderer.kt @@ -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 /** @@ -38,12 +37,12 @@ public class ChangeListRenderer( @Suppress("UNCHECKED_CAST") public fun render( - view: TreehouseView, + view: TreehouseView, changeList: SnapshotChangeList, ) { view.reset() val bridge = ProtocolBridge( - container = view.children as Widget.Children, + container = view.children, factory = view.widgetSystem.widgetFactory( json, ProtocolMismatchHandler.Throwing, diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/CodeListener.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/CodeListener.kt index e36eb85573..426d78a04a 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/CodeListener.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/CodeListener.kt @@ -23,7 +23,7 @@ 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 @@ -31,5 +31,5 @@ public open class CodeListener { * * @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) {} } diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt index 21a514d328..70cc6197bb 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt @@ -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] diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt index ea23ae1dec..dc58761173 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt @@ -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 Content.bindWhenReady(view: TreehouseView): Closeable { + val listener = ReadyForContentChangeListener { if (view.readyForContent) { bind(view) } else { @@ -45,8 +45,8 @@ public fun Content.bindWhenReady(view: TreehouseView): Closeable { } } -public fun TreehouseContentSource.bindWhenReady( - view: TreehouseView, +public fun TreehouseContentSource.bindWhenReady( + view: TreehouseView, app: TreehouseApp, codeListener: CodeListener = CodeListener(), ): Closeable { diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt index 0e4a9587a4..ca90462ace 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt @@ -51,7 +51,7 @@ private sealed interface ViewState { ) : ViewState class Bound( - val view: TreehouseView, + val view: TreehouseView<*>, ) : ViewState } @@ -102,7 +102,7 @@ internal class TreehouseAppContent( 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 @@ -260,7 +260,7 @@ private class ViewContentCodeBinding( 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 @@ -282,7 +282,7 @@ private class ViewContentCodeBinding( private var initViewCalled: Boolean = false - fun initView(view: TreehouseView) { + fun initView(view: TreehouseView<*>) { app.dispatchers.checkUi() require(!initViewCalled) diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt index c7196795c4..453bf3f45a 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt @@ -24,12 +24,12 @@ 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 { + public val children: Widget.Children public val uiConfiguration: StateFlow - public val widgetSystem: WidgetSystem + public val widgetSystem: WidgetSystem public val readyForContent: Boolean - public var readyForContentChangeListener: ReadyForContentChangeListener? + public var readyForContentChangeListener: ReadyForContentChangeListener? public var saveCallback: SaveCallback? public val stateSnapshotId: StateSnapshot.Id @@ -37,9 +37,9 @@ public interface TreehouseView { public fun reset() @ObjCName("TreehouseViewReadyForContentChangeListener", exact = true) - public fun interface ReadyForContentChangeListener { + public fun interface ReadyForContentChangeListener { /** Called when [TreehouseView.readyForContent] has changed. */ - public fun onReadyForContentChanged(view: TreehouseView) + public fun onReadyForContentChanged(view: TreehouseView) } @ObjCName("TreehouseViewSaveCallback", exact = true) @@ -49,11 +49,11 @@ public interface TreehouseView { } @ObjCName("TreehouseViewWidgetSystem", exact = true) - public fun interface WidgetSystem { + public fun interface WidgetSystem { /** 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 } } diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt index 4d87a62dde..4e63681a44 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt @@ -17,10 +17,10 @@ package app.cash.redwood.treehouse import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener -class CountingReadyForContentChangeListener : ReadyForContentChangeListener { +class CountingReadyForContentChangeListener : ReadyForContentChangeListener { var count = 0 - override fun onReadyForContentChanged(view: TreehouseView) { + override fun onReadyForContentChanged(view: TreehouseView) { count++ } } diff --git a/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIView.kt b/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIView.kt index 4f31f57209..c7dbcb31a2 100644 --- a/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIView.kt +++ b/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIView.kt @@ -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, view: UIView, -) : TreehouseView, RedwoodUIView(view) { +) : TreehouseView, 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? = null set(value) { check(value == null || field == null) { "View already bound to a listener" } field = value @@ -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) : this(widgetSystem, RootUiView()) init { (view as RootUiView).treehouseView = this diff --git a/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIViewTest.kt b/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIViewTest.kt index e8cb8b8da5..9cec78f092 100644 --- a/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIViewTest.kt +++ b/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIViewTest.kt @@ -52,7 +52,7 @@ class TreehouseUIViewTest { @Test fun attachAndDetachSendsStateChange() { val parent = UIView() val layout = TreehouseUIView(throwingWidgetSystem) - val listener = CountingReadyForContentChangeListener() + val listener = CountingReadyForContentChangeListener() layout.readyForContentChangeListener = listener assertThat(listener.count).isEqualTo(0) @@ -138,5 +138,5 @@ class TreehouseUIViewTest { } private val throwingWidgetSystem = - WidgetSystem { _, _ -> throw UnsupportedOperationException() } + WidgetSystem { _, _ -> throw UnsupportedOperationException() } } diff --git a/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt b/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt index de2599dbf9..fb8a09115d 100644 --- a/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt +++ b/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt @@ -42,7 +42,7 @@ fun exposedTypes( treehouseUIView: TreehouseUIView, uiViewRedwoodLayoutWidgetFactory: UIViewRedwoodLayoutWidgetFactory, uiViewRedwoodLazyLayoutWidgetFactory: UIViewRedwoodLazyLayoutWidgetFactory, - widgetSystem: WidgetSystem, + widgetSystem: WidgetSystem<*>, widgetFactories: EmojiSearchWidgetFactories<*>, ) { throw AssertionError() @@ -54,5 +54,5 @@ fun modifier(): Modifier = Modifier fun bindWhenReady( content: Content, - view: TreehouseView, + view: TreehouseView<*>, ): Closeable = content.bindWhenReady(view) diff --git a/test-app/android-views/src/main/kotlin/com/example/redwood/testing/android/views/TestAppActivity.kt b/test-app/android-views/src/main/kotlin/com/example/redwood/testing/android/views/TestAppActivity.kt index 435091986a..145125ae28 100644 --- a/test-app/android-views/src/main/kotlin/com/example/redwood/testing/android/views/TestAppActivity.kt +++ b/test-app/android-views/src/main/kotlin/com/example/redwood/testing/android/views/TestAppActivity.kt @@ -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 @@ -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 { override fun widgetFactory( json: Json, protocolMismatchHandler: ProtocolMismatchHandler,