fix(theme): inset-aware chrome compaction (no first-frame snap)

Previous attempt computed total chrome height as
`Modifier.height(content + insetReadViaPaddingValues)`. The inset is
read at composition time and arrives as 0 on the very first frame
before the system-bar inset connection delivers its value — the bar
laid out at the shorter (no-inset) height, then re-measured once the
inset arrived. Visible as a one-frame snap on app cold start.

Switch to a layout-time pattern that subscribes to inset changes
correctly:

    Modifier
        .windowInsetsPadding(insets)   // reserves the inset via padding
        .height(contentHeight)         // content area only

`windowInsetsPadding` is a Modifier.Node that re-layouts (not re-
composes) on inset arrival, so the bar measures at the right total
height on the first frame. The bar's own `windowInsets` is set to
`WindowInsets(0)` so it doesn't double-pad.

Applied to:
- `BottomBar` NavigationBar — content height 56dp + navigation-bars inset
- `FeedScreen` CenterAlignedTopAppBar — content height 48dp + status-bars
  inset
This commit is contained in:
The Daniel
2026-05-23 16:53:13 -04:00
parent 45ce719af9
commit 6f97c42b14
2 changed files with 22 additions and 18 deletions
@@ -19,8 +19,9 @@ import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon
import androidx.compose.ui.res.painterResource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
@@ -81,14 +82,16 @@ fun WispBottomBar(
// + chrome and reserves the lighter "surface" for elevated
// controls (pills, cards).
containerColor = MaterialTheme.colorScheme.background,
// Material's default 80dp NavigationBar reserves a tall slot
// for labels we never render — clamp to 56dp + the gesture
// inset for a chrome height closer to the iOS tab bar.
modifier = Modifier.height(
56.dp + NavigationBarDefaults.windowInsets
.asPaddingValues().calculateBottomPadding()
),
windowInsets = NavigationBarDefaults.windowInsets
// Compact the chrome: reserve the gesture inset via
// `windowInsetsPadding` (resolved at layout time, so no
// first-frame snap) and clamp the content area to 56dp
// (was Material's default 80dp). Pass `WindowInsets(0)` to
// disable NavigationBar's internal inset handling so we
// don't double-pad.
modifier = Modifier
.windowInsetsPadding(NavigationBarDefaults.windowInsets)
.height(56.dp),
windowInsets = WindowInsets(0)
) {
visibleTabs.forEach { tab ->
val selected = currentRoute == tab.route
@@ -15,8 +15,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -752,14 +752,15 @@ fun FeedScreen(
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = {
CenterAlignedTopAppBar(
// Material's default 64dp content + status-bar inset
// pads a chunky gap below the icon row. Clamp to 48dp
// + status-bar inset so the chrome lands closer to
// the iOS navigation bar (~44dp content + safe-area).
modifier = Modifier.height(
48.dp + WindowInsets.statusBars
.asPaddingValues().calculateTopPadding()
),
// Compact: reserve the status-bar inset via
// `windowInsetsPadding` (layout-time, no first-frame
// snap) and clamp the content area to 48dp (was
// Material's default 64dp). Disable the bar's
// internal inset handling to avoid double-padding.
modifier = Modifier
.windowInsetsPadding(WindowInsets.statusBars)
.height(48.dp),
windowInsets = WindowInsets(0),
title = {
Box {
Surface(