From 831b4c9d7ced73324c6bd8098e770900b9158c39 Mon Sep 17 00:00:00 2001 From: Colin White Date: Mon, 12 Aug 2024 15:13:57 -0400 Subject: [PATCH] Allow viewportSize to be null when it is unresolved. (#2221) * Allow viewportSize to be null when it is unresolved. * Fix build. * Fix tests. * Correct extensions. * Fix. --- CHANGELOG.md | 1 + .../cash/redwood/composeui/RedwoodContent.kt | 2 +- redwood-runtime/api/redwood-runtime.klib.api | 4 ++-- .../app/cash/redwood/ui/UiConfiguration.kt | 4 +++- .../treehouse/composeui/TreehouseContent.kt | 2 +- .../redwood/treehouse/TreehouseLayoutTest.kt | 17 +++++++++-------- .../redwood/treehouse/TreehouseUIViewTest.kt | 16 +++++++--------- .../testapp/presenter/UiConfiguration.kt | 4 ++-- 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c33a9274a5..08b7554027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ New: Changed: - `ProtocolFactory` interface is now sealed as arbitrary subtypes were never supported. Only schema-generated subtypes should be used. - `UIViewLazyList` doesn't crash with a `NullPointerException` if cells are added, removed, and re-added without being reused. +- Change `UiConfiguration.viewportSize` to be nullable. A null `viewportSize` indicates the viewport's size has not been resolved yet. Fixed: - Nothing yet! diff --git a/redwood-composeui/src/commonMain/kotlin/app/cash/redwood/composeui/RedwoodContent.kt b/redwood-composeui/src/commonMain/kotlin/app/cash/redwood/composeui/RedwoodContent.kt index 071cc35545..4d543709cc 100644 --- a/redwood-composeui/src/commonMain/kotlin/app/cash/redwood/composeui/RedwoodContent.kt +++ b/redwood-composeui/src/commonMain/kotlin/app/cash/redwood/composeui/RedwoodContent.kt @@ -50,7 +50,7 @@ public fun RedwoodContent( content: @Composable () -> Unit, ) { // If the provider or content change, reset any assumption about the rendered size. - var viewportSize by remember(widgetSystem, content) { mutableStateOf(Size.Zero) } + var viewportSize: Size? by remember(widgetSystem, content) { mutableStateOf(null) } val density = LocalDensity.current val uiConfiguration = UiConfiguration( diff --git a/redwood-runtime/api/redwood-runtime.klib.api b/redwood-runtime/api/redwood-runtime.klib.api index 96cae15221..86007f79e6 100644 --- a/redwood-runtime/api/redwood-runtime.klib.api +++ b/redwood-runtime/api/redwood-runtime.klib.api @@ -142,7 +142,7 @@ final class app.cash.redwood.ui/Size { // app.cash.redwood.ui/Size|null[0] } final class app.cash.redwood.ui/UiConfiguration { // app.cash.redwood.ui/UiConfiguration|null[0] - constructor (kotlin/Boolean = ..., app.cash.redwood.ui/Margin = ..., app.cash.redwood.ui/Size = ..., kotlin/Double = ..., app.cash.redwood.ui/LayoutDirection = ...) // app.cash.redwood.ui/UiConfiguration.|(kotlin.Boolean;app.cash.redwood.ui.Margin;app.cash.redwood.ui.Size;kotlin.Double;app.cash.redwood.ui.LayoutDirection){}[0] + constructor (kotlin/Boolean = ..., app.cash.redwood.ui/Margin = ..., app.cash.redwood.ui/Size? = ..., kotlin/Double = ..., app.cash.redwood.ui/LayoutDirection = ...) // app.cash.redwood.ui/UiConfiguration.|(kotlin.Boolean;app.cash.redwood.ui.Margin;app.cash.redwood.ui.Size?;kotlin.Double;app.cash.redwood.ui.LayoutDirection){}[0] final val darkMode // app.cash.redwood.ui/UiConfiguration.darkMode|{}darkMode[0] final fun (): kotlin/Boolean // app.cash.redwood.ui/UiConfiguration.darkMode.|(){}[0] @@ -153,7 +153,7 @@ final class app.cash.redwood.ui/UiConfiguration { // app.cash.redwood.ui/UiConfi final val safeAreaInsets // app.cash.redwood.ui/UiConfiguration.safeAreaInsets|{}safeAreaInsets[0] final fun (): app.cash.redwood.ui/Margin // app.cash.redwood.ui/UiConfiguration.safeAreaInsets.|(){}[0] final val viewportSize // app.cash.redwood.ui/UiConfiguration.viewportSize|{}viewportSize[0] - final fun (): app.cash.redwood.ui/Size // app.cash.redwood.ui/UiConfiguration.viewportSize.|(){}[0] + final fun (): app.cash.redwood.ui/Size? // app.cash.redwood.ui/UiConfiguration.viewportSize.|(){}[0] final fun equals(kotlin/Any?): kotlin/Boolean // app.cash.redwood.ui/UiConfiguration.equals|equals(kotlin.Any?){}[0] final fun hashCode(): kotlin/Int // app.cash.redwood.ui/UiConfiguration.hashCode|hashCode(){}[0] diff --git a/redwood-runtime/src/commonMain/kotlin/app/cash/redwood/ui/UiConfiguration.kt b/redwood-runtime/src/commonMain/kotlin/app/cash/redwood/ui/UiConfiguration.kt index 802f947fe4..2514464156 100644 --- a/redwood-runtime/src/commonMain/kotlin/app/cash/redwood/ui/UiConfiguration.kt +++ b/redwood-runtime/src/commonMain/kotlin/app/cash/redwood/ui/UiConfiguration.kt @@ -39,8 +39,10 @@ public class UiConfiguration( * This does not offer any information on the size of the individual composables which are * rendering within the composition, but is the frame into which they will render. The root * composable if stretching to fill the viewport will match this size. + * + * A null viewport size indicates it has not been resolved yet. */ - public val viewportSize: Size = Size.Zero, + public val viewportSize: Size? = null, /** * The density of the display. This can be used to convert [Dp] within other properties back to * raw pixels, if needed. 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 cd57472751..1a3cde5464 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 @@ -59,7 +59,7 @@ public fun TreehouseContent( ) { val onBackPressedDispatcher = platformOnBackPressedDispatcher() - var viewportSize by remember { mutableStateOf(Size.Zero) } + var viewportSize: Size? by remember { mutableStateOf(null) } val density = LocalDensity.current val uiConfiguration = UiConfiguration( darkMode = isSystemInDarkTheme(), 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 095453f038..c527d35a70 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 @@ -29,14 +29,15 @@ import app.cash.redwood.treehouse.TreehouseView.WidgetSystem import app.cash.redwood.ui.Density import app.cash.redwood.ui.LayoutDirection import app.cash.redwood.ui.Margin -import app.cash.redwood.ui.UiConfiguration import app.cash.redwood.widget.ViewGroupChildren import app.cash.redwood.widget.Widget import app.cash.turbine.test import assertk.assertThat import assertk.assertions.hasSize import assertk.assertions.isEqualTo +import assertk.assertions.isFalse import assertk.assertions.isSameInstanceAs +import assertk.assertions.isTrue import java.util.Locale import kotlinx.coroutines.test.runTest import org.junit.Test @@ -102,26 +103,26 @@ class TreehouseLayoutTest { newConfig.uiMode = (newConfig.uiMode and UI_MODE_NIGHT_MASK.inv()) or UI_MODE_NIGHT_YES val newContext = activity.createConfigurationContext(newConfig) // Needs API 26. val layout = TreehouseLayout(newContext, throwingWidgetSystem, activity.onBackPressedDispatcher) - assertThat(layout.uiConfiguration.value).isEqualTo(UiConfiguration(darkMode = true)) + assertThat(layout.uiConfiguration.value.darkMode).isTrue() } @Test fun uiConfigurationEmitsUiModeChanges() = runTest { val layout = TreehouseLayout(activity, throwingWidgetSystem, activity.onBackPressedDispatcher) layout.uiConfiguration.test { - assertThat(awaitItem()).isEqualTo(UiConfiguration(darkMode = false)) + assertThat(awaitItem().darkMode).isFalse() val newConfig = Configuration(activity.resources.configuration) newConfig.uiMode = (newConfig.uiMode and UI_MODE_NIGHT_MASK.inv()) or UI_MODE_NIGHT_YES layout.dispatchConfigurationChanged(newConfig) - assertThat(awaitItem()).isEqualTo(UiConfiguration(darkMode = true)) + assertThat(awaitItem().darkMode).isTrue() } } @Test fun uiConfigurationEmitsSystemBarsSafeAreaInsetsChanges() = runTest { val layout = TreehouseLayout(activity, throwingWidgetSystem, activity.onBackPressedDispatcher) layout.uiConfiguration.test { - assertThat(awaitItem()).isEqualTo(UiConfiguration(safeAreaInsets = Margin.Zero)) + assertThat(awaitItem().safeAreaInsets).isEqualTo(Margin.Zero) val insets = Insets.of(10, 20, 30, 40) val windowInsets = WindowInsetsCompat.Builder() .setInsets(WindowInsetsCompat.Type.systemBars(), insets) @@ -135,20 +136,20 @@ class TreehouseLayoutTest { bottom = insets.bottom.toDp(), ) } - assertThat(awaitItem()).isEqualTo(UiConfiguration(safeAreaInsets = expectedInsets)) + assertThat(awaitItem().safeAreaInsets).isEqualTo(expectedInsets) } } @Test fun uiConfigurationEmitsLayoutDirectionChanges() = runTest { val layout = TreehouseLayout(activity, throwingWidgetSystem, activity.onBackPressedDispatcher) layout.uiConfiguration.test { - assertThat(awaitItem()).isEqualTo(UiConfiguration(layoutDirection = LayoutDirection.Ltr)) + assertThat(awaitItem().layoutDirection).isEqualTo(LayoutDirection.Ltr) val newConfig = Configuration(activity.resources.configuration) newConfig.setLayoutDirection(Locale("he")) // Hebrew is RTL layout.dispatchConfigurationChanged(newConfig) - assertThat(awaitItem()).isEqualTo(UiConfiguration(layoutDirection = LayoutDirection.Rtl)) + assertThat(awaitItem().layoutDirection).isEqualTo(LayoutDirection.Rtl) } } 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 7d950b5f76..0d2e13abdb 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 @@ -19,12 +19,13 @@ import app.cash.redwood.ui.Default import app.cash.redwood.ui.Density import app.cash.redwood.ui.LayoutDirection import app.cash.redwood.ui.Margin -import app.cash.redwood.ui.UiConfiguration import app.cash.redwood.widget.UIViewChildren import app.cash.turbine.test import assertk.assertThat import assertk.assertions.hasSize import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isTrue import kotlin.test.Test import kotlinx.cinterop.objcPtr import kotlinx.cinterop.useContents @@ -95,7 +96,7 @@ class TreehouseUIViewTest { val layout = TreehouseUIView(throwingWidgetSystem) parent.addSubview(layout.view) - assertThat(layout.uiConfiguration.value).isEqualTo(UiConfiguration(darkMode = true)) + assertThat(layout.uiConfiguration.value.darkMode).isTrue() } @Test fun uiConfigurationEmitsUiModeChanges() = runTest { @@ -105,13 +106,13 @@ class TreehouseUIViewTest { parent.addSubview(layout.view) layout.uiConfiguration.test { - assertThat(awaitItem()).isEqualTo(UiConfiguration(darkMode = false)) + assertThat(awaitItem().darkMode).isFalse() parent.overrideUserInterfaceStyle = UIUserInterfaceStyleDark // Style propagation through hierarchy is async so yield to run loop for any posted work. NSRunLoop.currentRunLoop.runUntilDate(NSDate()) - assertThat(awaitItem()).isEqualTo(UiConfiguration(darkMode = true)) + assertThat(awaitItem().darkMode).isTrue() } } @@ -128,8 +129,7 @@ class TreehouseUIViewTest { val layout = TreehouseUIView(throwingWidgetSystem) parent.addSubview(layout.view) - assertThat(layout.uiConfiguration.value) - .isEqualTo(UiConfiguration(safeAreaInsets = expectedInsets)) + assertThat(layout.uiConfiguration.value.safeAreaInsets).isEqualTo(expectedInsets) } @Test @@ -144,8 +144,6 @@ class TreehouseUIViewTest { UIUserInterfaceLayoutDirectionRightToLeft -> LayoutDirection.Rtl else -> throw IllegalStateException("Unknown layout direction $direction") } - assertThat(layout.uiConfiguration.value).isEqualTo( - expected = UiConfiguration(layoutDirection = expectedLayoutDirection), - ) + assertThat(layout.uiConfiguration.value.layoutDirection).isEqualTo(expectedLayoutDirection) } } diff --git a/test-app/presenter/src/commonMain/kotlin/com/example/redwood/testapp/presenter/UiConfiguration.kt b/test-app/presenter/src/commonMain/kotlin/com/example/redwood/testapp/presenter/UiConfiguration.kt index dd99e22bd9..ac26c36473 100644 --- a/test-app/presenter/src/commonMain/kotlin/com/example/redwood/testapp/presenter/UiConfiguration.kt +++ b/test-app/presenter/src/commonMain/kotlin/com/example/redwood/testapp/presenter/UiConfiguration.kt @@ -40,8 +40,8 @@ fun UiConfigurationValues(modifier: Modifier = Modifier) { val viewportSize = uiConfiguration.viewportSize Text("Viewport size:") - Text("- width: ${viewportSize.width}") - Text("- height: ${viewportSize.height}") + Text("- width: ${viewportSize?.width}") + Text("- height: ${viewportSize?.height}") Text("Density: ${uiConfiguration.density}") }