diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index e2afbfa0..f9a1746b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -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, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingButtons.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingButtons.kt index 8726c786..13fd85f0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingButtons.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingButtons.kt @@ -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, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingOverlay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingOverlay.kt index fa1ca473..2f1acea2 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingOverlay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingOverlay.kt @@ -59,6 +59,7 @@ fun NowPlayingOverlay( current: AudioItem?, queue: List, 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() diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingPage.kt index 1a296070..d940bf69 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingPage.kt @@ -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, + ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingState.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingState.kt index c2bda912..de285169 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingState.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/NowPlayingState.kt @@ -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() +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0c1793a..9e7ec68f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -733,6 +733,10 @@ Popular songs Play next Music videos + Lyrics + Hide lyrics + Show lyrics + Song has lyrics