EARHORNMUSIC UPnP · Android TV · Phones · Android Auto · GNU/Linux
Release 0.5.0

LastContentFocus.kt

package dev.stave.ui.nav

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged

/**
 * Holds the [FocusRequester] of the most recently focused content row so
 * the Now Playing bar's Up gesture can restore focus to it — the user
 * returns to exactly where they were, not wherever Compose's spatial
 * resolver happens to pick.
 *
 * Screens that render content above the bar (Browse's list, Queue's
 * list, etc.) write their row FRs here via [Modifier.trackLastContentFocus]
 * on focus gain. The bar's `onUpFromBar` reads the current value and
 * calls `requestFocus()` on it; when the row has been disposed (scrolled
 * out of a LazyColumn) the call fails and the caller should fall back to
 * [androidx.compose.ui.focus.FocusManager.moveFocus].
 *
 * Default value is a no-op sink so code that reads the Local without a
 * provider (unlikely, but harmless) doesn't null-pointer.
 */
val LocalLastContentFocus =
    compositionLocalOf<MutableState<FocusRequester?>> { mutableStateOf(null) }

/**
 * Register [requester] as the "last focused content" when the modified
 * element gains focus. Pair with a real [Modifier.focusRequester] and a
 * focusable modifier (e.g. staveFocusable + clickable).
 */
fun Modifier.trackLastContentFocus(
    requester: FocusRequester,
    sink: MutableState<FocusRequester?>,
): Modifier = this
    .focusRequester(requester)
    .onFocusChanged { if (it.isFocused) sink.value = requester }