mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Dialog show/hide lyrics
This commit is contained in:
parent
59a0bee0a7
commit
18e3268638
6 changed files with 102 additions and 6 deletions
|
|
@ -68,6 +68,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -213,7 +214,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
) {
|
) {
|
||||||
val requestedDestination =
|
val requestedDestination =
|
||||||
remember(intent) {
|
remember(intent) {
|
||||||
intent?.let(::extractDestination) ?: Destination.Home()
|
intent?.let(::extractDestination) ?: // Destination.Home()
|
||||||
|
// TODO
|
||||||
|
Destination.MediaItem(
|
||||||
|
itemId = "011ef0c7ca45684f2cd9dd3b020ca5f6".toUUID(),
|
||||||
|
type = BaseItemKind.MUSIC_ALBUM,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
MainContent(
|
MainContent(
|
||||||
backStack = setupNavigationManager.backStack,
|
backStack = setupNavigationManager.backStack,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.music
|
package com.github.damontecres.wholphin.ui.detail.music
|
||||||
|
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
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.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
@ -19,6 +21,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.rememberPreviousButtonState
|
||||||
import androidx.media3.ui.compose.state.rememberRepeatButtonState
|
import androidx.media3.ui.compose.state.rememberRepeatButtonState
|
||||||
import androidx.media3.ui.compose.state.rememberShuffleButtonState
|
import androidx.media3.ui.compose.state.rememberShuffleButtonState
|
||||||
|
import androidx.tv.material3.Border
|
||||||
import androidx.tv.material3.ClickableSurfaceDefaults
|
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
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.components.Button
|
||||||
import com.github.damontecres.wholphin.ui.playback.ControllerViewState
|
import com.github.damontecres.wholphin.ui.playback.ControllerViewState
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackAction
|
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.PlaybackButtons
|
||||||
|
import com.github.damontecres.wholphin.ui.playback.PlaybackDialogType
|
||||||
import com.github.damontecres.wholphin.ui.playback.buttonSpacing
|
import com.github.damontecres.wholphin.ui.playback.buttonSpacing
|
||||||
import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource
|
import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
|
|
@ -53,6 +59,7 @@ fun NowPlayingButtons(
|
||||||
player: Player,
|
player: Player,
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
initialFocusRequester: FocusRequester,
|
initialFocusRequester: FocusRequester,
|
||||||
|
onClickMore: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val playPauseState = rememberPlayPauseButtonState(player)
|
val playPauseState = rememberPlayPauseButtonState(player)
|
||||||
|
|
@ -60,13 +67,30 @@ fun NowPlayingButtons(
|
||||||
val nextState = rememberNextButtonState(player)
|
val nextState = rememberNextButtonState(player)
|
||||||
val shuffleState = rememberShuffleButtonState(player)
|
val shuffleState = rememberShuffleButtonState(player)
|
||||||
val repeatState = rememberRepeatButtonState(player)
|
val repeatState = rememberRepeatButtonState(player)
|
||||||
|
|
||||||
|
val onControllerInteraction = remember { { controllerViewState.pulseControls() } }
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier,
|
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(
|
PlaybackButtons(
|
||||||
player = player,
|
player = player,
|
||||||
initialFocusRequester = initialFocusRequester,
|
initialFocusRequester = initialFocusRequester,
|
||||||
onControllerInteraction = { controllerViewState.pulseControls() },
|
onControllerInteraction = onControllerInteraction,
|
||||||
onPlaybackActionClick = {
|
onPlaybackActionClick = {
|
||||||
when (it) {
|
when (it) {
|
||||||
PlaybackAction.Next -> {
|
PlaybackAction.Next -> {
|
||||||
|
|
@ -102,7 +126,7 @@ fun NowPlayingButtons(
|
||||||
onClick = {
|
onClick = {
|
||||||
shuffleState.onClick()
|
shuffleState.onClick()
|
||||||
},
|
},
|
||||||
onControllerInteraction = { controllerViewState.pulseControls() },
|
onControllerInteraction = onControllerInteraction,
|
||||||
)
|
)
|
||||||
RepeatButton(
|
RepeatButton(
|
||||||
repeatMode = repeatState.repeatModeState,
|
repeatMode = repeatState.repeatModeState,
|
||||||
|
|
@ -110,7 +134,7 @@ fun NowPlayingButtons(
|
||||||
onClick = {
|
onClick = {
|
||||||
repeatState.onClick()
|
repeatState.onClick()
|
||||||
},
|
},
|
||||||
onControllerInteraction = { controllerViewState.pulseControls() },
|
onControllerInteraction = onControllerInteraction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ fun NowPlayingOverlay(
|
||||||
current: AudioItem?,
|
current: AudioItem?,
|
||||||
queue: List<AudioItem>,
|
queue: List<AudioItem>,
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
|
onClickMore: () -> Unit,
|
||||||
onMoveQueue: (Int, MoveDirection) -> Unit,
|
onMoveQueue: (Int, MoveDirection) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -122,6 +123,7 @@ fun NowPlayingOverlay(
|
||||||
player = player,
|
player = player,
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
initialFocusRequester = focusRequester,
|
initialFocusRequester = focusRequester,
|
||||||
|
onClickMore = onClickMore,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.music
|
package com.github.damontecres.wholphin.ui.detail.music
|
||||||
|
|
||||||
|
import android.view.Gravity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
|
@ -21,23 +22,29 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
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.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
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.services.rememberQueue
|
||||||
import com.github.damontecres.wholphin.ui.AppColors
|
import com.github.damontecres.wholphin.ui.AppColors
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
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.playback.PlaybackKeyHandler
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
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() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
Box(modifier) {
|
Box(modifier) {
|
||||||
|
|
@ -106,7 +117,7 @@ fun NowPlayingPage(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = state.lyrics != null && state.lyrics?.lyrics?.isNotEmpty() == true,
|
visible = lyricsActive && state.hasLyrics,
|
||||||
enter = expandHorizontally(expandFrom = Alignment.Start),
|
enter = expandHorizontally(expandFrom = Alignment.Start),
|
||||||
exit = shrinkHorizontally(shrinkTowards = Alignment.Start),
|
exit = shrinkHorizontally(shrinkTowards = Alignment.Start),
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
|
|
@ -177,6 +188,7 @@ fun NowPlayingPage(
|
||||||
queue = queue,
|
queue = queue,
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
|
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
|
||||||
|
onClickMore = { showMoreDialog = true },
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.background(AppColors.TransparentBlack50)
|
.background(AppColors.TransparentBlack50)
|
||||||
|
|
@ -187,4 +199,50 @@ fun NowPlayingPage(
|
||||||
LoadingPage(focusEnabled = false)
|
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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,6 @@ data class NowPlayingState(
|
||||||
val musicServiceState: MusicServiceState,
|
val musicServiceState: MusicServiceState,
|
||||||
val lyrics: LyricDto? = null,
|
val lyrics: LyricDto? = null,
|
||||||
val currentLyricIndex: Int? = null,
|
val currentLyricIndex: Int? = null,
|
||||||
)
|
) {
|
||||||
|
val hasLyrics: Boolean get() = lyrics != null && lyrics.lyrics.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -733,6 +733,10 @@
|
||||||
<string name="popular_songs">Popular songs</string>
|
<string name="popular_songs">Popular songs</string>
|
||||||
<string name="play_next">Play next</string>
|
<string name="play_next">Play next</string>
|
||||||
<string name="music_videos">Music videos</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>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue