Skip to content

Commit

Permalink
New API, RedwoodLayout.additionalInsets
Browse files Browse the repository at this point in the history
Consumers can use this to impose additional insets for system
bars and floating action buttons.
  • Loading branch information
squarejesse committed Dec 4, 2024
1 parent 72d1eb3 commit 1cc6328
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Breaking:

New:
- `UIConfiguration.viewInsets` tracks the safe area of the specific `RedwoodView` being targeted. This is currently implemented for views on Android and UIViews on iOS.
- `RedwoodLayout.additionalInsets` configures additional insets for application controls like tab bars and floating action buttons.
- `ConsumeInsets {}` composable consumes insets. Most applications should call this in their root composable function.

Changed:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,42 @@ class TreehouseLayoutTest {
}
}

@Test fun viewInsetsSumsSystemBarsAndAdditionalInsets() = runTest {
val layout = TreehouseLayout(activity, throwingWidgetSystem, activity.onBackPressedDispatcher)
layout.uiConfiguration.test {
assertThat(awaitItem().viewInsets).isEqualTo(Margin.Zero)

layout.additionalInsets = Insets.of(5, 6, 7, 8)
assertThat(awaitItem().viewInsets).isEqualTo(
with(Density(activity.resources)) {
Margin(
start = 5.toDp(),
top = 6.toDp(),
end = 7.toDp(),
bottom = 8.toDp(),
)
},
)

ViewCompat.dispatchApplyWindowInsets(
layout,
WindowInsetsCompat.Builder()
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(10, 20, 30, 40))
.build(),
)
assertThat(awaitItem().viewInsets).isEqualTo(
with(Density(activity.resources)) {
Margin(
start = 15.toDp(),
top = 26.toDp(),
end = 37.toDp(),
bottom = 48.toDp(),
)
},
)
}
}

@Test fun uiConfigurationEmitsLayoutDirectionChanges() = runTest {
val layout = TreehouseLayout(activity, throwingWidgetSystem, activity.onBackPressedDispatcher)
layout.uiConfiguration.test {
Expand Down
2 changes: 2 additions & 0 deletions redwood-widget/api/android/redwood-widget.api
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public final class app/cash/redwood/widget/MutableListChildren : app/cash/redwoo

public class app/cash/redwood/widget/RedwoodLayout : android/view/ViewGroup, app/cash/redwood/widget/RedwoodView {
public fun <init> (Landroid/content/Context;Landroidx/activity/OnBackPressedDispatcher;)V
public final fun getAdditionalInsets ()Landroidx/core/graphics/Insets;
public final fun getChildren ()Lapp/cash/redwood/widget/Widget$Children;
public fun getOnBackPressedDispatcher ()Lapp/cash/redwood/ui/OnBackPressedDispatcher;
public fun getSavedStateRegistry ()Lapp/cash/redwood/widget/SavedStateRegistry;
Expand All @@ -59,6 +60,7 @@ public class app/cash/redwood/widget/RedwoodLayout : android/view/ViewGroup, app
protected fun onConfigurationChanged (Landroid/content/res/Configuration;)V
protected fun onLayout (ZIIII)V
protected fun onMeasure (II)V
public final fun setAdditionalInsets (Landroidx/core/graphics/Insets;)V
}

public abstract interface class app/cash/redwood/widget/RedwoodView {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.view.View
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
Expand All @@ -46,15 +47,27 @@ public open class RedwoodLayout(
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
}
private var windowInsets: Insets = Insets.NONE
set(value) {
if (field == value) return
field = value
mutableUiConfiguration.value = computeUiConfiguration()
}

/**
* Additional insets that are summed with this view's window insets to produce
* [UiConfiguration.viewInsets]. Use this when an application-layer control like a floating action
* button or toolbar requires content to be inset.
*/
public var additionalInsets: Insets = Insets.NONE
set(value) {
if (field == value) return
field = value
mutableUiConfiguration.value = computeUiConfiguration()
}

private val mutableUiConfiguration = MutableStateFlow(
computeUiConfiguration(
viewInsets = Margin.Zero,
),
computeUiConfiguration(),
)

override val onBackPressedDispatcher: RedwoodOnBackPressedDispatcher =
Expand Down Expand Up @@ -85,10 +98,11 @@ public open class RedwoodLayout(
get() = mutableUiConfiguration

init {
// The view needs to have an ID to participate in instance state saving.
id = R.id.redwood_layout

setOnWindowInsetsChangeListener { insets ->
mutableUiConfiguration.value = computeUiConfiguration(
viewInsets = insets.safeDrawing.toMargin(Density(resources)),
)
windowInsets = insets.safeDrawing
}
}

Expand Down Expand Up @@ -118,13 +132,19 @@ public open class RedwoodLayout(

private fun computeUiConfiguration(
config: Configuration = context.resources.configuration,
viewInsets: Margin = uiConfiguration.value.viewInsets,
): UiConfiguration {
val viewportSize: Size
val density: Double
val viewInsets: Margin
with(Density(resources)) {
density = rawDensity
viewportSize = Size(width.toDp(), height.toDp())
viewInsets = Margin(
start = (windowInsets.left + additionalInsets.left).toDp(),
end = (windowInsets.right + additionalInsets.right).toDp(),
top = (windowInsets.top + additionalInsets.top).toDp(),
bottom = (windowInsets.bottom + additionalInsets.bottom).toDp(),
)
}
return UiConfiguration(
darkMode = (config.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES,
Expand Down

0 comments on commit 1cc6328

Please sign in to comment.