Returning from a stream left every menu laid out against the wrong safe
area: content shoved right by the landscape side inset, the profile row
sliding under the status bar, the tab labels crowding the gesture pill.
Compose attaches its OnApplyWindowInsets AND WindowInsetsAnimation
callbacks when the first composable reads an inset, and tears both down
when the last reader goes away (WindowInsetsHolder.increment /
decrementAccessors). The immersive stream reads no insets at all — it is
a bare full-screen surface — so entering one dropped the reader count to
zero right in the middle of the hide(systemBars()) animation StreamScreen
had just started. With the animation callback gone, that animation's
onEnd never arrived, so the listener kept runningAnimation = true for the
rest of the process, and from then on every onApplyWindowInsets was
swallowed (it defers to an onProgress that can no longer come). The
values froze at the last animation frame — landscape, bars hidden — and
that is what the menus got when they came back.
Hold one inset reader at the root for the activity's whole life: the
listeners now survive the stream, the landscape lock and the bar
animations, and every animation gets its onEnd. It subscribes to no inset
VALUE, so it costs nothing per frame. As a backstop, request a fresh
insets pass from onConfigurationChanged — the activity declares
configChanges=orientation|screenSize, so a rotation re-lays out in place
and a dropped dispatch would otherwise go unnoticed until the layout is
already wrong on screen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>