Fixes to Now Playing page (#1915)

## Description
Fixes several issues on the Now Playing page

- Keep controller focus after lyrics are focused
- Fix the queue not visually updating until recomposition
- The first focused queue shown/focused is now what's currently playing

### Related issues
Fixes #1821

### Testing
Emulator

## Screenshots
N/A, just behavior fixes

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-09-02 02:50:42 -04:00 • committed by GitHub
parent 6fbf6209a1
commit 826bbad49c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 18 deletions

View file

@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
@ -48,6 +50,7 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.AudioItem
import com.github.damontecres.wholphin.services.rememberQueue
import com.github.damontecres.wholphin.ui.components.Button
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
@ -65,7 +68,6 @@ fun NowPlayingOverlay(
state: NowPlayingState,
player: Player,
current: AudioItem?,
queue: List<AudioItem>,
controllerViewState: ControllerViewState,
onClickSong: (Int, AudioItem) -> Unit,
onLongClickSong: (Int, AudioItem) -> Unit,
@ -78,6 +80,7 @@ fun NowPlayingOverlay(
) {
val scope = rememberCoroutineScope()
val focusRequester = remember { FocusRequester() }
val currentItemFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
var queueHasFocus by remember { mutableStateOf(false) }
@ -89,13 +92,16 @@ fun NowPlayingOverlay(
},
animationSpec = tween(durationMillis = 500),
)
val listState = rememberLazyListState()
val listState =
rememberLazyListState(
initialFirstVisibleItemIndex = state.musicServiceState.currentIndex,
)
var showButtons by remember { mutableStateOf(true) }
val firstFocusRequester = remember { FocusRequester() }
BackHandler(!showButtons) {
scope.launch {
listState.animateScrollToItem(0)
listState.scrollToItem(0)
firstFocusRequester.tryRequestFocus()
}
}
@ -141,6 +147,12 @@ fun NowPlayingOverlay(
.align(Alignment.CenterHorizontally),
)
}
val queue =
rememberQueue(
player,
state.musicServiceState.queueVersion,
state.musicServiceState.queueSize,
)
if (queue.isEmpty()) {
Text("No items")
} else {
@ -158,12 +170,16 @@ fun NowPlayingOverlay(
.onFocusChanged {
queueHasFocus = it.hasFocus
}.focusProperties {
onEnter = {
currentItemFocusRequester.tryRequestFocus()
}
onExit = {
if (requestedFocusDirection == FocusDirection.Up) focusRequester.requestFocus()
}
},
) {
itemsIndexed(queue, key = { _, song -> song.key }) { index, song ->
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
@ -176,7 +192,8 @@ fun NowPlayingOverlay(
.onFocusChanged {
if (it.hasFocus) showButtons = index < 3
controllerViewState.pulseControls()
}.animateItem(),
}.animateItem()
.bringIntoViewRequester(bringIntoViewRequester),
) {
SongListItem(
title = song.title,
@ -193,6 +210,9 @@ fun NowPlayingOverlay(
.ifElse(
index == 0,
Modifier.focusRequester(firstFocusRequester),
).ifElse(
index == state.musicServiceState.currentIndex,
Modifier.focusRequester(currentItemFocusRequester),
),
)
Row(
@ -203,12 +223,22 @@ fun NowPlayingOverlay(
MoveButton(
icon = R.string.fa_caret_up,
enabled = index > 0,
onClick = { onMoveQueue.invoke(index, MoveDirection.UP) },
onClick = {
onMoveQueue.invoke(index, MoveDirection.UP)
scope.launch {
bringIntoViewRequester.bringIntoView()
}
},
)
MoveButton(
icon = R.string.fa_caret_down,
enabled = index < queue.lastIndex,
onClick = { onMoveQueue.invoke(index, MoveDirection.DOWN) },
onClick = {
onMoveQueue.invoke(index, MoveDirection.DOWN)
scope.launch {
bringIntoViewRequester.bringIntoView()
}
},
)
Button(
onClick = { onClickMoreItem.invoke(index, song) },

View file

@ -51,6 +51,7 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.compose.state.rememberCurrentMediaItemState
import androidx.tv.material3.Button
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
@ -60,7 +61,6 @@ import com.github.damontecres.wholphin.data.model.AudioItem
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.BackdropStyle
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.rememberQueue
import com.github.damontecres.wholphin.ui.components.BasicDialog
import com.github.damontecres.wholphin.ui.components.ContextMenu
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
@ -91,13 +91,8 @@ fun NowPlayingPage(
val context = LocalContext.current
val state by viewModel.state.collectAsState()
val player = viewModel.player
val queue =
rememberQueue(
player,
state.musicServiceState.queueVersion,
state.musicServiceState.queueSize,
)
val current = queue.getOrNull(state.musicServiceState.currentIndex)
val currentMediaItem = rememberCurrentMediaItemState(player)
val current = (currentMediaItem.mediaItem?.localConfiguration?.tag as? AudioItem)
val viz by viewModel.viz.collectAsState()
val controllerViewState = viewModel.controllerViewState
@ -152,10 +147,12 @@ fun NowPlayingPage(
LaunchedEffect(lyricsFocused) {
if (lyricsFocused != null) {
controllerViewState.hideControls()
delay(5.seconds)
if (!controllerViewState.controlsVisible) {
focusRequester.tryRequestFocus()
}
lyricsFocused = null
}
delay(5.seconds)
lyricsFocused = null
focusRequester.tryRequestFocus()
}
BackHandler(lyricsFocused != null) {
focusRequester.tryRequestFocus()
@ -351,7 +348,6 @@ fun NowPlayingPage(
state = state,
player = player,
current = current,
queue = queue,
controllerViewState = controllerViewState,
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
onClickMore = { showViewOptionsDialog = true },