From c40aa2521774cd74ecd7d6f9c35e3fa41fc59680 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Tue, 23 Jul 2024 17:54:54 -0400 Subject: [PATCH] WIP: UiConfiguration.windowInsets This introduces a new mutable property, windowInsets, that is propagated to the composition where it can be consumed. We put the burden on the user of RedwoodView to set this value to something non-zero if they have insets to be consumed. I've added an accelerator on Android for this, RedwoodLayout.consumeWindowInsets(mask: Int) I'll look into a similar API for iOS and HTML to do likewise. --- .../api/android/redwood-runtime.api | 5 +- redwood-runtime/api/jvm/redwood-runtime.api | 5 +- redwood-runtime/api/redwood-runtime.klib.api | 4 +- .../app/cash/redwood/ui/UiConfiguration.kt | 16 +++++ .../redwood-treehouse-host-composeui.api | 2 +- .../jvm/redwood-treehouse-host-composeui.api | 2 +- .../redwood-treehouse-host-composeui.klib.api | 2 +- .../treehouse/composeui/TreehouseContent.kt | 4 ++ redwood-widget/api/android/redwood-widget.api | 6 ++ redwood-widget/api/jvm/redwood-widget.api | 2 + redwood-widget/api/redwood-widget.klib.api | 8 +++ .../app/cash/redwood/widget/RedwoodLayout.kt | 62 +++++++++++++++++-- .../app/cash/redwood/widget/RedwoodView.kt | 7 +++ .../app/cash/redwood/widget/RedwoodUIView.kt | 19 ++++++ .../redwood/widget/RedwoodHTMLElementView.kt | 19 ++++++ 15 files changed, 149 insertions(+), 14 deletions(-) diff --git a/redwood-runtime/api/android/redwood-runtime.api b/redwood-runtime/api/android/redwood-runtime.api index 9c955bc8d8..0a22c29121 100644 --- a/redwood-runtime/api/android/redwood-runtime.api +++ b/redwood-runtime/api/android/redwood-runtime.api @@ -214,14 +214,15 @@ public final class app/cash/redwood/ui/UiConfiguration { public static final field $stable I public static final field Companion Lapp/cash/redwood/ui/UiConfiguration$Companion; public fun ()V - public fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;)V - public synthetic fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;)V + public synthetic fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getDarkMode ()Z public final fun getDensity ()D public final fun getLayoutDirection ()Lapp/cash/redwood/ui/LayoutDirection; public final fun getSafeAreaInsets ()Lapp/cash/redwood/ui/Margin; public final fun getViewportSize ()Lapp/cash/redwood/ui/Size; + public final fun getWindowInsets ()Lapp/cash/redwood/ui/Margin; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/redwood-runtime/api/jvm/redwood-runtime.api b/redwood-runtime/api/jvm/redwood-runtime.api index 1e28f23f73..3315e0d07d 100644 --- a/redwood-runtime/api/jvm/redwood-runtime.api +++ b/redwood-runtime/api/jvm/redwood-runtime.api @@ -210,14 +210,15 @@ public final class app/cash/redwood/ui/UiConfiguration { public static final field $stable I public static final field Companion Lapp/cash/redwood/ui/UiConfiguration$Companion; public fun ()V - public fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;)V - public synthetic fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;)V + public synthetic fun (ZLapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Margin;Lapp/cash/redwood/ui/Size;DLapp/cash/redwood/ui/LayoutDirection;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getDarkMode ()Z public final fun getDensity ()D public final fun getLayoutDirection ()Lapp/cash/redwood/ui/LayoutDirection; public final fun getSafeAreaInsets ()Lapp/cash/redwood/ui/Margin; public final fun getViewportSize ()Lapp/cash/redwood/ui/Size; + public final fun getWindowInsets ()Lapp/cash/redwood/ui/Margin; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/redwood-runtime/api/redwood-runtime.klib.api b/redwood-runtime/api/redwood-runtime.klib.api index 90d03a02f4..1e41740cd9 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/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.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] @@ -154,6 +154,8 @@ final class app.cash.redwood.ui/UiConfiguration { // app.cash.redwood.ui/UiConfi 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 val windowInsets // app.cash.redwood.ui/UiConfiguration.windowInsets|{}windowInsets[0] + final fun (): app.cash.redwood.ui/Margin // app.cash.redwood.ui/UiConfiguration.windowInsets.|(){}[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 bbf60208ef..087b7b4665 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 @@ -22,7 +22,23 @@ import kotlinx.serialization.Serializable @Poko public class UiConfiguration( public val darkMode: Boolean = false, + + /** + * The insets of the host window, independent of where the Redwood composition is positioned + * within it. The Redwood composition is not responsible for consuming these insets. + */ public val safeAreaInsets: Margin = Margin.Zero, + + /** + * The insets of the viewport that the composition is responsible for consuming. + * + * This may be zero if the host view isn't attached to a view hierarchy and therefore doesn't + * know its insets. + * + * See https://developer.android.com/develop/ui/views/layout/edge-to-edge + */ + public val windowInsets: Margin = Margin.Zero, + /** * The size of the viewport into which the composition is rendering. This could be as lage as the * entire screen or as small as an individual view within a larger native screen. 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 0953244033..3ac4000d44 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,4 +1,4 @@ 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;Lapp/cash/redwood/treehouse/CodeListener;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/ui/Margin;Lapp/cash/redwood/treehouse/CodeListener;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 0953244033..3ac4000d44 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,4 +1,4 @@ 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;Lapp/cash/redwood/treehouse/CodeListener;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/ui/Margin;Lapp/cash/redwood/treehouse/CodeListener;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 5180321d51..d8b190e1af 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,4 +6,4 @@ // - Show declarations: true // Library unique name: -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/CodeListener?, 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.CodeListener?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[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.ui/Margin?, app.cash.redwood.treehouse/CodeListener?, 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.ui.Margin?;app.cash.redwood.treehouse.CodeListener?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] 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..306ac66447 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 @@ -41,6 +41,7 @@ import app.cash.redwood.treehouse.TreehouseView.WidgetSystem import app.cash.redwood.treehouse.bindWhenReady import app.cash.redwood.ui.Density import app.cash.redwood.ui.LayoutDirection as RedwoodLayoutDirection +import app.cash.redwood.ui.Margin import app.cash.redwood.ui.OnBackPressedDispatcher import app.cash.redwood.ui.Size import app.cash.redwood.ui.UiConfiguration @@ -55,6 +56,7 @@ public fun TreehouseContent( widgetSystem: WidgetSystem<@Composable () -> Unit>, contentSource: TreehouseContentSource, modifier: Modifier = Modifier, + windowInsets: Margin = Margin.Zero, codeListener: CodeListener = remember { CodeListener() }, ) { val onBackPressedDispatcher = platformOnBackPressedDispatcher() @@ -64,6 +66,7 @@ public fun TreehouseContent( val uiConfiguration = UiConfiguration( darkMode = isSystemInDarkTheme(), safeAreaInsets = safeAreaInsets(), + windowInsets = windowInsets, viewportSize = viewportSize, density = density.density.toDouble(), layoutDirection = when (LocalLayoutDirection.current) { @@ -77,6 +80,7 @@ public fun TreehouseContent( override val children = ComposeWidgetChildren() override val onBackPressedDispatcher = onBackPressedDispatcher override val uiConfiguration = MutableStateFlow(uiConfiguration) + override var windowInsets: Margin = windowInsets // TODO TreehouseView is a weird type and shouldn't extend from RedwoodView. The concept // of this registry shouldn't exist for Treehouse / should be auto-wired via RedwoodContent. diff --git a/redwood-widget/api/android/redwood-widget.api b/redwood-widget/api/android/redwood-widget.api index da9c626ea1..8a0578dd7e 100644 --- a/redwood-widget/api/android/redwood-widget.api +++ b/redwood-widget/api/android/redwood-widget.api @@ -50,13 +50,17 @@ public final class app/cash/redwood/widget/MutableListChildren : app/cash/redwoo public class app/cash/redwood/widget/RedwoodLayout : android/widget/FrameLayout, app/cash/redwood/widget/RedwoodView { public fun (Landroid/content/Context;Landroidx/activity/OnBackPressedDispatcher;)V + public final fun consumeWindowInsets (I)V + public static synthetic fun consumeWindowInsets$default (Lapp/cash/redwood/widget/RedwoodLayout;IILjava/lang/Object;)V public fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; public fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher; public fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry; public fun getUiConfiguration ()Lkotlinx/coroutines/flow/StateFlow; + public fun getWindowInsets ()Lapp/cash/redwood/ui/Margin; protected fun onConfigurationChanged (Landroid/content/res/Configuration;)V protected fun onLayout (ZIIII)V public fun reset ()V + public fun setWindowInsets (Lapp/cash/redwood/ui/Margin;)V } public abstract interface class app/cash/redwood/widget/RedwoodView { @@ -64,7 +68,9 @@ public abstract interface class app/cash/redwood/widget/RedwoodView { public abstract fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher; public abstract fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry; public abstract fun getUiConfiguration ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun getWindowInsets ()Lapp/cash/redwood/ui/Margin; public abstract fun reset ()V + public abstract fun setWindowInsets (Lapp/cash/redwood/ui/Margin;)V } public abstract interface class app/cash/redwood/widget/SavedStateRegistry { diff --git a/redwood-widget/api/jvm/redwood-widget.api b/redwood-widget/api/jvm/redwood-widget.api index d16906742e..d1931ea1d2 100644 --- a/redwood-widget/api/jvm/redwood-widget.api +++ b/redwood-widget/api/jvm/redwood-widget.api @@ -53,7 +53,9 @@ public abstract interface class app/cash/redwood/widget/RedwoodView { public abstract fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher; public abstract fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry; public abstract fun getUiConfiguration ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun getWindowInsets ()Lapp/cash/redwood/ui/Margin; public abstract fun reset ()V + public abstract fun setWindowInsets (Lapp/cash/redwood/ui/Margin;)V } public abstract interface class app/cash/redwood/widget/SavedStateRegistry { diff --git a/redwood-widget/api/redwood-widget.klib.api b/redwood-widget/api/redwood-widget.klib.api index 6d65845e96..85aed2d560 100644 --- a/redwood-widget/api/redwood-widget.klib.api +++ b/redwood-widget/api/redwood-widget.klib.api @@ -17,6 +17,10 @@ abstract interface <#A: kotlin/Any> app.cash.redwood.widget/RedwoodView { // app 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 var windowInsets // app.cash.redwood.widget/RedwoodView.windowInsets|{}windowInsets[0] + abstract fun (): app.cash.redwood.ui/Margin // app.cash.redwood.widget/RedwoodView.windowInsets.|(){}[0] + abstract fun (app.cash.redwood.ui/Margin) // app.cash.redwood.widget/RedwoodView.windowInsets.|(app.cash.redwood.ui.Margin){}[0] + abstract fun reset() // app.cash.redwood.widget/RedwoodView.reset|reset(){}[0] } @@ -127,6 +131,10 @@ open class app.cash.redwood.widget/RedwoodUIView : app.cash.redwood.widget/Redwo 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 var windowInsets // app.cash.redwood.widget/RedwoodUIView.windowInsets|{}windowInsets[0] + open fun (): app.cash.redwood.ui/Margin // app.cash.redwood.widget/RedwoodUIView.windowInsets.|(){}[0] + open fun (app.cash.redwood.ui/Margin) // app.cash.redwood.widget/RedwoodUIView.windowInsets.|(app.cash.redwood.ui.Margin){}[0] + final fun updateUiConfiguration() // app.cash.redwood.widget/RedwoodUIView.updateUiConfiguration|updateUiConfiguration(){}[0] open fun reset() // app.cash.redwood.widget/RedwoodUIView.reset|reset(){}[0] } 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 46c30191e2..8b4a60baf5 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 @@ -23,10 +23,13 @@ import android.widget.FrameLayout import androidx.activity.OnBackPressedCallback as AndroidOnBackPressedCallback import androidx.activity.OnBackPressedDispatcher as AndroidOnBackPressedDispatcher import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.savedstate.findViewTreeSavedStateRegistryOwner import app.cash.redwood.ui.Cancellable import app.cash.redwood.ui.Density import app.cash.redwood.ui.LayoutDirection +import app.cash.redwood.ui.Margin import app.cash.redwood.ui.OnBackPressedCallback as RedwoodOnBackPressedCallback import app.cash.redwood.ui.OnBackPressedDispatcher as RedwoodOnBackPressedDispatcher import app.cash.redwood.ui.Size @@ -48,7 +51,11 @@ public open class RedwoodLayout( private val _children = ViewGroupChildren(this) override val children: Widget.Children get() = _children - private val mutableUiConfiguration = MutableStateFlow(computeUiConfiguration()) + private val mutableUiConfiguration = MutableStateFlow( + computeUiConfiguration( + windowInsets = Margin.Zero, + ), + ) override val onBackPressedDispatcher: RedwoodOnBackPressedDispatcher = object : RedwoodOnBackPressedDispatcher { @@ -77,6 +84,20 @@ public open class RedwoodLayout( override val uiConfiguration: StateFlow get() = mutableUiConfiguration + override var windowInsets: Margin + get() = uiConfiguration.value.windowInsets + set(value) { + val old = uiConfiguration.value + mutableUiConfiguration.value = UiConfiguration( + darkMode = old.darkMode, + safeAreaInsets = old.safeAreaInsets, + windowInsets = value, + viewportSize = old.viewportSize, + density = old.density, + layoutDirection = old.layoutDirection, + ) + } + override fun reset() { _children.remove(0, _children.widgets.size) @@ -86,24 +107,36 @@ public open class RedwoodLayout( init { setOnWindowInsetsChangeListener { insets -> - mutableUiConfiguration.value = computeUiConfiguration(insets = insets.safeDrawing) + val old = mutableUiConfiguration.value + mutableUiConfiguration.value = computeUiConfiguration( + safeAreaInsets = insets.safeDrawing, + windowInsets = old.windowInsets, + ) } } @SuppressLint("DrawAllocation") // It's only on layout. override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - mutableUiConfiguration.value = computeUiConfiguration() + val old = mutableUiConfiguration.value + mutableUiConfiguration.value = computeUiConfiguration( + windowInsets = old.windowInsets, + ) super.onLayout(changed, left, top, right, bottom) } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - mutableUiConfiguration.value = computeUiConfiguration(config = newConfig) + val old = mutableUiConfiguration.value + mutableUiConfiguration.value = computeUiConfiguration( + config = newConfig, + windowInsets = old.windowInsets, + ) } private fun computeUiConfiguration( config: Configuration = context.resources.configuration, - insets: Insets = rootWindowInsetsCompat.safeDrawing, + safeAreaInsets: Insets = rootWindowInsetsCompat.safeDrawing, + windowInsets: Margin, ): UiConfiguration { val viewportSize: Size val density: Double @@ -113,7 +146,8 @@ public open class RedwoodLayout( } return UiConfiguration( darkMode = (config.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES, - safeAreaInsets = insets.toMargin(Density(resources)), + safeAreaInsets = safeAreaInsets.toMargin(Density(resources)), + windowInsets = windowInsets, viewportSize = viewportSize, density = density, layoutDirection = when (config.layoutDirection) { @@ -123,6 +157,22 @@ public open class RedwoodLayout( }, ) } + + /** + * Consume the identified insets from the enclosing UI in the Redwood composition. + * + * This installs a `OnApplyWindowInsetsListener` on this view that updates [windowInsets] each + * time the window insets are updated. + */ + public fun consumeWindowInsets( + mask: Int = (WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()), + ) { + ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> + val insetsForMask = insets.getInsets(mask) + windowInsets = insetsForMask.toMargin(Density(resources)) + WindowInsetsCompat.CONSUMED + } + } } private fun RedwoodOnBackPressedCallback.toAndroid(): AndroidOnBackPressedCallback = 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 8e098a2aed..5e6d065c36 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 @@ -15,6 +15,7 @@ */ package app.cash.redwood.widget +import app.cash.redwood.ui.Margin import app.cash.redwood.ui.OnBackPressedDispatcher import app.cash.redwood.ui.UiConfiguration import kotlin.native.ObjCName @@ -27,6 +28,12 @@ public interface RedwoodView { public val uiConfiguration: StateFlow public val savedStateRegistry: SavedStateRegistry? + /** + * The insets of the viewport that the composition is responsible for consuming. This value is + * passed to the composition as [UiConfiguration.windowInsets]. + */ + public var windowInsets: Margin + /** * This should at minimum clear all [children]. * 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 8ec420c9b5..aa0e476a2e 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 @@ -47,6 +47,7 @@ public open class RedwoodUIView( MutableStateFlow( computeUiConfiguration( traitCollection = view.traitCollection, + windowInsets = Margin.Zero, layoutDirection = view.effectiveUserInterfaceLayoutDirection, bounds = view.bounds, ), @@ -63,6 +64,20 @@ public open class RedwoodUIView( override val uiConfiguration: StateFlow get() = mutableUiConfiguration + override var windowInsets: Margin + get() = uiConfiguration.value.windowInsets + set(value) { + val old = uiConfiguration.value + mutableUiConfiguration.value = UiConfiguration( + darkMode = old.darkMode, + safeAreaInsets = old.safeAreaInsets, + windowInsets = value, + viewportSize = old.viewportSize, + density = old.density, + layoutDirection = old.layoutDirection, + ) + } + override val savedStateRegistry: SavedStateRegistry? get() = null @@ -75,8 +90,10 @@ public open class RedwoodUIView( } protected fun updateUiConfiguration() { + val old = mutableUiConfiguration.value mutableUiConfiguration.value = computeUiConfiguration( traitCollection = view.traitCollection, + windowInsets = old.windowInsets, layoutDirection = view.effectiveUserInterfaceLayoutDirection, bounds = view.bounds, ) @@ -85,12 +102,14 @@ public open class RedwoodUIView( internal fun computeUiConfiguration( traitCollection: UITraitCollection, + windowInsets: Margin, layoutDirection: UIUserInterfaceLayoutDirection, bounds: CValue, ): UiConfiguration { return UiConfiguration( darkMode = traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark, safeAreaInsets = computeSafeAreaInsets(), + windowInsets = windowInsets, viewportSize = bounds.useContents { with(Density.Default) { Size(size.width.toDp(), size.height.toDp()) 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 d4a3cfbf20..783dc1a451 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 @@ -17,6 +17,7 @@ package app.cash.redwood.widget import app.cash.redwood.ui.Cancellable import app.cash.redwood.ui.LayoutDirection +import app.cash.redwood.ui.Margin import app.cash.redwood.ui.OnBackPressedCallback import app.cash.redwood.ui.OnBackPressedDispatcher import app.cash.redwood.ui.Size @@ -58,6 +59,21 @@ private class RedwoodHTMLElementView( private val _uiConfiguration: MutableStateFlow override val uiConfiguration: StateFlow get() = _uiConfiguration + override var windowInsets: Margin + get() = uiConfiguration.value.windowInsets + set(value) { + updateUiConfiguration { old -> + UiConfiguration( + darkMode = old.darkMode, + safeAreaInsets = old.safeAreaInsets, + windowInsets = value, + viewportSize = old.viewportSize, + density = old.density, + layoutDirection = old.layoutDirection, + ) + } + } + override val savedStateRegistry: SavedStateRegistry? get() = null @@ -67,6 +83,7 @@ private class RedwoodHTMLElementView( _uiConfiguration = MutableStateFlow( UiConfiguration( darkMode = colorSchemeQuery.matches, + windowInsets = Margin.Zero, viewportSize = Size(width = element.offsetWidth.dp, height = element.offsetHeight.dp), layoutDirection = when (element.dir) { "ltr" -> LayoutDirection.Ltr @@ -82,6 +99,7 @@ private class RedwoodHTMLElementView( UiConfiguration( darkMode = event.unsafeCast().matches, safeAreaInsets = old.safeAreaInsets, + windowInsets = old.windowInsets, viewportSize = old.viewportSize, density = old.density, layoutDirection = old.layoutDirection, @@ -114,6 +132,7 @@ private class RedwoodHTMLElementView( UiConfiguration( darkMode = old.darkMode, safeAreaInsets = old.safeAreaInsets, + windowInsets = old.windowInsets, viewportSize = old.viewportSize, density = window.devicePixelRatio, layoutDirection = old.layoutDirection,