diff --git a/redwood-runtime/api/android/redwood-runtime.api b/redwood-runtime/api/android/redwood-runtime.api index 9c955bc8d..0a22c2912 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 1e28f23f7..3315e0d07 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 90d03a02f..1e41740cd 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 bbf60208e..087b7b466 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 095324403..3ac4000d4 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 095324403..3ac4000d4 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 5180321d5..d8b190e1a 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 cd5747275..306ac6644 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 da9c626ea..8a0578dd7 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 d16906742..d1931ea1d 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 6d65845e9..85aed2d56 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 46c30191e..8b4a60baf 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 8e098a2ae..5e6d065c3 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 8ec420c9b..aa0e476a2 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 d4a3cfbf2..783dc1a45 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,