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:
parent
6fbf6209a1
commit
826bbad49c
2 changed files with 44 additions and 18 deletions
|
|
@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
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.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
|
@ -48,6 +50,7 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
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.components.Button
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
|
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
|
||||||
|
|
@ -65,7 +68,6 @@ fun NowPlayingOverlay(
|
||||||
state: NowPlayingState,
|
state: NowPlayingState,
|
||||||
player: Player,
|
player: Player,
|
||||||
current: AudioItem?,
|
current: AudioItem?,
|
||||||
queue: List<AudioItem>,
|
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
onClickSong: (Int, AudioItem) -> Unit,
|
onClickSong: (Int, AudioItem) -> Unit,
|
||||||
onLongClickSong: (Int, AudioItem) -> Unit,
|
onLongClickSong: (Int, AudioItem) -> Unit,
|
||||||
|
|
@ -78,6 +80,7 @@ fun NowPlayingOverlay(
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
val currentItemFocusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
|
|
||||||
var queueHasFocus by remember { mutableStateOf(false) }
|
var queueHasFocus by remember { mutableStateOf(false) }
|
||||||
|
|
@ -89,13 +92,16 @@ fun NowPlayingOverlay(
|
||||||
},
|
},
|
||||||
animationSpec = tween(durationMillis = 500),
|
animationSpec = tween(durationMillis = 500),
|
||||||
)
|
)
|
||||||
val listState = rememberLazyListState()
|
val listState =
|
||||||
|
rememberLazyListState(
|
||||||
|
initialFirstVisibleItemIndex = state.musicServiceState.currentIndex,
|
||||||
|
)
|
||||||
var showButtons by remember { mutableStateOf(true) }
|
var showButtons by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
val firstFocusRequester = remember { FocusRequester() }
|
val firstFocusRequester = remember { FocusRequester() }
|
||||||
BackHandler(!showButtons) {
|
BackHandler(!showButtons) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
listState.animateScrollToItem(0)
|
listState.scrollToItem(0)
|
||||||
firstFocusRequester.tryRequestFocus()
|
firstFocusRequester.tryRequestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +147,12 @@ fun NowPlayingOverlay(
|
||||||
.align(Alignment.CenterHorizontally),
|
.align(Alignment.CenterHorizontally),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val queue =
|
||||||
|
rememberQueue(
|
||||||
|
player,
|
||||||
|
state.musicServiceState.queueVersion,
|
||||||
|
state.musicServiceState.queueSize,
|
||||||
|
)
|
||||||
if (queue.isEmpty()) {
|
if (queue.isEmpty()) {
|
||||||
Text("No items")
|
Text("No items")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -158,12 +170,16 @@ fun NowPlayingOverlay(
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
queueHasFocus = it.hasFocus
|
queueHasFocus = it.hasFocus
|
||||||
}.focusProperties {
|
}.focusProperties {
|
||||||
|
onEnter = {
|
||||||
|
currentItemFocusRequester.tryRequestFocus()
|
||||||
|
}
|
||||||
onExit = {
|
onExit = {
|
||||||
if (requestedFocusDirection == FocusDirection.Up) focusRequester.requestFocus()
|
if (requestedFocusDirection == FocusDirection.Up) focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
itemsIndexed(queue, key = { _, song -> song.key }) { index, song ->
|
itemsIndexed(queue, key = { _, song -> song.key }) { index, song ->
|
||||||
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier =
|
modifier =
|
||||||
|
|
@ -176,7 +192,8 @@ fun NowPlayingOverlay(
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.hasFocus) showButtons = index < 3
|
if (it.hasFocus) showButtons = index < 3
|
||||||
controllerViewState.pulseControls()
|
controllerViewState.pulseControls()
|
||||||
}.animateItem(),
|
}.animateItem()
|
||||||
|
.bringIntoViewRequester(bringIntoViewRequester),
|
||||||
) {
|
) {
|
||||||
SongListItem(
|
SongListItem(
|
||||||
title = song.title,
|
title = song.title,
|
||||||
|
|
@ -193,6 +210,9 @@ fun NowPlayingOverlay(
|
||||||
.ifElse(
|
.ifElse(
|
||||||
index == 0,
|
index == 0,
|
||||||
Modifier.focusRequester(firstFocusRequester),
|
Modifier.focusRequester(firstFocusRequester),
|
||||||
|
).ifElse(
|
||||||
|
index == state.musicServiceState.currentIndex,
|
||||||
|
Modifier.focusRequester(currentItemFocusRequester),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -203,12 +223,22 @@ fun NowPlayingOverlay(
|
||||||
MoveButton(
|
MoveButton(
|
||||||
icon = R.string.fa_caret_up,
|
icon = R.string.fa_caret_up,
|
||||||
enabled = index > 0,
|
enabled = index > 0,
|
||||||
onClick = { onMoveQueue.invoke(index, MoveDirection.UP) },
|
onClick = {
|
||||||
|
onMoveQueue.invoke(index, MoveDirection.UP)
|
||||||
|
scope.launch {
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
MoveButton(
|
MoveButton(
|
||||||
icon = R.string.fa_caret_down,
|
icon = R.string.fa_caret_down,
|
||||||
enabled = index < queue.lastIndex,
|
enabled = index < queue.lastIndex,
|
||||||
onClick = { onMoveQueue.invoke(index, MoveDirection.DOWN) },
|
onClick = {
|
||||||
|
onMoveQueue.invoke(index, MoveDirection.DOWN)
|
||||||
|
scope.launch {
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = { onClickMoreItem.invoke(index, song) },
|
onClick = { onClickMoreItem.invoke(index, song) },
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.ui.compose.state.rememberCurrentMediaItemState
|
||||||
import androidx.tv.material3.Button
|
import androidx.tv.material3.Button
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
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.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.BackdropStyle
|
import com.github.damontecres.wholphin.preferences.BackdropStyle
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
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.BasicDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
|
|
@ -91,13 +91,8 @@ fun NowPlayingPage(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val player = viewModel.player
|
val player = viewModel.player
|
||||||
val queue =
|
val currentMediaItem = rememberCurrentMediaItemState(player)
|
||||||
rememberQueue(
|
val current = (currentMediaItem.mediaItem?.localConfiguration?.tag as? AudioItem)
|
||||||
player,
|
|
||||||
state.musicServiceState.queueVersion,
|
|
||||||
state.musicServiceState.queueSize,
|
|
||||||
)
|
|
||||||
val current = queue.getOrNull(state.musicServiceState.currentIndex)
|
|
||||||
val viz by viewModel.viz.collectAsState()
|
val viz by viewModel.viz.collectAsState()
|
||||||
|
|
||||||
val controllerViewState = viewModel.controllerViewState
|
val controllerViewState = viewModel.controllerViewState
|
||||||
|
|
@ -152,10 +147,12 @@ fun NowPlayingPage(
|
||||||
LaunchedEffect(lyricsFocused) {
|
LaunchedEffect(lyricsFocused) {
|
||||||
if (lyricsFocused != null) {
|
if (lyricsFocused != null) {
|
||||||
controllerViewState.hideControls()
|
controllerViewState.hideControls()
|
||||||
|
delay(5.seconds)
|
||||||
|
if (!controllerViewState.controlsVisible) {
|
||||||
|
focusRequester.tryRequestFocus()
|
||||||
|
}
|
||||||
|
lyricsFocused = null
|
||||||
}
|
}
|
||||||
delay(5.seconds)
|
|
||||||
lyricsFocused = null
|
|
||||||
focusRequester.tryRequestFocus()
|
|
||||||
}
|
}
|
||||||
BackHandler(lyricsFocused != null) {
|
BackHandler(lyricsFocused != null) {
|
||||||
focusRequester.tryRequestFocus()
|
focusRequester.tryRequestFocus()
|
||||||
|
|
@ -351,7 +348,6 @@ fun NowPlayingPage(
|
||||||
state = state,
|
state = state,
|
||||||
player = player,
|
player = player,
|
||||||
current = current,
|
current = current,
|
||||||
queue = queue,
|
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
|
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
|
||||||
onClickMore = { showViewOptionsDialog = true },
|
onClickMore = { showViewOptionsDialog = true },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue