Skip to content

Fix mobile controls hiding immediately when tapped after first play - #139

Merged
MattiasBuelens merged 2 commits into
developfrom
bugfix/controls-visibility-stale-state
Oct 20, 2026
Merged

MattiasBuelens merged 2 commits into
developfrom
bugfix/controls-visibility-stale-state

Conversation

@MattiasBuelens

@MattiasBuelens MattiasBuelens commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • After first play, once the controls auto-hide, tapping the mobile player showed the controls on press and hid them again on release.
  • Cause: rememberControlsVisibility used remember(firstPlay, paused, forceVisible) { derivedStateOf { ... } }, so it built a new visible State every time one of those inputs changed. toggleControlsOnTap uses pointerInput(Unit), so it keeps whichever State existed at the first pointer event, typically the tap on play, before firstPlay flips. In that stale state "not played yet" means "visible", so it reports true forever, and every tap ends in hideControls().
  • Fix: read the inputs through rememberUpdatedState and keep a single derivedStateOf in a plain remember { }, so visible is one State for the life of the composable.
  • Hardening: toggleControlsOnTap now wraps its State and callbacks in rememberUpdatedState, so its pointerInput(Unit) handler always uses the latest values even if a caller passes a new State. It's now a @Composable modifier factory, which is fine because it's internal and has one call site.
  • The regression came in with 0089217 ("Share UIController core between mobile and TV"). That commit isn't on main or in any release, so no changelog entry.

Test plan

  • New ControlsVisibilityTest (6 tests): State stays the same across input changes, plus the ready delay, show/hide-delay, hide(), paused and forceVisible behaviour
  • New UIControllerTapTest: tap, play, auto-hide, tap shows the controls and they stay, tap again hides them
  • New ToggleControlsOnTapTest (3 tests): the handler uses a swapped-in State and swapped-in callbacks, plus the existing tap-to-show/tap-to-hide behaviour
  • The regression, tap and swap tests fail without the fixes and pass with them
  • ./gradlew :ui:testDebugUnitTest :ui-common:testDebugUnitTest :ui-tv:testDebugUnitTest
  • ./gradlew :ui:lintDebug: no new warnings

Generated with Devin

rememberControlsVisibility recreated its visible State whenever firstPlay,
paused or forceVisible changed, but the mobile tap handler keeps the State
it saw on the first pointer event. After first play, that stale state always
reported the controls as visible, so tapping hidden controls showed them on
press and immediately hid them again on release.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@MattiasBuelens MattiasBuelens added the bug Something isn't working label Oct 20, 2026
pointerInput(Unit) keeps the State and callbacks it captured when the
gesture handler first launched. Wrap them in rememberUpdatedState so the
handler always uses the latest values, even if callers pass a new State.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@MattiasBuelens
MattiasBuelens merged commit c2f7e46 into develop Oct 20, 2026
1 of 2 checks passed
@MattiasBuelens
MattiasBuelens deleted the bugfix/controls-visibility-stale-state branch October 20, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant