From c6bedfdbcde67fd9fdeb57ad60a1467a71b85c21 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Mon, 21 Oct 2024 11:06:39 -0400 Subject: [PATCH 1/5] Flatten RedwoodView and RedwoodView.Root This is a step in simplifying the complex interaction between these two classes. It also removes an unnecessary layer from the view tree. We also had a potential bug on hot reloads, where the previous content was detached but not necessarily removed. With this change we clear the child views by default on a hot reload. --- CHANGELOG.md | 2 +- .../redwood/compose/RedwoodComposition.kt | 4 +- .../{ComposeUiRoot.kt => DynamicContent.kt} | 41 +++++---- .../treehouse/composeui/TreehouseContent.kt | 22 ++++- .../cash/redwood/treehouse/TreehouseLayout.kt | 11 +-- .../app/cash/redwood/treehouse/LeaksTest.kt | 2 +- .../redwood/treehouse/ChangeListRenderer.kt | 4 +- .../redwood/treehouse/TreehouseAppContent.kt | 14 +-- .../app/cash/redwood/treehouse/FakeRoot.kt | 60 ------------ .../redwood/treehouse/FakeTreehouseView.kt | 48 ++++++++-- .../treehouse/TreehouseAppContentTest.kt | 6 +- .../cash/redwood/treehouse/TreehouseUIView.kt | 8 +- .../cash/redwood/treehouse/LayoutTester.kt | 4 +- .../redwood/treehouse/TreehouseUIViewTest.kt | 18 ++-- .../redwood/widget/AbstractRedwoodViewTest.kt | 2 +- .../app/cash/redwood/widget/RedwoodLayout.kt | 39 ++++++-- .../app/cash/redwood/widget/ViewRoot.kt | 65 ------------- .../app/cash/redwood/widget/RedwoodView.kt | 84 +++++++++-------- .../app/cash/redwood/widget/RedwoodUIView.kt | 83 ++++++++++++++--- .../app/cash/redwood/widget/UIViewRoot.kt | 91 ------------------- .../app/cash/redwood/widget/HTMLRoot.kt | 32 ------- .../redwood/widget/RedwoodHTMLElementView.kt | 22 ++++- .../ios/CounterViewControllerDelegate.kt | 1 - .../android/composeui/EmojiSearchActivity.kt | 2 +- .../composeui/EmojiSearchComposeUiRoot.kt | 29 ++---- .../android/views/EmojiSearchActivity.kt | 32 +++---- .../EmojiSearchViewController.swift | 6 +- 27 files changed, 298 insertions(+), 434 deletions(-) rename redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/{ComposeUiRoot.kt => DynamicContent.kt} (51%) delete mode 100644 redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeRoot.kt delete mode 100644 redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewRoot.kt delete mode 100644 redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewRoot.kt delete mode 100644 redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/HTMLRoot.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d8c205ebe..2a31f6699e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ Fixed: - Fix inconsistencies between iOS and Android for `Column` and `Row` layouts. Breaking: -- Replace `CodeListener` with `RedwoodView.Root`. This puts the loading/error/ready state with the UI that displays that state. +- Replace `CodeListener` with new functions in `RedwoodView`. This puts the loading/error/ready state with the UI that displays that state. ## [0.15.0] - 2024-09-30 diff --git a/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt b/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt index 256a1e5eb4..89f0855f6f 100644 --- a/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt +++ b/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt @@ -65,7 +65,7 @@ public fun RedwoodComposition( widgetSystem: WidgetSystem, onEndChanges: () -> Unit = {}, ): RedwoodComposition { - view.root.children.remove(0, view.root.children.widgets.size) + view.children.remove(0, view.children.widgets.size) val saveableStateRegistry = view.savedStateRegistry?.let { viewRegistry -> val state = viewRegistry.consumeRestoredState() @@ -95,7 +95,7 @@ public fun RedwoodComposition( return RedwoodComposition( scope, - view.root.children, + view.children, view.onBackPressedDispatcher, saveableStateRegistry, view.uiConfiguration, diff --git a/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/ComposeUiRoot.kt b/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/DynamicContent.kt similarity index 51% rename from redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/ComposeUiRoot.kt rename to redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/DynamicContent.kt index d20aa1f2bd..0baa408fac 100644 --- a/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/ComposeUiRoot.kt +++ b/redwood-treehouse-host-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/DynamicContent.kt @@ -16,37 +16,40 @@ package app.cash.redwood.treehouse.composeui import androidx.compose.runtime.Composable -import app.cash.redwood.Modifier -import app.cash.redwood.widget.RedwoodView +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import app.cash.redwood.widget.compose.ComposeWidgetChildren +import kotlinx.coroutines.CoroutineScope -/** - * A default base implementation of [RedwoodView.Root]. - * - * This composition contributes nothing to the view hierarchy. It delegates directly to its child - * views. - */ -public open class ComposeUiRoot : RedwoodView.Root<@Composable () -> Unit> { - override val children: ComposeWidgetChildren = ComposeWidgetChildren() - - override var modifier: Modifier = Modifier +public open class DynamicContent { + public var loadCount: Int by mutableIntStateOf(0) + private set + public var attached: Boolean by mutableStateOf(false) + private set + public var uncaughtException: Throwable? by mutableStateOf(null) + private set + public var restart: (() -> Unit)? by mutableStateOf(null) + private set - override fun contentState( + public open fun contentState( + scope: CoroutineScope, loadCount: Int, attached: Boolean, uncaughtException: Throwable?, ) { + this.loadCount = loadCount + this.attached = attached + this.uncaughtException = uncaughtException } - override fun restart(restart: (() -> Unit)?) { - } - - override val value: @Composable () -> Unit = { - Render() + public fun restart(restart: (() -> Unit)?) { + this.restart = restart } @Composable - public open fun Render() { + public open fun Render(children: ComposeWidgetChildren) { children.Render() } } 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 cff17f4909..7ae24f03d7 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 @@ -45,9 +45,8 @@ import app.cash.redwood.ui.OnBackPressedDispatcher import app.cash.redwood.ui.Size import app.cash.redwood.ui.UiConfiguration import app.cash.redwood.ui.dp as redwoodDp -import app.cash.redwood.widget.RedwoodView import app.cash.redwood.widget.SavedStateRegistry -import kotlinx.coroutines.CoroutineScope +import app.cash.redwood.widget.compose.ComposeWidgetChildren import kotlinx.coroutines.flow.MutableStateFlow @Composable @@ -56,7 +55,7 @@ public fun TreehouseContent( widgetSystem: WidgetSystem<@Composable () -> Unit>, contentSource: TreehouseContentSource, modifier: Modifier = Modifier, - root: ((CoroutineScope) -> RedwoodView.Root<@Composable () -> Unit>) = { _ -> ComposeUiRoot() }, + dynamicContent: DynamicContent = DynamicContent(), ) { val onBackPressedDispatcher = platformOnBackPressedDispatcher() val scope = rememberCoroutineScope() @@ -75,7 +74,20 @@ public fun TreehouseContent( ) val treehouseView = remember(widgetSystem) { object : TreehouseView<@Composable () -> Unit> { - override val root: RedwoodView.Root<@Composable () -> Unit> = root(scope) + override val children: ComposeWidgetChildren = ComposeWidgetChildren() + + override val value: @Composable () -> Unit = { + dynamicContent.Render(children) + } + + override fun contentState(loadCount: Int, attached: Boolean, uncaughtException: Throwable?) { + dynamicContent.contentState(scope, loadCount, attached, uncaughtException) + } + + override fun restart(restart: (() -> Unit)?) { + dynamicContent.restart(restart) + } + override val onBackPressedDispatcher = onBackPressedDispatcher override val uiConfiguration = MutableStateFlow(uiConfiguration) @@ -106,7 +118,7 @@ public fun TreehouseContent( } }, ) { - treehouseView.root.value() + treehouseView.value() } } 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 112ffab2b5..c525b702ae 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 @@ -24,18 +24,15 @@ import androidx.activity.OnBackPressedDispatcher as AndroidOnBackPressedDispatch import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener import app.cash.redwood.treehouse.TreehouseView.WidgetSystem import app.cash.redwood.widget.RedwoodLayout -import app.cash.redwood.widget.RedwoodView -import app.cash.redwood.widget.ViewRoot import app.cash.treehouse.host.R import java.util.UUID @SuppressLint("ViewConstructor") -public class TreehouseLayout( +public open class TreehouseLayout( context: Context, override val widgetSystem: WidgetSystem, androidOnBackPressedDispatcher: AndroidOnBackPressedDispatcher, - root: RedwoodView.Root = ViewRoot(context), -) : RedwoodLayout(context, androidOnBackPressedDispatcher, root), +) : RedwoodLayout(context, androidOnBackPressedDispatcher), TreehouseView { override var readyForContentChangeListener: ReadyForContentChangeListener? = null set(value) { @@ -47,10 +44,10 @@ public class TreehouseLayout( * Like [View.isAttachedToWindow]. We'd prefer that property but it's false until * [onAttachedToWindow] returns and true until [onDetachedFromWindow] returns. */ - override var readyForContent: Boolean = false + final override var readyForContent: Boolean = false private set - override var stateSnapshotId: StateSnapshot.Id = StateSnapshot.Id(null) + final override var stateSnapshotId: StateSnapshot.Id = StateSnapshot.Id(null) private set override var saveCallback: TreehouseView.SaveCallback? = null diff --git a/redwood-treehouse-host/src/appsJvmTest/kotlin/app/cash/redwood/treehouse/LeaksTest.kt b/redwood-treehouse-host/src/appsJvmTest/kotlin/app/cash/redwood/treehouse/LeaksTest.kt index 71c44b7ba5..712e641157 100644 --- a/redwood-treehouse-host/src/appsJvmTest/kotlin/app/cash/redwood/treehouse/LeaksTest.kt +++ b/redwood-treehouse-host/src/appsJvmTest/kotlin/app/cash/redwood/treehouse/LeaksTest.kt @@ -46,7 +46,7 @@ class LeaksTest { assertThat(textInputValue.text).isEqualTo("what would you like to see?") val widgetLeakWatcher = LeakWatcher { - view.root.children.widgets.single() + view.children.widgets.single() } // While the widget is in the UI, it's expected to be in a reference cycle. 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 dbae374355..9a6ee548e2 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 @@ -40,12 +40,12 @@ public class ChangeListRenderer( view: TreehouseView, changeList: SnapshotChangeList, ) { - view.root.children.remove(0, view.root.children.widgets.size) + view.children.remove(0, view.children.widgets.size) val hostAdapter = HostProtocolAdapter( // Use latest host version as the guest version to avoid any compatibility behavior. guestVersion = hostRedwoodVersion, - container = view.root.children, + container = view.children, factory = view.widgetSystem.widgetFactory( json, ProtocolMismatchHandler.Throwing, 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 e6d3a0974f..837462aa4a 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 @@ -140,7 +140,7 @@ internal class TreehouseAppContent( if (stateFlow.value.viewState == ViewState.Bound(view)) return // Idempotent. - view.root.restart(codeHost::restart) + view.restart(codeHost::restart) preload(view.onBackPressedDispatcher, view.uiConfiguration) @@ -156,7 +156,7 @@ internal class TreehouseAppContent( // code is coming. when (nextCodeState) { is CodeState.Idle -> { - view.root.contentState( + view.contentState( loadCount = nextCodeState.loadCount, attached = false, ) @@ -187,7 +187,7 @@ internal class TreehouseAppContent( if (previousViewState is ViewState.None) return // Idempotent. if (previousViewState is ViewState.Bound) { - previousViewState.view.root.restart(null) // Break a reference cycle. + previousViewState.view.restart(null) // Break a reference cycle. } val nextViewState = ViewState.None val nextCodeState = CodeState.Idle( @@ -424,7 +424,7 @@ private class ViewContentCodeBinding( @Suppress("UNCHECKED_CAST") // We don't have a type parameter for the widget type. hostAdapter = HostProtocolAdapter( guestVersion = codeSession.guestProtocolVersion, - container = view.root.children as Widget.Children, + container = view.children as Widget.Children, factory = view.widgetSystem.widgetFactory( json = codeSession.json, protocolMismatchHandler = eventPublisher.widgetProtocolMismatchHandler, @@ -436,8 +436,8 @@ private class ViewContentCodeBinding( } if (deliveredChangeCount++ == 0) { - view.root.children.remove(0, view.root.children.widgets.size) - view.root.contentState( + view.children.remove(0, view.children.widgets.size) + view.contentState( loadCount = loadCount, attached = true, ) @@ -537,7 +537,7 @@ private class ViewContentCodeBinding( canceled = true viewOrNull?.let { view -> - view.root.contentState( + view.contentState( loadCount = loadCount, attached = false, uncaughtException = exception, diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeRoot.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeRoot.kt deleted file mode 100644 index 388cb5ed54..0000000000 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeRoot.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2024 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.redwood.treehouse - -import app.cash.redwood.Modifier -import app.cash.redwood.testing.WidgetValue -import app.cash.redwood.widget.MutableListChildren -import app.cash.redwood.widget.RedwoodView -import app.cash.redwood.widget.Widget - -class FakeRoot( - private val eventLog: EventLog, -) : RedwoodView.Root { - private val childrenDelegate = MutableListChildren() - - /** Keep views (but not widgets) after [Widget.Children.detach]. */ - private var viewsAfterDetach: List? = null - - val views: List - get() = viewsAfterDetach ?: childrenDelegate.widgets.map { it.value } - - override val children = object : Widget.Children by childrenDelegate { - override fun detach() { - viewsAfterDetach = childrenDelegate.widgets.map { it.value } - childrenDelegate.detach() - } - } - - override val value: WidgetValue - get() = error("unexpected call") - override var modifier: Modifier = Modifier - - override fun contentState(loadCount: Int, attached: Boolean, uncaughtException: Throwable?) { - // Canonicalize "java.lang.Exception(boom!)" to "kotlin.Exception(boom!)". - val exceptionString = uncaughtException?.toString()?.replace("java.lang.", "kotlin.") - - // TODO(jwilson): this is a backwards-compatibility shim. Emit a simpler event. - eventLog += when { - loadCount == 0 && !attached -> "codeListener.onInitialCodeLoading()" - attached -> "codeListener.onCodeLoaded($loadCount)" - else -> "codeListener.onCodeDetached($exceptionString)" - } - } - - override fun restart(restart: (() -> Unit)?) { - } -} diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseView.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseView.kt index 087137d5fa..a09fc7ad4e 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseView.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseView.kt @@ -15,13 +15,13 @@ */ package app.cash.redwood.treehouse -import app.cash.redwood.RedwoodCodegenApi import app.cash.redwood.layout.testing.RedwoodLayoutTestingWidgetFactory import app.cash.redwood.lazylayout.testing.RedwoodLazyLayoutTestingWidgetFactory import app.cash.redwood.testing.WidgetValue import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener import app.cash.redwood.ui.OnBackPressedDispatcher import app.cash.redwood.ui.UiConfiguration +import app.cash.redwood.widget.MutableListChildren import app.cash.redwood.widget.SavedStateRegistry import app.cash.redwood.widget.Widget import com.example.redwood.testapp.protocol.host.TestSchemaProtocolFactory @@ -38,17 +38,18 @@ import kotlinx.coroutines.flow.StateFlow */ internal class FakeTreehouseView( private val name: String, - eventLog: EventLog, + private val eventLog: EventLog, override val onBackPressedDispatcher: OnBackPressedDispatcher, override val uiConfiguration: StateFlow = MutableStateFlow(UiConfiguration()), ) : TreehouseView { - override val root = FakeRoot(eventLog) + override val children = FakeChildren() - @Deprecated("inline this?") val views: List - get() = root.children.widgets.map { it.value } + get() = children.views + + override val value: WidgetValue + get() = error("unexpected call") - @OptIn(RedwoodCodegenApi::class) override val widgetSystem = TreehouseView.WidgetSystem { json, protocolMismatchHandler -> TestSchemaProtocolFactory( widgetSystem = TestSchemaWidgetSystem( @@ -74,5 +75,40 @@ internal class FakeTreehouseView( override val savedStateRegistry: SavedStateRegistry? = null + override fun contentState(loadCount: Int, attached: Boolean, uncaughtException: Throwable?) { + // Remove all child views in case the previous content state left some behind. + if (attached) children.views.clear() + + // Canonicalize "java.lang.Exception(boom!)" to "kotlin.Exception(boom!)". + val exceptionString = uncaughtException?.toString()?.replace("java.lang.", "kotlin.") + + // TODO(jwilson): this is a backwards-compatibility shim. Emit a simpler event. + eventLog += when { + loadCount == 0 && !attached -> "codeListener.onInitialCodeLoading()" + attached -> "codeListener.onCodeLoaded($loadCount)" + else -> "codeListener.onCodeDetached($exceptionString)" + } + } + + override fun restart(restart: (() -> Unit)?) { + } + override fun toString() = name + + internal class FakeChildren( + private val childrenDelegate: MutableListChildren = MutableListChildren(), + ) : Widget.Children by childrenDelegate { + /** A private copy of the child views that isn't cleared by `detach()`. */ + val views = mutableListOf() + + override fun insert(index: Int, widget: Widget) { + views.add(index, widget.value) + childrenDelegate.insert(index, widget) + } + + override fun remove(index: Int, count: Int) { + views.subList(index, index + count).clear() + childrenDelegate.remove(index, count) + } + } } diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt index a3ae22d026..ef14fb0a4f 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt @@ -187,9 +187,9 @@ class TreehouseAppContentTest { "codeSessionB.app.uis[0].start()", ) - // This still shows UI from codeSessionA. There's no onCodeLoaded() and no reset() until the new - // code's first widget is added! - val buttonA = view1.root.views.single() as ButtonValue + // This still shows UI from codeSessionA. The content isn't attached until the new code's first + // widget is added! + val buttonA = view1.views.single() as ButtonValue assertThat(buttonA.text).isEqualTo("helloA") codeSessionB.appService.uis.single().addWidget("helloB") eventLog.takeEvent("codeListener.onCodeLoaded(1)") 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 e490b820b9..e154890403 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 @@ -18,14 +18,12 @@ package app.cash.redwood.treehouse import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener import app.cash.redwood.treehouse.TreehouseView.WidgetSystem import app.cash.redwood.widget.RedwoodUIView -import app.cash.redwood.widget.UIViewRoot import platform.UIKit.UIView @ObjCName("TreehouseUIView", exact = true) public class TreehouseUIView( override val widgetSystem: WidgetSystem, - root: UIViewRoot, -) : RedwoodUIView(root), +) : RedwoodUIView(), TreehouseView { override var saveCallback: TreehouseView.SaveCallback? = null override var stateSnapshotId: StateSnapshot.Id = StateSnapshot.Id(null) @@ -37,9 +35,7 @@ public class TreehouseUIView( } override val readyForContent: Boolean - get() = root.value.superview != null - - public constructor(widgetSystem: WidgetSystem) : this(widgetSystem, UIViewRoot()) + get() = value.superview != null override fun superviewChanged() { readyForContentChangeListener?.onReadyForContentChanged(this) diff --git a/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/LayoutTester.kt b/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/LayoutTester.kt index 088500112d..7e689d9cdb 100644 --- a/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/LayoutTester.kt +++ b/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/LayoutTester.kt @@ -69,9 +69,9 @@ class LayoutTester( Subject.TreehouseView -> TreehouseUIView(throwingWidgetSystem) .apply { - (this.root.children as UIViewChildren).insert(0, viewWidget(referenceView)) + (this.children as UIViewChildren).insert(0, viewWidget(referenceView)) } - .root.value + .value } } 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 aa3c521507..eabbd07238 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 @@ -43,10 +43,10 @@ class TreehouseUIViewTest { val layout = TreehouseUIView(throwingWidgetSystem) val view = UIView() - layout.root.children.insert(0, viewWidget(view)) - assertThat(layout.root.value.subviews).hasSize(1) + layout.children.insert(0, viewWidget(view)) + assertThat(layout.value.subviews).hasSize(1) // For some reason `assertSame` fails on these references. - assertThat(layout.root.value.subviews[0].objcPtr()).isEqualTo(view.objcPtr()) + assertThat(layout.value.subviews[0].objcPtr()).isEqualTo(view.objcPtr()) } @Test fun attachAndDetachSendsStateChange() { @@ -57,10 +57,10 @@ class TreehouseUIViewTest { layout.readyForContentChangeListener = listener assertThat(listener.count).isEqualTo(0) - parent.addSubview(layout.root.value) + parent.addSubview(layout.value) assertThat(listener.count).isEqualTo(1) - layout.root.value.removeFromSuperview() + layout.value.removeFromSuperview() assertThat(listener.count).isEqualTo(2) } @@ -70,7 +70,7 @@ class TreehouseUIViewTest { parent.overrideUserInterfaceStyle = UIUserInterfaceStyleDark val layout = TreehouseUIView(throwingWidgetSystem) - parent.addSubview(layout.root.value) + parent.addSubview(layout.value) assertThat(layout.uiConfiguration.value.darkMode).isTrue() } @@ -79,7 +79,7 @@ class TreehouseUIViewTest { val parent = UIWindow() val layout = TreehouseUIView(throwingWidgetSystem) - parent.addSubview(layout.root.value) + parent.addSubview(layout.value) layout.uiConfiguration.test { assertThat(awaitItem().darkMode).isFalse() @@ -103,7 +103,7 @@ class TreehouseUIViewTest { } val layout = TreehouseUIView(throwingWidgetSystem) - parent.addSubview(layout.root.value) + parent.addSubview(layout.value) assertThat(layout.uiConfiguration.value.safeAreaInsets).isEqualTo(expectedInsets) } @@ -113,7 +113,7 @@ class TreehouseUIViewTest { val parent = UIWindow() val layout = TreehouseUIView(throwingWidgetSystem) - parent.addSubview(layout.root.value) + parent.addSubview(layout.value) val expectedLayoutDirection = when (val direction = parent.effectiveUserInterfaceLayoutDirection) { UIUserInterfaceLayoutDirectionLeftToRight -> LayoutDirection.Ltr diff --git a/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt b/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt index fda3b79476..2aee8fe1ee 100644 --- a/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt +++ b/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt @@ -35,7 +35,7 @@ abstract class AbstractRedwoodViewTest> { @Test fun testSingleChildElement() { val redwoodView = redwoodView() - redwoodView.root.children.insert(0, widgetFactory.text("Hello ".repeat(50))) + redwoodView.children.insert(0, widgetFactory.text("Hello ".repeat(50))) snapshotter(redwoodView).snapshot() } } diff --git a/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/RedwoodLayout.kt b/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/RedwoodLayout.kt index e830e17b64..d064e320d5 100644 --- a/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/RedwoodLayout.kt +++ b/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/RedwoodLayout.kt @@ -19,10 +19,11 @@ import android.annotation.SuppressLint import android.content.Context import android.content.res.Configuration import android.view.View -import android.widget.FrameLayout +import android.view.ViewGroup import androidx.activity.OnBackPressedCallback as AndroidOnBackPressedCallback import androidx.activity.OnBackPressedDispatcher as AndroidOnBackPressedDispatcher import androidx.core.graphics.Insets +import androidx.core.view.children as viewGroupChildren import androidx.savedstate.findViewTreeSavedStateRegistryOwner import app.cash.redwood.ui.Cancellable import app.cash.redwood.ui.Density @@ -38,13 +39,16 @@ import kotlinx.coroutines.flow.StateFlow public open class RedwoodLayout( context: Context, androidOnBackPressedDispatcher: AndroidOnBackPressedDispatcher, - public final override val root: RedwoodView.Root = ViewRoot(context), -) : FrameLayout(context), +) : ViewGroup(context), RedwoodView { + + final override val children: Widget.Children = ViewGroupChildren(this) + final override val value: View + get() = this + init { // The view needs to have an ID to participate in instance state saving. id = R.id.redwood_layout - super.addView(root.value) } private val mutableUiConfiguration = MutableStateFlow(computeUiConfiguration()) @@ -82,15 +86,36 @@ public open class RedwoodLayout( } } + override fun contentState( + loadCount: Int, + attached: Boolean, + uncaughtException: Throwable?, + ) { + // Remove all child views in case the previous content state left some behind. + removeAllViews() + } + + override fun restart(restart: (() -> Unit)?) { + // This base class doesn't call restart(). + } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - root.value.measure(widthMeasureSpec, heightMeasureSpec) - setMeasuredDimension(root.value.measuredWidth, root.value.measuredHeight) + var maxWidth = 0 + var maxHeight = 0 + for (child in viewGroupChildren) { + child.measure(widthMeasureSpec, heightMeasureSpec) + maxWidth = maxOf(maxWidth, child.measuredWidth) + maxHeight = maxOf(maxHeight, child.measuredHeight) + } + setMeasuredDimension(maxWidth, maxHeight) } @SuppressLint("DrawAllocation") // It's only on layout. override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { mutableUiConfiguration.value = computeUiConfiguration() - root.value.layout(0, 0, right - left, bottom - top) + for (child in viewGroupChildren) { + child.layout(0, 0, right - left, bottom - top) + } } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewRoot.kt b/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewRoot.kt deleted file mode 100644 index 3d879628ac..0000000000 --- a/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewRoot.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2024 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.redwood.widget - -import android.content.Context -import android.view.View -import android.view.ViewGroup -import androidx.core.view.children as viewGroupChildren -import app.cash.redwood.Modifier - -/** - * A default base implementation of [RedwoodView.Root] suitable for subclassing. - * - * This view contributes nothing to the view hierarchy. It forwards all measurement and layout calls - * from its own parent view to its child views. - */ -public open class ViewRoot( - context: Context, -) : ViewGroup(context), - RedwoodView.Root { - final override val children: Widget.Children = ViewGroupChildren(this) - final override val value: View - get() = this - final override var modifier: Modifier = Modifier - - override fun contentState( - loadCount: Int, - attached: Boolean, - uncaughtException: Throwable?, - ) { - } - - override fun restart(restart: (() -> Unit)?) { - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - var maxWidth = 0 - var maxHeight = 0 - for (child in viewGroupChildren) { - child.measure(widthMeasureSpec, heightMeasureSpec) - maxWidth = maxOf(maxWidth, child.measuredWidth) - maxHeight = maxOf(maxHeight, child.measuredHeight) - } - setMeasuredDimension(maxWidth, maxHeight) - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - for (child in viewGroupChildren) { - child.layout(0, 0, right - left, bottom - top) - } - } -} diff --git a/redwood-widget/src/commonMain/kotlin/app/cash/redwood/widget/RedwoodView.kt b/redwood-widget/src/commonMain/kotlin/app/cash/redwood/widget/RedwoodView.kt index dff67e4f8e..7f5b909235 100644 --- a/redwood-widget/src/commonMain/kotlin/app/cash/redwood/widget/RedwoodView.kt +++ b/redwood-widget/src/commonMain/kotlin/app/cash/redwood/widget/RedwoodView.kt @@ -22,53 +22,51 @@ import kotlinx.coroutines.flow.StateFlow @ObjCName("RedwoodView", exact = true) public interface RedwoodView { - public val root: Root public val onBackPressedDispatcher: OnBackPressedDispatcher public val uiConfiguration: StateFlow public val savedStateRegistry: SavedStateRegistry? - @ObjCName("Root", exact = true) - public interface Root : Widget { - /** - * The current dynamic content. If the application logic is stopped or crashed this will retain - * a snapshot of the most-recent content, but the content will ignore user actions. - */ - public val children: Widget.Children + public val value: W - /** - * Called for lifecycle changes to the content choreographing this view. - * - * Content may be bound to this view before it has a view tree. When that happens this will - * be called with `loadCount = 0` and `attached = false`. Content won't be ready if it is busy - * downloading, launching, or computing an initial view tree. - * - * When the content's initial view tree is ready, this is called with `loadCount = 1` and - * `attached = true`. This signals that the content is both running and interactive. - * - * If the content stops, this is called again with `attached = false`. This happens when the - * content is detached, either gracefully or due to a crash. Call the lambda provided to - * [restart] to relaunch the content. - * - * Each time the content is replaced dynamically (a ‘hot reload’), this is called with an - * incremented [loadCount]. - * - * @param loadCount how many different versions of content have been loaded for this view. This - * is 1 for the first load, and increments for subsequent reloads. Use this to trigger an - * 'reloaded' message or animation when business logic is updated. This may skip values if - * content stops before it emits its initial view tree. - * @param attached true if the content is interactive. This is false if it has stopped or - * crashed. - * @param uncaughtException the exception that caused the content to detach. - */ - public fun contentState( - loadCount: Int, - attached: Boolean, - uncaughtException: Throwable? = null, - ) + /** + * The current dynamic content. If the application logic is stopped or crashed this will retain + * a snapshot of the most-recent content, but the content will ignore user actions. + */ + public val children: Widget.Children - /** - * Call the provided lambda to restart the business logic that powers this UI. - */ - public fun restart(restart: (() -> Unit)?) - } + /** + * Called for lifecycle changes to the content choreographing this view. + * + * Content may be bound to this view before it has a view tree. When that happens this will + * be called with `loadCount = 0` and `attached = false`. Content won't be ready if it is busy + * downloading, launching, or computing an initial view tree. + * + * When the content's initial view tree is ready, this is called with `loadCount = 1` and + * `attached = true`. This signals that the content is both running and interactive. + * + * If the content stops, this is called again with `attached = false`. This happens when the + * content is detached, either gracefully or due to a crash. Call the lambda provided to + * [restart] to relaunch the content. + * + * Each time the content is replaced dynamically (a ‘hot reload’), this is called with an + * incremented [loadCount]. + * + * @param loadCount how many different versions of content have been loaded for this view. This + * is 1 for the first load, and increments for subsequent reloads. Use this to trigger an + * 'reloaded' message or animation when business logic is updated. This may skip values if + * content stops before it emits its initial view tree. + * @param attached true if the content is interactive. This is false if it has stopped or + * crashed. + * @param uncaughtException the exception that caused the content to detach. + */ + public fun contentState( + loadCount: Int, + attached: Boolean, + uncaughtException: Throwable? = null, + ) + + /** + * Call the provided lambda to restart the business logic that powers this UI. + */ + public fun restart(restart: (() -> Unit)?) } diff --git a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt index 8028543f8b..99b8d2d1d0 100644 --- a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt +++ b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt @@ -25,11 +25,17 @@ import app.cash.redwood.ui.OnBackPressedDispatcher import app.cash.redwood.ui.Size import app.cash.redwood.ui.UiConfiguration import kotlinx.cinterop.CValue +import kotlinx.cinterop.cValue import kotlinx.cinterop.useContents import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import platform.CoreGraphics.CGRect +import platform.CoreGraphics.CGRectZero import platform.UIKit.UIApplication +import platform.UIKit.UILayoutConstraintAxisVertical +import platform.UIKit.UIStackView +import platform.UIKit.UIStackViewAlignmentFill +import platform.UIKit.UIStackViewDistributionFillEqually import platform.UIKit.UITraitCollection import platform.UIKit.UIUserInterfaceLayoutDirection import platform.UIKit.UIUserInterfaceLayoutDirection.UIUserInterfaceLayoutDirectionLeftToRight @@ -37,17 +43,22 @@ import platform.UIKit.UIUserInterfaceLayoutDirection.UIUserInterfaceLayoutDirect import platform.UIKit.UIUserInterfaceStyle import platform.UIKit.UIView -public open class RedwoodUIView( - final override val root: UIViewRoot, -) : RedwoodView { - public constructor() : this(UIViewRoot()) +public open class RedwoodUIView : RedwoodView { + private val valueRootView: RootUIStackView = RootUIStackView() + + override val value: UIView + get() = valueRootView + + private val _children = UIViewChildren(valueRootView) + override val children: Widget.Children + get() = _children private val mutableUiConfiguration = MutableStateFlow( computeUiConfiguration( - traitCollection = root.value.traitCollection, - layoutDirection = root.value.effectiveUserInterfaceLayoutDirection, - bounds = root.value.bounds, + traitCollection = valueRootView.traitCollection, + layoutDirection = valueRootView.effectiveUserInterfaceLayoutDirection, + bounds = valueRootView.bounds, ), ) @@ -65,19 +76,63 @@ public open class RedwoodUIView( override val savedStateRegistry: SavedStateRegistry? get() = null - init { - root.valueRootView.redwoodUIView = this + override fun contentState( + loadCount: Int, + attached: Boolean, + uncaughtException: Throwable?, + ) { + // Remove all child views in case the previous content state left some behind. + for (subview in value.subviews.toList()) { + (subview as UIView).removeFromSuperview() + } + } + + override fun restart(restart: (() -> Unit)?) { + // This base class doesn't call restart(). } - public fun updateUiConfiguration() { + private fun updateUiConfiguration() { mutableUiConfiguration.value = computeUiConfiguration( - traitCollection = root.value.traitCollection, - layoutDirection = root.value.effectiveUserInterfaceLayoutDirection, - bounds = root.value.bounds, + traitCollection = valueRootView.traitCollection, + layoutDirection = valueRootView.effectiveUserInterfaceLayoutDirection, + bounds = valueRootView.bounds, ) } - public open fun superviewChanged() { + protected open fun superviewChanged() { + } + + /** + * In practice we expect this to contain either zero child subviews (especially when + * newly-initialized) or one child subview, which will usually be a layout container. + * + * This could just as easily be a horizontal stack. A box would be even better, but there's no + * such built-in component and implementing it manually is difficult if we want to react to + * content resizes. + */ + private inner class RootUIStackView : UIStackView(cValue { CGRectZero }) { + init { + this.axis = UILayoutConstraintAxisVertical + this.alignment = UIStackViewAlignmentFill // Fill horizontal. + this.distribution = UIStackViewDistributionFillEqually // Fill vertical. + } + + override fun layoutSubviews() { + super.layoutSubviews() + + // Bounds likely changed. Report new size. + updateUiConfiguration() + } + + override fun didMoveToSuperview() { + super.didMoveToSuperview() + superviewChanged() + } + + override fun traitCollectionDidChange(previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateUiConfiguration() + } } } diff --git a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewRoot.kt b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewRoot.kt deleted file mode 100644 index 289e4f2750..0000000000 --- a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewRoot.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2024 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.redwood.widget - -import app.cash.redwood.Modifier -import kotlinx.cinterop.cValue -import platform.CoreGraphics.CGRectZero -import platform.UIKit.UILayoutConstraintAxisVertical -import platform.UIKit.UIStackView -import platform.UIKit.UIStackViewAlignmentFill -import platform.UIKit.UIStackViewDistributionFillEqually -import platform.UIKit.UITraitCollection -import platform.UIKit.UIView - -/** - * A default base implementation of [RedwoodView.Root] suitable for subclassing. - * - * The [value] view contributes nothing to the view hierarchy. It forwards all measurement and - * layout calls from its own parent view to its child views. - */ -@ObjCName("UIViewRoot", exact = true) -public open class UIViewRoot : RedwoodView.Root { - internal val valueRootView: RootUIStackView = RootUIStackView() - - override val value: UIView - get() = valueRootView - - private val _children = UIViewChildren(valueRootView) - override val children: Widget.Children - get() = _children - - override var modifier: Modifier = Modifier - - override fun contentState( - loadCount: Int, - attached: Boolean, - uncaughtException: Throwable?, - ) { - } - - override fun restart(restart: (() -> Unit)?) { - } - - /** - * In practice we expect this to contain either zero child subviews (especially when - * newly-initialized) or one child subview, which will usually be a layout container. - * - * This could just as easily be a horizontal stack. A box would be even better, but there's no - * such built-in component and implementing it manually is difficult if we want to react to - * content resizes. - */ - internal class RootUIStackView : UIStackView(cValue { CGRectZero }) { - var redwoodUIView: RedwoodUIView? = null - - init { - this.axis = UILayoutConstraintAxisVertical - this.alignment = UIStackViewAlignmentFill // Fill horizontal. - this.distribution = UIStackViewDistributionFillEqually // Fill vertical. - } - - override fun layoutSubviews() { - super.layoutSubviews() - - // Bounds likely changed. Report new size. - redwoodUIView?.updateUiConfiguration() - } - - override fun didMoveToSuperview() { - super.didMoveToSuperview() - redwoodUIView?.superviewChanged() - } - - override fun traitCollectionDidChange(previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - redwoodUIView?.updateUiConfiguration() - } - } -} diff --git a/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/HTMLRoot.kt b/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/HTMLRoot.kt deleted file mode 100644 index c588581047..0000000000 --- a/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/HTMLRoot.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.redwood.widget - -import app.cash.redwood.Modifier -import org.w3c.dom.HTMLElement - -public class HTMLRoot( - override val value: HTMLElement, -) : RedwoodView.Root { - override val children: Widget.Children = HTMLElementChildren(value) - override var modifier: Modifier = Modifier - - override fun contentState(loadCount: Int, attached: Boolean, uncaughtException: Throwable?) { - } - - override fun restart(restart: (() -> Unit)?) { - } -} diff --git a/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/RedwoodHTMLElementView.kt b/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/RedwoodHTMLElementView.kt index 80be9f9002..d3666fe444 100644 --- a/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/RedwoodHTMLElementView.kt +++ b/redwood-widget/src/jsMain/kotlin/app/cash/redwood/widget/RedwoodHTMLElementView.kt @@ -25,6 +25,7 @@ import app.cash.redwood.ui.dp import kotlinx.browser.window import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.dom.clear import org.w3c.dom.HTMLElement import org.w3c.dom.MediaQueryList import org.w3c.dom.events.Event @@ -37,9 +38,9 @@ public fun HTMLElement.asRedwoodView(): RedwoodView { } private class RedwoodHTMLElementView( - element: HTMLElement, + override val value: HTMLElement, ) : RedwoodView { - override val root = HTMLRoot(element) + override val children: Widget.Children = HTMLElementChildren(value) private var pixelRatioQueryRemover: (() -> Unit)? = null @@ -64,8 +65,8 @@ private class RedwoodHTMLElementView( _uiConfiguration = MutableStateFlow( UiConfiguration( darkMode = colorSchemeQuery.matches, - viewportSize = Size(width = element.offsetWidth.dp, height = element.offsetHeight.dp), - layoutDirection = when (element.dir) { + viewportSize = Size(width = value.offsetWidth.dp, height = value.offsetHeight.dp), + layoutDirection = when (value.dir) { "ltr" -> LayoutDirection.Ltr "rtl" -> LayoutDirection.Rtl "auto" -> LayoutDirection.Auto @@ -92,6 +93,19 @@ private class RedwoodHTMLElementView( // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver } + override fun contentState( + loadCount: Int, + attached: Boolean, + uncaughtException: Throwable?, + ) { + // Remove all child views in case the previous content state left some behind. + value.clear() + } + + override fun restart(restart: (() -> Unit)?) { + // This base class doesn't call restart(). + } + private fun observePixelRatioChange() { // From https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#javascript_2. diff --git a/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt b/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt index 625c892bab..ebe06998f0 100644 --- a/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt +++ b/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt @@ -19,7 +19,6 @@ import app.cash.redwood.compose.DisplayLinkClock import app.cash.redwood.compose.RedwoodComposition import app.cash.redwood.layout.uiview.UIViewRedwoodLayoutWidgetFactory import app.cash.redwood.widget.RedwoodUIView -import app.cash.redwood.widget.UIViewRoot import com.example.redwood.counter.presenter.Counter import com.example.redwood.counter.widget.SchemaWidgetSystem import kotlinx.coroutines.MainScope diff --git a/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt b/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt index 5edf9a4d1b..293791bda4 100644 --- a/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt +++ b/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt @@ -116,7 +116,7 @@ class EmojiSearchActivity : ComponentActivity() { widgetSystem = widgetSystem, contentSource = treehouseContentSource, modifier = Modifier.padding(contentPadding), - root = { scope -> EmojiSearchComposeUiRoot(scope) }, + dynamicContent = EmojiSearchComposeUiRoot(), ) } } diff --git a/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchComposeUiRoot.kt b/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchComposeUiRoot.kt index d24937475b..86f16dcc13 100644 --- a/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchComposeUiRoot.kt +++ b/samples/emoji-search/android-composeui/src/main/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchComposeUiRoot.kt @@ -19,34 +19,23 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import app.cash.redwood.treehouse.composeui.ComposeUiRoot +import app.cash.redwood.treehouse.composeui.DynamicContent +import app.cash.redwood.widget.compose.ComposeWidgetChildren import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -internal class EmojiSearchComposeUiRoot( - private val scope: CoroutineScope, -) : ComposeUiRoot() { - private var loadCount by mutableIntStateOf(0) - private var attached by mutableStateOf(false) - private var uncaughtException by mutableStateOf(null) - private var restart: (() -> Unit)? = null - +internal class EmojiSearchComposeUiRoot : DynamicContent() { override fun contentState( + scope: CoroutineScope, loadCount: Int, attached: Boolean, uncaughtException: Throwable?, ) { - this.loadCount = loadCount - this.attached = attached - this.uncaughtException = uncaughtException + super.contentState(scope, loadCount, attached, uncaughtException) if (uncaughtException != null) { scope.launch { @@ -56,12 +45,8 @@ internal class EmojiSearchComposeUiRoot( } } - override fun restart(restart: (() -> Unit)?) { - this.restart = restart - } - @Composable - override fun Render() { + override fun Render(children: ComposeWidgetChildren) { val uncaughtException = this.uncaughtException if (uncaughtException != null) { Box( @@ -83,6 +68,6 @@ internal class EmojiSearchComposeUiRoot( return } - super.Render() + super.Render(children) } } diff --git a/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt b/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt index 409658dc90..8ef7d39203 100644 --- a/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt +++ b/samples/emoji-search/android-views/src/main/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt @@ -19,9 +19,10 @@ import android.annotation.SuppressLint import android.content.Context import android.os.Bundle import android.util.Log +import android.view.View import androidx.activity.ComponentActivity +import androidx.activity.OnBackPressedDispatcher import androidx.core.view.WindowCompat -import androidx.core.view.iterator import app.cash.redwood.compose.AndroidUiDispatcher.Companion.Main import app.cash.redwood.layout.view.ViewRedwoodLayoutWidgetFactory import app.cash.redwood.lazylayout.view.ViewRedwoodLazyLayoutWidgetFactory @@ -31,9 +32,8 @@ import app.cash.redwood.treehouse.TreehouseApp import app.cash.redwood.treehouse.TreehouseAppFactory import app.cash.redwood.treehouse.TreehouseContentSource import app.cash.redwood.treehouse.TreehouseLayout -import app.cash.redwood.treehouse.TreehouseView +import app.cash.redwood.treehouse.TreehouseView.WidgetSystem import app.cash.redwood.treehouse.bindWhenReady -import app.cash.redwood.widget.ViewRoot import app.cash.zipline.Zipline import app.cash.zipline.ZiplineManifest import app.cash.zipline.loader.ManifestVerifier @@ -82,7 +82,7 @@ class EmojiSearchActivity : ComponentActivity() { val treehouseApp = createTreehouseApp() val treehouseContentSource = TreehouseContentSource(EmojiSearchPresenter::launch) - val widgetSystem = TreehouseView.WidgetSystem { json, protocolMismatchHandler -> + val widgetSystem = WidgetSystem { json, protocolMismatchHandler -> EmojiSearchProtocolFactory( widgetSystem = EmojiSearchWidgetSystem( EmojiSearch = AndroidEmojiSearchWidgetFactory(context), @@ -94,18 +94,19 @@ class EmojiSearchActivity : ComponentActivity() { ) } - val viewRoot = EmojiSearchViewRoot(context, scope) - - treehouseLayout = TreehouseLayout(this, widgetSystem, onBackPressedDispatcher, viewRoot).apply { - treehouseContentSource.bindWhenReady(this, treehouseApp) - } + treehouseLayout = EmojiSearchTreehouseLayout(this, widgetSystem, onBackPressedDispatcher, scope) + .apply { + treehouseContentSource.bindWhenReady(this, treehouseApp) + } setContentView(treehouseLayout) } - private class EmojiSearchViewRoot( + private class EmojiSearchTreehouseLayout( context: Context, + widgetSystem: WidgetSystem, + androidOnBackPressedDispatcher: OnBackPressedDispatcher, private val scope: CoroutineScope, - ) : ViewRoot(context) { + ) : TreehouseLayout(context, widgetSystem, androidOnBackPressedDispatcher) { private var restart: (() -> Unit)? = null override fun contentState( @@ -113,6 +114,8 @@ class EmojiSearchActivity : ComponentActivity() { attached: Boolean, uncaughtException: Throwable?, ) { + super.contentState(loadCount, attached, uncaughtException) + if (uncaughtException != null) { scope.launch { delay(2.seconds) @@ -120,13 +123,6 @@ class EmojiSearchActivity : ComponentActivity() { } } - val i = iterator() - while (i.hasNext()) { - val child = i.next() - if (child is ExceptionView || child is LoadingView) { - i.remove() - } - } if (loadCount == 0) { addView(LoadingView(context)) } diff --git a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift index 42e0f9c71b..04bd2dbde3 100644 --- a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift +++ b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift @@ -79,11 +79,7 @@ class EmojiSearchUIViewRoot : UIViewRoot { attached: Bool, uncaughtException: KotlinThrowable? ) { - for view in value.subviews { - if let exceptionView = view as? ExceptionView { - exceptionView.removeFromSuperview() - } - } + super.contentState(loadCount: loadCount, attached: attached, uncaughtException: uncaughtException) if uncaughtException != nil { let exceptionView = ExceptionView(uncaughtException!) From 6609017010e07695425e30a69a281e1f9643d225 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Mon, 21 Oct 2024 11:45:38 -0400 Subject: [PATCH 2/5] Fixup Swift --- .../redwood/treehouse/TreehouseLayoutTest.kt | 4 ++-- .../cash/redwood/treehouse/TreehouseUIView.kt | 2 +- .../app/cash/redwood/widget/RedwoodUIView.kt | 1 + .../counter/ios/CounterViewControllerDelegate.kt | 4 ++-- .../CounterApp/CounterViewController.swift | 8 ++++---- .../EmojiSearchViewController.swift | 16 +++++++++++----- 6 files changed, 21 insertions(+), 14 deletions(-) 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 9e82869911..deefbd17e0 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 @@ -51,10 +51,10 @@ class TreehouseLayoutTest { @Test fun widgetsAddChildViews() { val layout = TreehouseLayout(activity, throwingWidgetSystem, activity.onBackPressedDispatcher) - val rootView = layout.root.value as ViewGroup + val rootView = layout.value as ViewGroup val view = View(activity) - layout.root.children.insert(0, viewWidget(view)) + layout.children.insert(0, viewWidget(view)) assertThat(rootView.childCount).isEqualTo(1) assertThat(rootView.getChildAt(0)).isSameInstanceAs(view) } 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 e154890403..16b7bba0df 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 @@ -21,7 +21,7 @@ import app.cash.redwood.widget.RedwoodUIView import platform.UIKit.UIView @ObjCName("TreehouseUIView", exact = true) -public class TreehouseUIView( +public open class TreehouseUIView( override val widgetSystem: WidgetSystem, ) : RedwoodUIView(), TreehouseView { diff --git a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt index 99b8d2d1d0..b4fe59acbf 100644 --- a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt +++ b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/RedwoodUIView.kt @@ -43,6 +43,7 @@ import platform.UIKit.UIUserInterfaceLayoutDirection.UIUserInterfaceLayoutDirect import platform.UIKit.UIUserInterfaceStyle import platform.UIKit.UIView +@ObjCName("RedwoodUIView", exact = true) public open class RedwoodUIView : RedwoodView { private val valueRootView: RootUIStackView = RootUIStackView() diff --git a/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt b/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt index ebe06998f0..041cb230c3 100644 --- a/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt +++ b/samples/counter/ios-shared/src/commonMain/kotlin/com/example/redwood/counter/ios/CounterViewControllerDelegate.kt @@ -27,14 +27,14 @@ import kotlinx.coroutines.plus @Suppress("unused") // Called from Swift. class CounterViewControllerDelegate( - root: UIViewRoot, + redwoodUIView: RedwoodUIView, ) { private val scope = MainScope() + DisplayLinkClock init { val composition = RedwoodComposition( scope = scope, - view = RedwoodUIView(root), + view = redwoodUIView, widgetSystem = SchemaWidgetSystem( Schema = IosWidgetFactory, RedwoodLayout = UIViewRedwoodLayoutWidgetFactory(), diff --git a/samples/counter/ios-uikit/CounterApp/CounterViewController.swift b/samples/counter/ios-uikit/CounterApp/CounterViewController.swift index 63aed6ee43..4c788e42ed 100644 --- a/samples/counter/ios-uikit/CounterApp/CounterViewController.swift +++ b/samples/counter/ios-uikit/CounterApp/CounterViewController.swift @@ -6,18 +6,18 @@ class CounterViewController : UIViewController { private var delegate: CounterViewControllerDelegate! override func viewDidLoad() { - let root = UIViewRoot() + let redwoodUIView = RedwoodUIView() - let rootView = root.value + let rootView = redwoodUIView.value rootView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(root.value) + view.addSubview(rootView) rootView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true rootView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true rootView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true rootView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true - self.delegate = CounterViewControllerDelegate(root: root) + self.delegate = CounterViewControllerDelegate(redwoodUIView: redwoodUIView) } deinit { diff --git a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift index 04bd2dbde3..ceb9e4b775 100644 --- a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift +++ b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift @@ -38,15 +38,14 @@ class EmojiSearchViewController : UIViewController, EmojiSearchEventListener { let emojiSearchLauncher = EmojiSearchLauncher(nsurlSession: urlSession, hostApi: IosHostApi()) let treehouseApp = emojiSearchLauncher.createTreehouseApp(listener: self) let widgetSystem = EmojiSearchTreehouseWidgetSystem(treehouseApp: treehouseApp) - let treehouseView = TreehouseUIView( - widgetSystem: widgetSystem, - root: EmojiSearchUIViewRoot() + let treehouseView = EmojiSearchTreehouseUIView( + widgetSystem: widgetSystem ) let content = treehouseApp.createContent( source: EmojiSearchContent() ) ExposedKt.bindWhenReady(content: content, view: treehouseView) - view = treehouseView.root.value + view = treehouseView.value } func codeLoadFailed() { @@ -73,7 +72,14 @@ class EmojiSearchViewController : UIViewController, EmojiSearchEventListener { } } -class EmojiSearchUIViewRoot : UIViewRoot { +class EmojiSearchTreehouseUIView : TreehouseUIView { + + init( + widgetSystem: EmojiSearchTreehouseWidgetSystem + ) { + super.init(widgetSystem: widgetSystem) + } + override func contentState( loadCount: Int32, attached: Bool, From e686e6cab420e4dfd1e593d6ea3cbcf6a0cf839c Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Mon, 21 Oct 2024 11:46:29 -0400 Subject: [PATCH 3/5] Track another API change --- .../app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt b/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt index 683f9bb417..e2676f1d9c 100644 --- a/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt +++ b/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt @@ -30,5 +30,5 @@ class UIViewRedwoodViewTest( override fun redwoodView() = RedwoodUIView() override fun snapshotter(redwoodView: RedwoodUIView) = - UIViewSnapshotter.framed(callback, redwoodView.root.value) + UIViewSnapshotter.framed(callback, redwoodView.value) } From 55d2a67349d66b62d8205a0819e2241d71a03eb6 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Mon, 21 Oct 2024 13:07:08 -0400 Subject: [PATCH 4/5] apiDump --- .../redwood-treehouse-host-composeui.api | 20 +++---- .../jvm/redwood-treehouse-host-composeui.api | 20 +++---- .../redwood-treehouse-host-composeui.klib.api | 32 +++++----- .../api/android/redwood-treehouse-host.api | 13 ++-- .../api/redwood-treehouse-host.klib.api | 43 +++++++------- redwood-widget/api/android/redwood-widget.api | 35 ++++------- redwood-widget/api/jvm/redwood-widget.api | 11 ++-- redwood-widget/api/redwood-widget.klib.api | 59 ++++--------------- 8 files changed, 90 insertions(+), 143 deletions(-) diff --git a/redwood-treehouse-host-composeui/api/android/redwood-treehouse-host-composeui.api b/redwood-treehouse-host-composeui/api/android/redwood-treehouse-host-composeui.api index 54705d514f..c405f746a4 100644 --- a/redwood-treehouse-host-composeui/api/android/redwood-treehouse-host-composeui.api +++ b/redwood-treehouse-host-composeui/api/android/redwood-treehouse-host-composeui.api @@ -1,18 +1,16 @@ -public class app/cash/redwood/treehouse/composeui/ComposeUiRoot : app/cash/redwood/widget/RedwoodView$Root { +public class app/cash/redwood/treehouse/composeui/DynamicContent { public static final field $stable I public fun ()V - public fun Render (Landroidx/compose/runtime/Composer;I)V - public fun contentState (IZLjava/lang/Throwable;)V - public synthetic fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; - public fun getChildren ()Lapp/cash/redwood/widget/compose/ComposeWidgetChildren; - public fun getModifier ()Lapp/cash/redwood/Modifier; - public synthetic fun getValue ()Ljava/lang/Object; - public fun getValue ()Lkotlin/jvm/functions/Function2; - public fun restart (Lkotlin/jvm/functions/Function0;)V - public fun setModifier (Lapp/cash/redwood/Modifier;)V + public fun Render (Lapp/cash/redwood/widget/compose/ComposeWidgetChildren;Landroidx/compose/runtime/Composer;I)V + public fun contentState (Lkotlinx/coroutines/CoroutineScope;IZLjava/lang/Throwable;)V + public final fun getAttached ()Z + public final fun getLoadCount ()I + public final fun getRestart ()Lkotlin/jvm/functions/Function0; + public final fun getUncaughtException ()Ljava/lang/Throwable; + public final fun restart (Lkotlin/jvm/functions/Function0;)V } public final class app/cash/redwood/treehouse/composeui/TreehouseContentKt { - public static final fun TreehouseContent (Lapp/cash/redwood/treehouse/TreehouseApp;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Lapp/cash/redwood/treehouse/TreehouseContentSource;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun TreehouseContent (Lapp/cash/redwood/treehouse/TreehouseApp;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Lapp/cash/redwood/treehouse/TreehouseContentSource;Landroidx/compose/ui/Modifier;Lapp/cash/redwood/treehouse/composeui/DynamicContent;Landroidx/compose/runtime/Composer;II)V } diff --git a/redwood-treehouse-host-composeui/api/jvm/redwood-treehouse-host-composeui.api b/redwood-treehouse-host-composeui/api/jvm/redwood-treehouse-host-composeui.api index 54705d514f..c405f746a4 100644 --- a/redwood-treehouse-host-composeui/api/jvm/redwood-treehouse-host-composeui.api +++ b/redwood-treehouse-host-composeui/api/jvm/redwood-treehouse-host-composeui.api @@ -1,18 +1,16 @@ -public class app/cash/redwood/treehouse/composeui/ComposeUiRoot : app/cash/redwood/widget/RedwoodView$Root { +public class app/cash/redwood/treehouse/composeui/DynamicContent { public static final field $stable I public fun ()V - public fun Render (Landroidx/compose/runtime/Composer;I)V - public fun contentState (IZLjava/lang/Throwable;)V - public synthetic fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; - public fun getChildren ()Lapp/cash/redwood/widget/compose/ComposeWidgetChildren; - public fun getModifier ()Lapp/cash/redwood/Modifier; - public synthetic fun getValue ()Ljava/lang/Object; - public fun getValue ()Lkotlin/jvm/functions/Function2; - public fun restart (Lkotlin/jvm/functions/Function0;)V - public fun setModifier (Lapp/cash/redwood/Modifier;)V + public fun Render (Lapp/cash/redwood/widget/compose/ComposeWidgetChildren;Landroidx/compose/runtime/Composer;I)V + public fun contentState (Lkotlinx/coroutines/CoroutineScope;IZLjava/lang/Throwable;)V + public final fun getAttached ()Z + public final fun getLoadCount ()I + public final fun getRestart ()Lkotlin/jvm/functions/Function0; + public final fun getUncaughtException ()Ljava/lang/Throwable; + public final fun restart (Lkotlin/jvm/functions/Function0;)V } public final class app/cash/redwood/treehouse/composeui/TreehouseContentKt { - public static final fun TreehouseContent (Lapp/cash/redwood/treehouse/TreehouseApp;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Lapp/cash/redwood/treehouse/TreehouseContentSource;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun TreehouseContent (Lapp/cash/redwood/treehouse/TreehouseApp;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Lapp/cash/redwood/treehouse/TreehouseContentSource;Landroidx/compose/ui/Modifier;Lapp/cash/redwood/treehouse/composeui/DynamicContent;Landroidx/compose/runtime/Composer;II)V } diff --git a/redwood-treehouse-host-composeui/api/redwood-treehouse-host-composeui.klib.api b/redwood-treehouse-host-composeui/api/redwood-treehouse-host-composeui.klib.api index 879b63bc97..5bcdbaa379 100644 --- a/redwood-treehouse-host-composeui/api/redwood-treehouse-host-composeui.klib.api +++ b/redwood-treehouse-host-composeui/api/redwood-treehouse-host-composeui.klib.api @@ -6,24 +6,24 @@ // - Show declarations: true // Library unique name: -open class app.cash.redwood.treehouse.composeui/ComposeUiRoot : app.cash.redwood.widget/RedwoodView.Root> { // app.cash.redwood.treehouse.composeui/ComposeUiRoot|null[0] - constructor () // app.cash.redwood.treehouse.composeui/ComposeUiRoot.|(){}[0] +open class app.cash.redwood.treehouse.composeui/DynamicContent { // app.cash.redwood.treehouse.composeui/DynamicContent|null[0] + constructor () // app.cash.redwood.treehouse.composeui/DynamicContent.|(){}[0] - open val children // app.cash.redwood.treehouse.composeui/ComposeUiRoot.children|{}children[0] - open fun (): app.cash.redwood.widget.compose/ComposeWidgetChildren // app.cash.redwood.treehouse.composeui/ComposeUiRoot.children.|(){}[0] - open val value // app.cash.redwood.treehouse.composeui/ComposeUiRoot.value|{}value[0] - open fun (): kotlin/Function2 // app.cash.redwood.treehouse.composeui/ComposeUiRoot.value.|(){}[0] + final var attached // app.cash.redwood.treehouse.composeui/DynamicContent.attached|{}attached[0] + final fun (): kotlin/Boolean // app.cash.redwood.treehouse.composeui/DynamicContent.attached.|(){}[0] + final var loadCount // app.cash.redwood.treehouse.composeui/DynamicContent.loadCount|{}loadCount[0] + final fun (): kotlin/Int // app.cash.redwood.treehouse.composeui/DynamicContent.loadCount.|(){}[0] + final var restart // app.cash.redwood.treehouse.composeui/DynamicContent.restart|{}restart[0] + final fun (): kotlin/Function0? // app.cash.redwood.treehouse.composeui/DynamicContent.restart.|(){}[0] + final var uncaughtException // app.cash.redwood.treehouse.composeui/DynamicContent.uncaughtException|{}uncaughtException[0] + final fun (): kotlin/Throwable? // app.cash.redwood.treehouse.composeui/DynamicContent.uncaughtException.|(){}[0] - open var modifier // app.cash.redwood.treehouse.composeui/ComposeUiRoot.modifier|{}modifier[0] - open fun (): app.cash.redwood/Modifier // app.cash.redwood.treehouse.composeui/ComposeUiRoot.modifier.|(){}[0] - open fun (app.cash.redwood/Modifier) // app.cash.redwood.treehouse.composeui/ComposeUiRoot.modifier.|(app.cash.redwood.Modifier){}[0] - - open fun Render(androidx.compose.runtime/Composer?, kotlin/Int) // app.cash.redwood.treehouse.composeui/ComposeUiRoot.Render|Render(androidx.compose.runtime.Composer?;kotlin.Int){}[0] - open fun contentState(kotlin/Int, kotlin/Boolean, kotlin/Throwable?) // app.cash.redwood.treehouse.composeui/ComposeUiRoot.contentState|contentState(kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] - open fun restart(kotlin/Function0?) // app.cash.redwood.treehouse.composeui/ComposeUiRoot.restart|restart(kotlin.Function0?){}[0] + final fun restart(kotlin/Function0?) // app.cash.redwood.treehouse.composeui/DynamicContent.restart|restart(kotlin.Function0?){}[0] + open fun Render(app.cash.redwood.widget.compose/ComposeWidgetChildren, androidx.compose.runtime/Composer?, kotlin/Int) // app.cash.redwood.treehouse.composeui/DynamicContent.Render|Render(app.cash.redwood.widget.compose.ComposeWidgetChildren;androidx.compose.runtime.Composer?;kotlin.Int){}[0] + open fun contentState(kotlinx.coroutines/CoroutineScope, kotlin/Int, kotlin/Boolean, kotlin/Throwable?) // app.cash.redwood.treehouse.composeui/DynamicContent.contentState|contentState(kotlinx.coroutines.CoroutineScope;kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] } -final val app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_ComposeUiRoot$stableprop // app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_ComposeUiRoot$stableprop|#static{}app_cash_redwood_treehouse_composeui_ComposeUiRoot$stableprop[0] +final val app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_DynamicContent$stableprop // app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_DynamicContent$stableprop|#static{}app_cash_redwood_treehouse_composeui_DynamicContent$stableprop[0] -final fun <#A: app.cash.redwood.treehouse/AppService> app.cash.redwood.treehouse.composeui/TreehouseContent(app.cash.redwood.treehouse/TreehouseApp<#A>, app.cash.redwood.treehouse/TreehouseView.WidgetSystem>, app.cash.redwood.treehouse/TreehouseContentSource<#A>, androidx.compose.ui/Modifier?, kotlin/Function1>>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // app.cash.redwood.treehouse.composeui/TreehouseContent|TreehouseContent(app.cash.redwood.treehouse.TreehouseApp<0:0>;app.cash.redwood.treehouse.TreehouseView.WidgetSystem>;app.cash.redwood.treehouse.TreehouseContentSource<0:0>;androidx.compose.ui.Modifier?;kotlin.Function1>>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] -final fun app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_ComposeUiRoot$stableprop_getter(): kotlin/Int // app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_ComposeUiRoot$stableprop_getter|app_cash_redwood_treehouse_composeui_ComposeUiRoot$stableprop_getter(){}[0] +final fun <#A: app.cash.redwood.treehouse/AppService> app.cash.redwood.treehouse.composeui/TreehouseContent(app.cash.redwood.treehouse/TreehouseApp<#A>, app.cash.redwood.treehouse/TreehouseView.WidgetSystem>, app.cash.redwood.treehouse/TreehouseContentSource<#A>, androidx.compose.ui/Modifier?, app.cash.redwood.treehouse.composeui/DynamicContent?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // app.cash.redwood.treehouse.composeui/TreehouseContent|TreehouseContent(app.cash.redwood.treehouse.TreehouseApp<0:0>;app.cash.redwood.treehouse.TreehouseView.WidgetSystem>;app.cash.redwood.treehouse.TreehouseContentSource<0:0>;androidx.compose.ui.Modifier?;app.cash.redwood.treehouse.composeui.DynamicContent?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] +final fun app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_DynamicContent$stableprop_getter(): kotlin/Int // app.cash.redwood.treehouse.composeui/app_cash_redwood_treehouse_composeui_DynamicContent$stableprop_getter|app_cash_redwood_treehouse_composeui_DynamicContent$stableprop_getter(){}[0] diff --git a/redwood-treehouse-host/api/android/redwood-treehouse-host.api b/redwood-treehouse-host/api/android/redwood-treehouse-host.api index 4baae7fb67..3358ac087f 100644 --- a/redwood-treehouse-host/api/android/redwood-treehouse-host.api +++ b/redwood-treehouse-host/api/android/redwood-treehouse-host.api @@ -116,14 +116,17 @@ public abstract interface class app/cash/redwood/treehouse/TreehouseDispatchers public abstract fun getZipline ()Lkotlinx/coroutines/CoroutineDispatcher; } -public final class app/cash/redwood/treehouse/TreehouseLayout : app/cash/redwood/widget/RedwoodLayout, app/cash/redwood/treehouse/TreehouseView { - public fun (Landroid/content/Context;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Landroidx/activity/OnBackPressedDispatcher;Lapp/cash/redwood/widget/RedwoodView$Root;)V - public synthetic fun (Landroid/content/Context;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Landroidx/activity/OnBackPressedDispatcher;Lapp/cash/redwood/widget/RedwoodView$Root;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getReadyForContent ()Z +public class app/cash/redwood/treehouse/TreehouseLayout : app/cash/redwood/widget/RedwoodLayout, app/cash/redwood/treehouse/TreehouseView { + public fun (Landroid/content/Context;Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem;Landroidx/activity/OnBackPressedDispatcher;)V + public final fun getReadyForContent ()Z public fun getReadyForContentChangeListener ()Lapp/cash/redwood/treehouse/TreehouseView$ReadyForContentChangeListener; public fun getSaveCallback ()Lapp/cash/redwood/treehouse/TreehouseView$SaveCallback; - public fun getStateSnapshotId-kwWZ-Q0 ()Ljava/lang/String; + public final fun getStateSnapshotId-kwWZ-Q0 ()Ljava/lang/String; public fun getWidgetSystem ()Lapp/cash/redwood/treehouse/TreehouseView$WidgetSystem; + protected fun onAttachedToWindow ()V + protected fun onDetachedFromWindow ()V + protected fun onRestoreInstanceState (Landroid/os/Parcelable;)V + protected fun onSaveInstanceState ()Landroid/os/Parcelable; public fun setReadyForContentChangeListener (Lapp/cash/redwood/treehouse/TreehouseView$ReadyForContentChangeListener;)V public fun setSaveCallback (Lapp/cash/redwood/treehouse/TreehouseView$SaveCallback;)V } diff --git a/redwood-treehouse-host/api/redwood-treehouse-host.klib.api b/redwood-treehouse-host/api/redwood-treehouse-host.klib.api index b75bb9232b..bf26a36069 100644 --- a/redwood-treehouse-host/api/redwood-treehouse-host.klib.api +++ b/redwood-treehouse-host/api/redwood-treehouse-host.klib.api @@ -113,28 +113,6 @@ final class app.cash.redwood.treehouse/MemoryStateStore : app.cash.redwood.treeh final suspend fun put(kotlin/String, app.cash.redwood.treehouse/StateSnapshot) // app.cash.redwood.treehouse/MemoryStateStore.put|put(kotlin.String;app.cash.redwood.treehouse.StateSnapshot){}[0] } -final class app.cash.redwood.treehouse/TreehouseUIView : app.cash.redwood.treehouse/TreehouseView, app.cash.redwood.widget/RedwoodUIView { // app.cash.redwood.treehouse/TreehouseUIView|null[0] - constructor (app.cash.redwood.treehouse/TreehouseView.WidgetSystem) // app.cash.redwood.treehouse/TreehouseUIView.|(app.cash.redwood.treehouse.TreehouseView.WidgetSystem){}[0] - constructor (app.cash.redwood.treehouse/TreehouseView.WidgetSystem, app.cash.redwood.widget/UIViewRoot) // app.cash.redwood.treehouse/TreehouseUIView.|(app.cash.redwood.treehouse.TreehouseView.WidgetSystem;app.cash.redwood.widget.UIViewRoot){}[0] - - final val readyForContent // app.cash.redwood.treehouse/TreehouseUIView.readyForContent|{}readyForContent[0] - final fun (): kotlin/Boolean // app.cash.redwood.treehouse/TreehouseUIView.readyForContent.|(){}[0] - final val widgetSystem // app.cash.redwood.treehouse/TreehouseUIView.widgetSystem|{}widgetSystem[0] - final fun (): app.cash.redwood.treehouse/TreehouseView.WidgetSystem // app.cash.redwood.treehouse/TreehouseUIView.widgetSystem.|(){}[0] - - final var readyForContentChangeListener // app.cash.redwood.treehouse/TreehouseUIView.readyForContentChangeListener|{}readyForContentChangeListener[0] - final fun (): app.cash.redwood.treehouse/TreehouseView.ReadyForContentChangeListener? // app.cash.redwood.treehouse/TreehouseUIView.readyForContentChangeListener.|(){}[0] - final fun (app.cash.redwood.treehouse/TreehouseView.ReadyForContentChangeListener?) // app.cash.redwood.treehouse/TreehouseUIView.readyForContentChangeListener.|(app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener?){}[0] - final var saveCallback // app.cash.redwood.treehouse/TreehouseUIView.saveCallback|{}saveCallback[0] - final fun (): app.cash.redwood.treehouse/TreehouseView.SaveCallback? // app.cash.redwood.treehouse/TreehouseUIView.saveCallback.|(){}[0] - final fun (app.cash.redwood.treehouse/TreehouseView.SaveCallback?) // app.cash.redwood.treehouse/TreehouseUIView.saveCallback.|(app.cash.redwood.treehouse.TreehouseView.SaveCallback?){}[0] - final var stateSnapshotId // app.cash.redwood.treehouse/TreehouseUIView.stateSnapshotId|{}stateSnapshotId[0] - final fun (): app.cash.redwood.treehouse/StateSnapshot.Id // app.cash.redwood.treehouse/TreehouseUIView.stateSnapshotId.|(){}[0] - final fun (app.cash.redwood.treehouse/StateSnapshot.Id) // app.cash.redwood.treehouse/TreehouseUIView.stateSnapshotId.|(app.cash.redwood.treehouse.StateSnapshot.Id){}[0] - - final fun superviewChanged() // app.cash.redwood.treehouse/TreehouseUIView.superviewChanged|superviewChanged(){}[0] -} - open class app.cash.redwood.treehouse/EventListener { // app.cash.redwood.treehouse/EventListener|null[0] constructor () // app.cash.redwood.treehouse/EventListener.|(){}[0] @@ -180,6 +158,27 @@ open class app.cash.redwood.treehouse/EventListener { // app.cash.redwood.treeho } } +open class app.cash.redwood.treehouse/TreehouseUIView : app.cash.redwood.treehouse/TreehouseView, app.cash.redwood.widget/RedwoodUIView { // app.cash.redwood.treehouse/TreehouseUIView|null[0] + constructor (app.cash.redwood.treehouse/TreehouseView.WidgetSystem) // app.cash.redwood.treehouse/TreehouseUIView.|(app.cash.redwood.treehouse.TreehouseView.WidgetSystem){}[0] + + open val readyForContent // app.cash.redwood.treehouse/TreehouseUIView.readyForContent|{}readyForContent[0] + open fun (): kotlin/Boolean // app.cash.redwood.treehouse/TreehouseUIView.readyForContent.|(){}[0] + open val widgetSystem // app.cash.redwood.treehouse/TreehouseUIView.widgetSystem|{}widgetSystem[0] + open fun (): app.cash.redwood.treehouse/TreehouseView.WidgetSystem // app.cash.redwood.treehouse/TreehouseUIView.widgetSystem.|(){}[0] + + open var readyForContentChangeListener // app.cash.redwood.treehouse/TreehouseUIView.readyForContentChangeListener|{}readyForContentChangeListener[0] + open fun (): app.cash.redwood.treehouse/TreehouseView.ReadyForContentChangeListener? // app.cash.redwood.treehouse/TreehouseUIView.readyForContentChangeListener.|(){}[0] + open fun (app.cash.redwood.treehouse/TreehouseView.ReadyForContentChangeListener?) // app.cash.redwood.treehouse/TreehouseUIView.readyForContentChangeListener.|(app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener?){}[0] + open var saveCallback // app.cash.redwood.treehouse/TreehouseUIView.saveCallback|{}saveCallback[0] + open fun (): app.cash.redwood.treehouse/TreehouseView.SaveCallback? // app.cash.redwood.treehouse/TreehouseUIView.saveCallback.|(){}[0] + open fun (app.cash.redwood.treehouse/TreehouseView.SaveCallback?) // app.cash.redwood.treehouse/TreehouseUIView.saveCallback.|(app.cash.redwood.treehouse.TreehouseView.SaveCallback?){}[0] + open var stateSnapshotId // app.cash.redwood.treehouse/TreehouseUIView.stateSnapshotId|{}stateSnapshotId[0] + open fun (): app.cash.redwood.treehouse/StateSnapshot.Id // app.cash.redwood.treehouse/TreehouseUIView.stateSnapshotId.|(){}[0] + open fun (app.cash.redwood.treehouse/StateSnapshot.Id) // app.cash.redwood.treehouse/TreehouseUIView.stateSnapshotId.|(app.cash.redwood.treehouse.StateSnapshot.Id){}[0] + + open fun superviewChanged() // app.cash.redwood.treehouse/TreehouseUIView.superviewChanged|superviewChanged(){}[0] +} + final fun <#A: app.cash.redwood.treehouse/AppService, #B: kotlin/Any> (app.cash.redwood.treehouse/TreehouseContentSource<#A>).app.cash.redwood.treehouse/bindWhenReady(app.cash.redwood.treehouse/TreehouseView<#B>, app.cash.redwood.treehouse/TreehouseApp<#A>): okio/Closeable // app.cash.redwood.treehouse/bindWhenReady|bindWhenReady@app.cash.redwood.treehouse.TreehouseContentSource<0:0>(app.cash.redwood.treehouse.TreehouseView<0:1>;app.cash.redwood.treehouse.TreehouseApp<0:0>){0§;1§}[0] final fun <#A: kotlin/Any> (app.cash.redwood.treehouse/Content).app.cash.redwood.treehouse/bindWhenReady(app.cash.redwood.treehouse/TreehouseView<#A>): okio/Closeable // app.cash.redwood.treehouse/bindWhenReady|bindWhenReady@app.cash.redwood.treehouse.Content(app.cash.redwood.treehouse.TreehouseView<0:0>){0§}[0] final fun app.cash.redwood.treehouse/TreehouseAppFactory(app.cash.zipline.loader/ZiplineHttpClient, app.cash.zipline.loader/ManifestVerifier, okio/FileSystem? = ..., okio/Path? = ..., kotlin/String = ..., kotlin/Long = ..., kotlin/Int = ..., app.cash.zipline.loader/LoaderEventListener = ..., app.cash.redwood.treehouse/StateStore = ..., app.cash.redwood.leaks/LeakDetector = ...): app.cash.redwood.treehouse/TreehouseApp.Factory // app.cash.redwood.treehouse/TreehouseAppFactory|TreehouseAppFactory(app.cash.zipline.loader.ZiplineHttpClient;app.cash.zipline.loader.ManifestVerifier;okio.FileSystem?;okio.Path?;kotlin.String;kotlin.Long;kotlin.Int;app.cash.zipline.loader.LoaderEventListener;app.cash.redwood.treehouse.StateStore;app.cash.redwood.leaks.LeakDetector){}[0] diff --git a/redwood-widget/api/android/redwood-widget.api b/redwood-widget/api/android/redwood-widget.api index a579ccca78..bd7f08b74c 100644 --- a/redwood-widget/api/android/redwood-widget.api +++ b/redwood-widget/api/android/redwood-widget.api @@ -48,29 +48,29 @@ public final class app/cash/redwood/widget/MutableListChildren : app/cash/redwoo public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; } -public class app/cash/redwood/widget/RedwoodLayout : android/widget/FrameLayout, app/cash/redwood/widget/RedwoodView { - public fun (Landroid/content/Context;Landroidx/activity/OnBackPressedDispatcher;Lapp/cash/redwood/widget/RedwoodView$Root;)V - public synthetic fun (Landroid/content/Context;Landroidx/activity/OnBackPressedDispatcher;Lapp/cash/redwood/widget/RedwoodView$Root;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public class app/cash/redwood/widget/RedwoodLayout : android/view/ViewGroup, app/cash/redwood/widget/RedwoodView { + public fun (Landroid/content/Context;Landroidx/activity/OnBackPressedDispatcher;)V + public fun contentState (IZLjava/lang/Throwable;)V + public final fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; public fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher; - public final fun getRoot ()Lapp/cash/redwood/widget/RedwoodView$Root; public fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry; public fun getUiConfiguration ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getValue ()Landroid/view/View; + public synthetic fun getValue ()Ljava/lang/Object; protected fun onConfigurationChanged (Landroid/content/res/Configuration;)V protected fun onLayout (ZIIII)V protected fun onMeasure (II)V + public fun restart (Lkotlin/jvm/functions/Function0;)V } public abstract interface class app/cash/redwood/widget/RedwoodView { + public abstract fun contentState (IZLjava/lang/Throwable;)V + public static synthetic fun contentState$default (Lapp/cash/redwood/widget/RedwoodView;IZLjava/lang/Throwable;ILjava/lang/Object;)V + public abstract fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; public abstract fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher; - public abstract fun getRoot ()Lapp/cash/redwood/widget/RedwoodView$Root; public abstract fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry; public abstract fun getUiConfiguration ()Lkotlinx/coroutines/flow/StateFlow; -} - -public abstract interface class app/cash/redwood/widget/RedwoodView$Root : app/cash/redwood/widget/Widget { - public abstract fun contentState (IZLjava/lang/Throwable;)V - public static synthetic fun contentState$default (Lapp/cash/redwood/widget/RedwoodView$Root;IZLjava/lang/Throwable;ILjava/lang/Object;)V - public abstract fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; + public abstract fun getValue ()Ljava/lang/Object; public abstract fun restart (Lkotlin/jvm/functions/Function0;)V } @@ -92,19 +92,6 @@ public final class app/cash/redwood/widget/ViewGroupChildren : app/cash/redwood/ public fun remove (II)V } -public class app/cash/redwood/widget/ViewRoot : android/view/ViewGroup, app/cash/redwood/widget/RedwoodView$Root { - public fun (Landroid/content/Context;)V - public fun contentState (IZLjava/lang/Throwable;)V - public final fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; - public final fun getModifier ()Lapp/cash/redwood/Modifier; - public final fun getValue ()Landroid/view/View; - public synthetic fun getValue ()Ljava/lang/Object; - protected fun onLayout (ZIIII)V - protected fun onMeasure (II)V - public fun restart (Lkotlin/jvm/functions/Function0;)V - public final fun setModifier (Lapp/cash/redwood/Modifier;)V -} - public abstract interface class app/cash/redwood/widget/Widget { public abstract fun getModifier ()Lapp/cash/redwood/Modifier; public abstract fun getValue ()Ljava/lang/Object; diff --git a/redwood-widget/api/jvm/redwood-widget.api b/redwood-widget/api/jvm/redwood-widget.api index e86d3d77e8..517c46dfed 100644 --- a/redwood-widget/api/jvm/redwood-widget.api +++ b/redwood-widget/api/jvm/redwood-widget.api @@ -49,16 +49,13 @@ public final class app/cash/redwood/widget/MutableListChildren : app/cash/redwoo } public abstract interface class app/cash/redwood/widget/RedwoodView { + public abstract fun contentState (IZLjava/lang/Throwable;)V + public static synthetic fun contentState$default (Lapp/cash/redwood/widget/RedwoodView;IZLjava/lang/Throwable;ILjava/lang/Object;)V + public abstract fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; public abstract fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher; - public abstract fun getRoot ()Lapp/cash/redwood/widget/RedwoodView$Root; public abstract fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry; public abstract fun getUiConfiguration ()Lkotlinx/coroutines/flow/StateFlow; -} - -public abstract interface class app/cash/redwood/widget/RedwoodView$Root : app/cash/redwood/widget/Widget { - public abstract fun contentState (IZLjava/lang/Throwable;)V - public static synthetic fun contentState$default (Lapp/cash/redwood/widget/RedwoodView$Root;IZLjava/lang/Throwable;ILjava/lang/Object;)V - public abstract fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; + public abstract fun getValue ()Ljava/lang/Object; public abstract fun restart (Lkotlin/jvm/functions/Function0;)V } diff --git a/redwood-widget/api/redwood-widget.klib.api b/redwood-widget/api/redwood-widget.klib.api index 622897e333..ea8ff07a2d 100644 --- a/redwood-widget/api/redwood-widget.klib.api +++ b/redwood-widget/api/redwood-widget.klib.api @@ -8,22 +8,19 @@ // Library unique name: abstract interface <#A: kotlin/Any> app.cash.redwood.widget/RedwoodView { // app.cash.redwood.widget/RedwoodView|null[0] + abstract val children // app.cash.redwood.widget/RedwoodView.children|{}children[0] + abstract fun (): app.cash.redwood.widget/Widget.Children<#A> // app.cash.redwood.widget/RedwoodView.children.|(){}[0] abstract val onBackPressedDispatcher // app.cash.redwood.widget/RedwoodView.onBackPressedDispatcher|{}onBackPressedDispatcher[0] abstract fun (): app.cash.redwood.ui/OnBackPressedDispatcher // app.cash.redwood.widget/RedwoodView.onBackPressedDispatcher.|(){}[0] - abstract val root // app.cash.redwood.widget/RedwoodView.root|{}root[0] - abstract fun (): app.cash.redwood.widget/RedwoodView.Root<#A> // app.cash.redwood.widget/RedwoodView.root.|(){}[0] abstract val savedStateRegistry // app.cash.redwood.widget/RedwoodView.savedStateRegistry|{}savedStateRegistry[0] abstract fun (): app.cash.redwood.widget/SavedStateRegistry? // app.cash.redwood.widget/RedwoodView.savedStateRegistry.|(){}[0] abstract val uiConfiguration // app.cash.redwood.widget/RedwoodView.uiConfiguration|{}uiConfiguration[0] abstract fun (): kotlinx.coroutines.flow/StateFlow // app.cash.redwood.widget/RedwoodView.uiConfiguration.|(){}[0] + abstract val value // app.cash.redwood.widget/RedwoodView.value|{}value[0] + abstract fun (): #A // app.cash.redwood.widget/RedwoodView.value.|(){}[0] - abstract interface <#A1: kotlin/Any> Root : app.cash.redwood.widget/Widget<#A1> { // app.cash.redwood.widget/RedwoodView.Root|null[0] - abstract val children // app.cash.redwood.widget/RedwoodView.Root.children|{}children[0] - abstract fun (): app.cash.redwood.widget/Widget.Children<#A1> // app.cash.redwood.widget/RedwoodView.Root.children.|(){}[0] - - abstract fun contentState(kotlin/Int, kotlin/Boolean, kotlin/Throwable? = ...) // app.cash.redwood.widget/RedwoodView.Root.contentState|contentState(kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] - abstract fun restart(kotlin/Function0?) // app.cash.redwood.widget/RedwoodView.Root.restart|restart(kotlin.Function0?){}[0] - } + abstract fun contentState(kotlin/Int, kotlin/Boolean, kotlin/Throwable? = ...) // app.cash.redwood.widget/RedwoodView.contentState|contentState(kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] + abstract fun restart(kotlin/Function0?) // app.cash.redwood.widget/RedwoodView.restart|restart(kotlin.Function0?){}[0] } abstract interface <#A: kotlin/Any> app.cash.redwood.widget/Widget { // app.cash.redwood.widget/Widget|null[0] @@ -130,38 +127,23 @@ final class app.cash.redwood.widget/UIViewChildren : app.cash.redwood.widget/Wid // Targets: [ios] open class app.cash.redwood.widget/RedwoodUIView : app.cash.redwood.widget/RedwoodView { // app.cash.redwood.widget/RedwoodUIView|null[0] constructor () // app.cash.redwood.widget/RedwoodUIView.|(){}[0] - constructor (app.cash.redwood.widget/UIViewRoot) // app.cash.redwood.widget/RedwoodUIView.|(app.cash.redwood.widget.UIViewRoot){}[0] - final val root // app.cash.redwood.widget/RedwoodUIView.root|{}root[0] - final fun (): app.cash.redwood.widget/UIViewRoot // app.cash.redwood.widget/RedwoodUIView.root.|(){}[0] + open val children // app.cash.redwood.widget/RedwoodUIView.children|{}children[0] + open fun (): app.cash.redwood.widget/Widget.Children // app.cash.redwood.widget/RedwoodUIView.children.|(){}[0] open val onBackPressedDispatcher // app.cash.redwood.widget/RedwoodUIView.onBackPressedDispatcher|{}onBackPressedDispatcher[0] open fun (): app.cash.redwood.ui/OnBackPressedDispatcher // app.cash.redwood.widget/RedwoodUIView.onBackPressedDispatcher.|(){}[0] open val savedStateRegistry // app.cash.redwood.widget/RedwoodUIView.savedStateRegistry|{}savedStateRegistry[0] open fun (): app.cash.redwood.widget/SavedStateRegistry? // app.cash.redwood.widget/RedwoodUIView.savedStateRegistry.|(){}[0] open val uiConfiguration // app.cash.redwood.widget/RedwoodUIView.uiConfiguration|{}uiConfiguration[0] open fun (): kotlinx.coroutines.flow/StateFlow // app.cash.redwood.widget/RedwoodUIView.uiConfiguration.|(){}[0] + open val value // app.cash.redwood.widget/RedwoodUIView.value|{}value[0] + open fun (): platform.UIKit/UIView // app.cash.redwood.widget/RedwoodUIView.value.|(){}[0] - final fun updateUiConfiguration() // app.cash.redwood.widget/RedwoodUIView.updateUiConfiguration|updateUiConfiguration(){}[0] + open fun contentState(kotlin/Int, kotlin/Boolean, kotlin/Throwable?) // app.cash.redwood.widget/RedwoodUIView.contentState|contentState(kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] + open fun restart(kotlin/Function0?) // app.cash.redwood.widget/RedwoodUIView.restart|restart(kotlin.Function0?){}[0] open fun superviewChanged() // app.cash.redwood.widget/RedwoodUIView.superviewChanged|superviewChanged(){}[0] } -// Targets: [ios] -open class app.cash.redwood.widget/UIViewRoot : app.cash.redwood.widget/RedwoodView.Root { // app.cash.redwood.widget/UIViewRoot|null[0] - constructor () // app.cash.redwood.widget/UIViewRoot.|(){}[0] - - open val children // app.cash.redwood.widget/UIViewRoot.children|{}children[0] - open fun (): app.cash.redwood.widget/Widget.Children // app.cash.redwood.widget/UIViewRoot.children.|(){}[0] - open val value // app.cash.redwood.widget/UIViewRoot.value|{}value[0] - open fun (): platform.UIKit/UIView // app.cash.redwood.widget/UIViewRoot.value.|(){}[0] - - open var modifier // app.cash.redwood.widget/UIViewRoot.modifier|{}modifier[0] - open fun (): app.cash.redwood/Modifier // app.cash.redwood.widget/UIViewRoot.modifier.|(){}[0] - open fun (app.cash.redwood/Modifier) // app.cash.redwood.widget/UIViewRoot.modifier.|(app.cash.redwood.Modifier){}[0] - - open fun contentState(kotlin/Int, kotlin/Boolean, kotlin/Throwable?) // app.cash.redwood.widget/UIViewRoot.contentState|contentState(kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] - open fun restart(kotlin/Function0?) // app.cash.redwood.widget/UIViewRoot.restart|restart(kotlin.Function0?){}[0] -} - // Targets: [js] final class app.cash.redwood.widget/HTMLElementChildren : app.cash.redwood.widget/Widget.Children { // app.cash.redwood.widget/HTMLElementChildren|null[0] constructor (org.w3c.dom/HTMLElement) // app.cash.redwood.widget/HTMLElementChildren.|(org.w3c.dom.HTMLElement){}[0] @@ -176,22 +158,5 @@ final class app.cash.redwood.widget/HTMLElementChildren : app.cash.redwood.widge final fun remove(kotlin/Int, kotlin/Int) // app.cash.redwood.widget/HTMLElementChildren.remove|remove(kotlin.Int;kotlin.Int){}[0] } -// Targets: [js] -final class app.cash.redwood.widget/HTMLRoot : app.cash.redwood.widget/RedwoodView.Root { // app.cash.redwood.widget/HTMLRoot|null[0] - constructor (org.w3c.dom/HTMLElement) // app.cash.redwood.widget/HTMLRoot.|(org.w3c.dom.HTMLElement){}[0] - - final val children // app.cash.redwood.widget/HTMLRoot.children|{}children[0] - final fun (): app.cash.redwood.widget/Widget.Children // app.cash.redwood.widget/HTMLRoot.children.|(){}[0] - final val value // app.cash.redwood.widget/HTMLRoot.value|{}value[0] - final fun (): org.w3c.dom/HTMLElement // app.cash.redwood.widget/HTMLRoot.value.|(){}[0] - - final var modifier // app.cash.redwood.widget/HTMLRoot.modifier|{}modifier[0] - final fun (): app.cash.redwood/Modifier // app.cash.redwood.widget/HTMLRoot.modifier.|(){}[0] - final fun (app.cash.redwood/Modifier) // app.cash.redwood.widget/HTMLRoot.modifier.|(app.cash.redwood.Modifier){}[0] - - final fun contentState(kotlin/Int, kotlin/Boolean, kotlin/Throwable?) // app.cash.redwood.widget/HTMLRoot.contentState|contentState(kotlin.Int;kotlin.Boolean;kotlin.Throwable?){}[0] - final fun restart(kotlin/Function0?) // app.cash.redwood.widget/HTMLRoot.restart|restart(kotlin.Function0?){}[0] -} - // Targets: [js] final fun (org.w3c.dom/HTMLElement).app.cash.redwood.widget/asRedwoodView(): app.cash.redwood.widget/RedwoodView // app.cash.redwood.widget/asRedwoodView|asRedwoodView@org.w3c.dom.HTMLElement(){}[0] From 55e93369c1e6010f193451018ebe525a326b0d4d Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Mon, 21 Oct 2024 14:28:14 -0400 Subject: [PATCH 5/5] Track a API change --- test-app/ios-uikit/TestApp/TestAppViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-app/ios-uikit/TestApp/TestAppViewController.swift b/test-app/ios-uikit/TestApp/TestAppViewController.swift index 84e2a0bff9..4db0de4cc9 100644 --- a/test-app/ios-uikit/TestApp/TestAppViewController.swift +++ b/test-app/ios-uikit/TestApp/TestAppViewController.swift @@ -40,7 +40,7 @@ class TestAppViewController : UIViewController { ) ExposedKt.bindWhenReady(content: content, view: treehouseView) - let tv = treehouseView.root.value + let tv = treehouseView.value tv.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(tv)