From fcc1b2d6c7f0d1755378bcae19121c589d192c9d Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Fri, 25 Oct 2024 12:14:45 -0400 Subject: [PATCH] Migrate Compose UI Row+Column away from subtype When widgets switch to abstract classes, this will no longer be allowed. --- .../api/redwood-layout-composeui.klib.api | 4 +++ .../composeui/ComposeUiFlexContainerTest.kt | 36 +++++++++++++------ .../composeui/ComposeUiFlexContainer.kt | 35 ++++++++++++++++++ .../ComposeUiRedwoodLayoutWidgetFactory.kt | 5 ++- .../layout/AbstractFlexContainerTest.kt | 34 +++++++++++++++++- .../layout/uiview/UIViewFlexContainerTest.kt | 2 +- .../layout/view/ViewFlexContainerTest.kt | 2 +- 7 files changed, 102 insertions(+), 16 deletions(-) diff --git a/redwood-layout-composeui/api/redwood-layout-composeui.klib.api b/redwood-layout-composeui/api/redwood-layout-composeui.klib.api index fc79d342b8..0b4e9fe349 100644 --- a/redwood-layout-composeui/api/redwood-layout-composeui.klib.api +++ b/redwood-layout-composeui/api/redwood-layout-composeui.klib.api @@ -30,8 +30,10 @@ final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_Bo final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_BoxMeasurePolicy$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_BoxMeasurePolicy$stableprop|#static{}app_cash_redwood_layout_composeui_BoxMeasurePolicy$stableprop[0] final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeMeasureCallback$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeMeasureCallback$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeMeasureCallback$stableprop[0] final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiBox$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiBox$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeUiBox$stableprop[0] +final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiColumn$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiColumn$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeUiColumn$stableprop[0] final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiFlexContainer$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiFlexContainer$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeUiFlexContainer$stableprop[0] final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRedwoodLayoutWidgetFactory$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRedwoodLayoutWidgetFactory$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeUiRedwoodLayoutWidgetFactory$stableprop[0] +final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRow$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRow$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeUiRow$stableprop[0] final val app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiSpacer$stableprop // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiSpacer$stableprop|#static{}app_cash_redwood_layout_composeui_ComposeUiSpacer$stableprop[0] final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_BoxChildLayoutInfo$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_BoxChildLayoutInfo$stableprop_getter|app_cash_redwood_layout_composeui_BoxChildLayoutInfo$stableprop_getter(){}[0] @@ -39,6 +41,8 @@ final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_Bo final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_BoxMeasurePolicy$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_BoxMeasurePolicy$stableprop_getter|app_cash_redwood_layout_composeui_BoxMeasurePolicy$stableprop_getter(){}[0] final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeMeasureCallback$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeMeasureCallback$stableprop_getter|app_cash_redwood_layout_composeui_ComposeMeasureCallback$stableprop_getter(){}[0] final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiBox$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiBox$stableprop_getter|app_cash_redwood_layout_composeui_ComposeUiBox$stableprop_getter(){}[0] +final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiColumn$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiColumn$stableprop_getter|app_cash_redwood_layout_composeui_ComposeUiColumn$stableprop_getter(){}[0] final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiFlexContainer$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiFlexContainer$stableprop_getter|app_cash_redwood_layout_composeui_ComposeUiFlexContainer$stableprop_getter(){}[0] final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRedwoodLayoutWidgetFactory$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRedwoodLayoutWidgetFactory$stableprop_getter|app_cash_redwood_layout_composeui_ComposeUiRedwoodLayoutWidgetFactory$stableprop_getter(){}[0] +final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRow$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiRow$stableprop_getter|app_cash_redwood_layout_composeui_ComposeUiRow$stableprop_getter(){}[0] final fun app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiSpacer$stableprop_getter(): kotlin/Int // app.cash.redwood.layout.composeui/app_cash_redwood_layout_composeui_ComposeUiSpacer$stableprop_getter|app_cash_redwood_layout_composeui_ComposeUiSpacer$stableprop_getter(){}[0] diff --git a/redwood-layout-composeui/src/androidUnitTest/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainerTest.kt b/redwood-layout-composeui/src/androidUnitTest/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainerTest.kt index 4872ef4a00..7f643ba239 100644 --- a/redwood-layout-composeui/src/androidUnitTest/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainerTest.kt +++ b/redwood-layout-composeui/src/androidUnitTest/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainerTest.kt @@ -24,11 +24,14 @@ import app.cash.paparazzi.DeviceConfig import app.cash.paparazzi.Paparazzi import app.cash.redwood.layout.AbstractFlexContainerTest import app.cash.redwood.layout.TestFlexContainer +import app.cash.redwood.layout.api.Constraint +import app.cash.redwood.layout.api.Overflow +import app.cash.redwood.layout.widget.Column +import app.cash.redwood.layout.widget.Row import app.cash.redwood.layout.widget.Spacer import app.cash.redwood.snapshot.testing.ComposeSnapshotter import app.cash.redwood.snapshot.testing.ComposeUiTestWidgetFactory import app.cash.redwood.ui.Px -import app.cash.redwood.widget.compose.ComposeWidgetChildren import app.cash.redwood.yoga.FlexDirection import com.android.resources.LayoutDirection import kotlinx.coroutines.runBlocking @@ -51,18 +54,26 @@ class ComposeUiFlexContainerTest( override fun flexContainer( direction: FlexDirection, backgroundColor: Int, - ): ComposeTestFlexContainer { + ): TestFlexContainer<@Composable () -> Unit> { return ComposeTestFlexContainer(direction, backgroundColor) - .apply { applyDefaults() } + .apply { (this as TestFlexContainer<*>).applyDefaults() } } - override fun row() = flexContainer(FlexDirection.Row) + override fun row(): Row<@Composable () -> Unit> = ComposeUiRow() + .apply { + container.testOnlyModifier = Modifier.background(Color(defaultBackgroundColor)) + applyDefaults() + } - override fun column() = flexContainer(FlexDirection.Column) + override fun column(): Column<@Composable () -> Unit> = ComposeUiColumn() + .apply { + container.testOnlyModifier = Modifier.background(Color(defaultBackgroundColor)) + applyDefaults() + } override fun spacer(backgroundColor: Int): Spacer<@Composable () -> Unit> { // TODO: honor backgroundColor. - return ComposeUiRedwoodLayoutWidgetFactory().Spacer() + return ComposeUiSpacer() } override fun snapshotter(widget: @Composable () -> Unit) = ComposeSnapshotter(paparazzi, widget) @@ -71,7 +82,6 @@ class ComposeUiFlexContainerTest( private val delegate: ComposeUiFlexContainer, ) : TestFlexContainer<@Composable () -> Unit>, YogaFlexContainer<@Composable () -> Unit> by delegate { - override val children: ComposeWidgetChildren = delegate.children constructor(direction: FlexDirection, backgroundColor: Int) : this( ComposeUiFlexContainer(direction).apply { @@ -79,9 +89,15 @@ class ComposeUiFlexContainerTest( }, ) - override fun onScroll(onScroll: ((Px) -> Unit)?) { - delegate.onScroll(onScroll) - } + override val value get() = delegate.value + override var modifier by delegate::modifier + + override val children get() = delegate.children + + override fun width(width: Constraint) = delegate.width(width) + override fun height(height: Constraint) = delegate.height(height) + override fun overflow(overflow: Overflow) = delegate.overflow(overflow) + override fun onScroll(onScroll: ((Px) -> Unit)?) = delegate.onScroll(onScroll) override fun scroll(offset: Px) { runBlocking { diff --git a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt index ff3056dcf7..92b2b060f9 100644 --- a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt +++ b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt @@ -46,6 +46,8 @@ import app.cash.redwood.layout.api.Constraint import app.cash.redwood.layout.api.CrossAxisAlignment import app.cash.redwood.layout.api.MainAxisAlignment import app.cash.redwood.layout.api.Overflow +import app.cash.redwood.layout.widget.Column +import app.cash.redwood.layout.widget.Row import app.cash.redwood.ui.Density import app.cash.redwood.ui.Margin import app.cash.redwood.ui.Px @@ -56,6 +58,38 @@ import app.cash.redwood.yoga.Node import app.cash.redwood.yoga.Size import app.cash.redwood.yoga.isHorizontal +internal class ComposeUiColumn : Column<@Composable () -> Unit> { + internal val container = ComposeUiFlexContainer(FlexDirection.Column) + + override val value get() = container.value + override var modifier by container::modifier + override val children get() = container.children + + override fun width(width: Constraint) = container.width(width) + override fun height(height: Constraint) = container.height(height) + override fun margin(margin: Margin) = container.margin(margin) + override fun overflow(overflow: Overflow) = container.overflow(overflow) + override fun horizontalAlignment(horizontalAlignment: CrossAxisAlignment) = container.horizontalAlignment(horizontalAlignment) + override fun verticalAlignment(verticalAlignment: MainAxisAlignment) = container.verticalAlignment(verticalAlignment) + override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll) +} + +internal class ComposeUiRow : Row<@Composable () -> Unit> { + internal val container = ComposeUiFlexContainer(FlexDirection.Row) + + override val value get() = container.value + override var modifier by container::modifier + override val children get() = container.children + + override fun width(width: Constraint) = container.width(width) + override fun height(height: Constraint) = container.height(height) + override fun margin(margin: Margin) = container.margin(margin) + override fun overflow(overflow: Overflow) = container.overflow(overflow) + override fun horizontalAlignment(horizontalAlignment: MainAxisAlignment) = container.horizontalAlignment(horizontalAlignment) + override fun verticalAlignment(verticalAlignment: CrossAxisAlignment) = container.verticalAlignment(verticalAlignment) + override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll) +} + internal class ComposeUiFlexContainer( private val flexDirection: FlexDirection, ) : YogaFlexContainer<@Composable () -> Unit> { @@ -85,6 +119,7 @@ internal class ComposeUiFlexContainer( } override fun margin(margin: Margin) { + super.margin(margin) this.margin = margin } diff --git a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt index 7df88755bd..ccae48fe5f 100644 --- a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt +++ b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt @@ -21,11 +21,10 @@ import app.cash.redwood.layout.widget.Column import app.cash.redwood.layout.widget.RedwoodLayoutWidgetFactory import app.cash.redwood.layout.widget.Row import app.cash.redwood.layout.widget.Spacer -import app.cash.redwood.yoga.FlexDirection public class ComposeUiRedwoodLayoutWidgetFactory : RedwoodLayoutWidgetFactory<@Composable () -> Unit> { override fun Box(): Box<@Composable () -> Unit> = ComposeUiBox() - override fun Column(): Column<@Composable () -> Unit> = ComposeUiFlexContainer(FlexDirection.Column) - override fun Row(): Row<@Composable () -> Unit> = ComposeUiFlexContainer(FlexDirection.Row) + override fun Column(): Column<@Composable () -> Unit> = ComposeUiColumn() + override fun Row(): Row<@Composable () -> Unit> = ComposeUiRow() override fun Spacer(): Spacer<@Composable () -> Unit> = ComposeUiSpacer() } diff --git a/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt b/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt index 2dcead9b98..8322223cc2 100644 --- a/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt +++ b/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt @@ -43,9 +43,11 @@ import kotlin.test.assertTrue abstract class AbstractFlexContainerTest { abstract val widgetFactory: TestWidgetFactory + protected val defaultBackgroundColor = argb(51, 0, 0, 255) + abstract fun flexContainer( direction: FlexDirection, - backgroundColor: Int = argb(51, 0, 0, 255), + backgroundColor: Int = defaultBackgroundColor, ): TestFlexContainer /** @@ -63,6 +65,36 @@ abstract class AbstractFlexContainerTest { onScroll(null) } + /** + * Yoga node’s default values for properties like alignment are different from Redwood's default + * values, so we explicitly apply those defaults here. This is only necessary in tests; in + * production the framework explicitly sets every property. + */ + protected fun Row<*>.applyDefaults() { + width(Constraint.Wrap) + height(Constraint.Wrap) + margin(Margin.Zero) + overflow(Overflow.Clip) + verticalAlignment(CrossAxisAlignment.Start) + horizontalAlignment(MainAxisAlignment.Start) + onScroll(null) + } + + /** + * Yoga node’s default values for properties like alignment are different from Redwood's default + * values, so we explicitly apply those defaults here. This is only necessary in tests; in + * production the framework explicitly sets every property. + */ + protected fun Column<*>.applyDefaults() { + width(Constraint.Wrap) + height(Constraint.Wrap) + margin(Margin.Zero) + overflow(Overflow.Clip) + horizontalAlignment(CrossAxisAlignment.Start) + verticalAlignment(MainAxisAlignment.Start) + onScroll(null) + } + protected fun TestFlexContainer.add(widget: Widget) { addAt(children.widgets.size, widget) } diff --git a/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt b/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt index c744a57993..dd39353108 100644 --- a/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt +++ b/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt @@ -62,7 +62,7 @@ class UIViewFlexContainerTest( } } - applyDefaults() + (this as TestFlexContainer<*>).applyDefaults() } } diff --git a/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt b/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt index 840141258a..d0a1995346 100644 --- a/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt +++ b/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt @@ -54,7 +54,7 @@ class ViewFlexContainerTest( value.setBackgroundColor(backgroundColor) } return ViewTestFlexContainer(delegate) - .apply { applyDefaults() } + .apply { (this as TestFlexContainer<*>).applyDefaults() } } override fun row() = flexContainer(FlexDirection.Row)