Dialog show/hide lyrics

This commit is contained in:
Damontecres 2026-03-03 21:28:30 -05:00
parent 59a0bee0a7
commit 18e3268638
No known key found for this signature in database
6 changed files with 102 additions and 6 deletions

View file

@ -68,6 +68,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.serializer.toUUID
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber
import javax.inject.Inject
@ -213,7 +214,12 @@ class MainActivity : AppCompatActivity() {
) {
val requestedDestination =
remember(intent) {
intent?.let(::extractDestination) ?: Destination.Home()
intent?.let(::extractDestination) ?: // Destination.Home()
// TODO
Destination.MediaItem(
itemId = "011ef0c7ca45684f2cd9dd3b020ca5f6".toUUID(),
type = BaseItemKind.MUSIC_ALBUM,
)
}
MainContent(
backStack = setupNavigationManager.backStack,

View file

@ -1,6 +1,7 @@
package com.github.damontecres.wholphin.ui.detail.music
import androidx.annotation.OptIn
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
@ -19,6 +21,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@ -31,6 +34,7 @@ import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
import androidx.media3.ui.compose.state.rememberPreviousButtonState
import androidx.media3.ui.compose.state.rememberRepeatButtonState
import androidx.media3.ui.compose.state.rememberShuffleButtonState
import androidx.tv.material3.Border
import androidx.tv.material3.ClickableSurfaceDefaults
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
@ -41,7 +45,9 @@ import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.components.Button
import com.github.damontecres.wholphin.ui.playback.ControllerViewState
import com.github.damontecres.wholphin.ui.playback.PlaybackAction
import com.github.damontecres.wholphin.ui.playback.PlaybackButton
import com.github.damontecres.wholphin.ui.playback.PlaybackButtons
import com.github.damontecres.wholphin.ui.playback.PlaybackDialogType
import com.github.damontecres.wholphin.ui.playback.buttonSpacing
import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
@ -53,6 +59,7 @@ fun NowPlayingButtons(
player: Player,
controllerViewState: ControllerViewState,
initialFocusRequester: FocusRequester,
onClickMore: () -> Unit,
modifier: Modifier = Modifier,
) {
val playPauseState = rememberPlayPauseButtonState(player)
@ -60,13 +67,30 @@ fun NowPlayingButtons(
val nextState = rememberNextButtonState(player)
val shuffleState = rememberShuffleButtonState(player)
val repeatState = rememberRepeatButtonState(player)
val onControllerInteraction = remember { { controllerViewState.pulseControls() } }
Box(
modifier = modifier,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(buttonSpacing),
modifier = Modifier.align(Alignment.CenterStart),
) {
PlaybackButton(
iconRes = R.drawable.baseline_more_vert_96,
onClick = {
onControllerInteraction.invoke()
onClickMore.invoke()
},
enabled = true,
onControllerInteraction = onControllerInteraction,
modifier = Modifier,
)
}
PlaybackButtons(
player = player,
initialFocusRequester = initialFocusRequester,
onControllerInteraction = { controllerViewState.pulseControls() },
onControllerInteraction = onControllerInteraction,
onPlaybackActionClick = {
when (it) {
PlaybackAction.Next -> {
@ -102,7 +126,7 @@ fun NowPlayingButtons(
onClick = {
shuffleState.onClick()
},
onControllerInteraction = { controllerViewState.pulseControls() },
onControllerInteraction = onControllerInteraction,
)
RepeatButton(
repeatMode = repeatState.repeatModeState,
@ -110,7 +134,7 @@ fun NowPlayingButtons(
onClick = {
repeatState.onClick()
},
onControllerInteraction = { controllerViewState.pulseControls() },
onControllerInteraction = onControllerInteraction,
)
}
}

View file

@ -59,6 +59,7 @@ fun NowPlayingOverlay(
current: AudioItem?,
queue: List<AudioItem>,
controllerViewState: ControllerViewState,
onClickMore: () -> Unit,
onMoveQueue: (Int, MoveDirection) -> Unit,
modifier: Modifier = Modifier,
) {
@ -122,6 +123,7 @@ fun NowPlayingOverlay(
player = player,
controllerViewState = controllerViewState,
initialFocusRequester = focusRequester,
onClickMore = onClickMore,
modifier =
Modifier
.fillMaxWidth()

View file

@ -1,5 +1,6 @@
package com.github.damontecres.wholphin.ui.detail.music
import android.view.Gravity
import androidx.activity.compose.BackHandler
import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibility
@ -21,23 +22,29 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.media3.common.util.UnstableApi
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.rememberQueue
import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.playback.BottomDialog
import com.github.damontecres.wholphin.ui.playback.BottomDialogItem
import com.github.damontecres.wholphin.ui.playback.PlaybackKeyHandler
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.LoadingState
@ -90,6 +97,10 @@ fun NowPlayingPage(
)
}
var showMoreDialog by remember { mutableStateOf(false) }
var lyricsActive by remember { mutableStateOf(true) }
var showDebugInfo by remember { mutableStateOf(true) }
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Box(modifier) {
@ -106,7 +117,7 @@ fun NowPlayingPage(
modifier = Modifier.fillMaxSize(),
) {
AnimatedVisibility(
visible = state.lyrics != null && state.lyrics?.lyrics?.isNotEmpty() == true,
visible = lyricsActive && state.hasLyrics,
enter = expandHorizontally(expandFrom = Alignment.Start),
exit = shrinkHorizontally(shrinkTowards = Alignment.Start),
modifier = Modifier,
@ -177,6 +188,7 @@ fun NowPlayingPage(
queue = queue,
controllerViewState = controllerViewState,
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
onClickMore = { showMoreDialog = true },
modifier =
Modifier
.background(AppColors.TransparentBlack50)
@ -187,4 +199,50 @@ fun NowPlayingPage(
LoadingPage(focusEnabled = false)
}
}
if (showMoreDialog) {
NowPlayingBottomDialog(
showDebugInfo = showDebugInfo,
lyricsActive = lyricsActive,
songHasLyrics = state.hasLyrics,
onDismissRequest = { showMoreDialog = false },
onClickShowDebug = { showDebugInfo = !showDebugInfo },
onClickLyrics = { lyricsActive = !lyricsActive },
)
}
}
@Composable
fun NowPlayingBottomDialog(
showDebugInfo: Boolean,
lyricsActive: Boolean,
songHasLyrics: Boolean,
onDismissRequest: () -> Unit,
onClickShowDebug: () -> Unit,
onClickLyrics: () -> Unit,
) {
val choices =
mapOf(
BottomDialogItem(
data = 0,
headline = stringResource(if (showDebugInfo) R.string.hide_debug_info else R.string.show_debug_info),
supporting = null,
) to onClickShowDebug,
BottomDialogItem(
data = 0,
headline = stringResource(if (lyricsActive) R.string.hide_lyrics else R.string.show_lyrics),
supporting = if (songHasLyrics) stringResource(R.string.song_has_lyrics) else null,
) to onClickLyrics,
)
BottomDialog(
choices = choices.keys.toList(),
onDismissRequest = {
onDismissRequest.invoke()
},
onSelectChoice = { _, choice ->
choices[choice]?.invoke()
onDismissRequest.invoke()
},
gravity = Gravity.START,
)
}

View file

@ -9,4 +9,6 @@ data class NowPlayingState(
val musicServiceState: MusicServiceState,
val lyrics: LyricDto? = null,
val currentLyricIndex: Int? = null,
)
) {
val hasLyrics: Boolean get() = lyrics != null && lyrics.lyrics.isNotEmpty()
}

View file

@ -733,6 +733,10 @@
<string name="popular_songs">Popular songs</string>
<string name="play_next">Play next</string>
<string name="music_videos">Music videos</string>
<string name="lyrics">Lyrics</string>
<string name="hide_lyrics">Hide lyrics</string>
<string name="show_lyrics">Show lyrics</string>
<string name="song_has_lyrics">Song has lyrics</string>
</resources>