Show lyrics menu even when the fetch fails (#221)
This commit is contained in:
@@ -57,15 +57,16 @@ import it.vfsfitvnm.vimusic.ui.components.themed.Menu
|
|||||||
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.PureBlackColorPalette
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.DefaultDarkColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.DefaultDarkColorPalette
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.PureBlackColorPalette
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.onOverlayShimmer
|
import it.vfsfitvnm.vimusic.ui.styling.onOverlayShimmer
|
||||||
import it.vfsfitvnm.vimusic.utils.SynchronizedLyrics
|
import it.vfsfitvnm.vimusic.utils.SynchronizedLyrics
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.color
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
import it.vfsfitvnm.vimusic.utils.isShowingSynchronizedLyricsKey
|
import it.vfsfitvnm.vimusic.utils.isShowingSynchronizedLyricsKey
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
|
import it.vfsfitvnm.vimusic.utils.relaunchableEffect
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.verticalFadingEdge
|
import it.vfsfitvnm.vimusic.utils.verticalFadingEdge
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
@@ -89,29 +90,22 @@ fun Lyrics(
|
|||||||
nestedScrollConnectionProvider: () -> NestedScrollConnection,
|
nestedScrollConnectionProvider: () -> NestedScrollConnection,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isDisplayed,
|
visible = isDisplayed,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
var isShowingSynchronizedLyrics by rememberPreference(isShowingSynchronizedLyricsKey, false)
|
var isShowingSynchronizedLyrics by rememberPreference(isShowingSynchronizedLyricsKey, false)
|
||||||
|
|
||||||
var isLoading by remember(mediaId, isShowingSynchronizedLyrics) {
|
var state by remember(mediaId, isShowingSynchronizedLyrics) {
|
||||||
mutableStateOf(false)
|
mutableStateOf(LyricsState())
|
||||||
}
|
}
|
||||||
|
|
||||||
var isEditingLyrics by remember(mediaId, isShowingSynchronizedLyrics) {
|
val fetchLyrics = relaunchableEffect(mediaId, isShowingSynchronizedLyrics) {
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var lyrics by remember(mediaId, isShowingSynchronizedLyrics) {
|
|
||||||
mutableStateOf<String?>(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(mediaId, isShowingSynchronizedLyrics) {
|
|
||||||
if (isShowingSynchronizedLyrics) {
|
if (isShowingSynchronizedLyrics) {
|
||||||
Database.synchronizedLyrics(mediaId)
|
Database.synchronizedLyrics(mediaId)
|
||||||
} else {
|
} else {
|
||||||
@@ -119,7 +113,7 @@ fun Lyrics(
|
|||||||
}.distinctUntilChanged().map flowMap@{ lyrics ->
|
}.distinctUntilChanged().map flowMap@{ lyrics ->
|
||||||
if (lyrics != null) return@flowMap lyrics
|
if (lyrics != null) return@flowMap lyrics
|
||||||
|
|
||||||
isLoading = true
|
state = state.copy(isLoading = true)
|
||||||
|
|
||||||
if (isShowingSynchronizedLyrics) {
|
if (isShowingSynchronizedLyrics) {
|
||||||
val mediaMetadata = mediaMetadataProvider()
|
val mediaMetadata = mediaMetadataProvider()
|
||||||
@@ -134,32 +128,33 @@ fun Lyrics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KuGou.lyrics(mediaMetadata.artist?.toString() ?: "", mediaMetadata.title?.toString() ?: "", duration / 1000)?.map {
|
KuGou.lyrics(
|
||||||
it?.value
|
artist = mediaMetadata.artist?.toString() ?: "",
|
||||||
}
|
title = mediaMetadata.title?.toString() ?: "",
|
||||||
|
duration = duration / 1000
|
||||||
|
)?.map { it?.value }
|
||||||
} else {
|
} else {
|
||||||
YouTube.next(mediaId, null)?.map { nextResult -> nextResult.lyrics?.text()?.getOrNull() }
|
YouTube.next(mediaId, null)
|
||||||
|
?.map { nextResult -> nextResult.lyrics?.text()?.getOrNull() }
|
||||||
}?.map { newLyrics ->
|
}?.map { newLyrics ->
|
||||||
onLyricsUpdate(isShowingSynchronizedLyrics, mediaId, newLyrics ?: "")
|
onLyricsUpdate(isShowingSynchronizedLyrics, mediaId, newLyrics ?: "")
|
||||||
isLoading = false
|
state = state.copy(isLoading = false)
|
||||||
return@flowMap newLyrics ?: ""
|
return@flowMap newLyrics ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = false
|
state = state.copy(isLoading = false)
|
||||||
null
|
null
|
||||||
}.flowOn(Dispatchers.IO).collect { lyrics = it }
|
}.flowOn(Dispatchers.IO).collect { state = state.copy(lyrics = it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditingLyrics) {
|
if (state.isEditing) {
|
||||||
TextFieldDialog(
|
TextFieldDialog(
|
||||||
hintText = "Enter the lyrics",
|
hintText = "Enter the lyrics",
|
||||||
initialTextInput = lyrics ?: "",
|
initialTextInput = state.lyrics ?: "",
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
maxLines = 10,
|
maxLines = 10,
|
||||||
isTextInputValid = { true },
|
isTextInputValid = { true },
|
||||||
onDismiss = {
|
onDismiss = { state = state.copy(isEditing = false) },
|
||||||
isEditingLyrics = false
|
|
||||||
},
|
|
||||||
onDone = {
|
onDone = {
|
||||||
query {
|
query {
|
||||||
if (isShowingSynchronizedLyrics) {
|
if (isShowingSynchronizedLyrics) {
|
||||||
@@ -178,16 +173,14 @@ fun Lyrics(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onTap = {
|
onTap = { onDismiss() }
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color.Black.copy(0.8f))
|
.background(Color.Black.copy(0.8f))
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = !isLoading && lyrics == null,
|
visible = !state.isLoading && state.lyrics == null,
|
||||||
enter = slideInVertically { -it },
|
enter = slideInVertically { -it },
|
||||||
exit = slideOutVertically { -it },
|
exit = slideOutVertically { -it },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -204,7 +197,7 @@ fun Lyrics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = lyrics?.let(String::isEmpty) ?: false,
|
visible = state.lyrics?.let(String::isEmpty) ?: false,
|
||||||
enter = slideInVertically { -it },
|
enter = slideInVertically { -it },
|
||||||
exit = slideOutVertically { -it },
|
exit = slideOutVertically { -it },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -220,7 +213,7 @@ fun Lyrics(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (state.isLoading) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -235,11 +228,12 @@ fun Lyrics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lyrics?.let { lyrics ->
|
state.lyrics?.let { lyrics ->
|
||||||
if (lyrics.isNotEmpty() && lyrics != ".") {
|
if (lyrics.isNotEmpty() && lyrics != ".") {
|
||||||
if (isShowingSynchronizedLyrics) {
|
if (isShowingSynchronizedLyrics) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val player = LocalPlayerServiceBinder.current?.player ?: return@AnimatedVisibility
|
val player = LocalPlayerServiceBinder.current?.player
|
||||||
|
?: return@AnimatedVisibility
|
||||||
|
|
||||||
val synchronizedLyrics = remember(lyrics) {
|
val synchronizedLyrics = remember(lyrics) {
|
||||||
SynchronizedLyrics(KuGou.Lyrics(lyrics).sentences) {
|
SynchronizedLyrics(KuGou.Lyrics(lyrics).sentences) {
|
||||||
@@ -247,15 +241,20 @@ fun Lyrics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState(synchronizedLyrics.index, with (density) { size.roundToPx() } / 6)
|
val lazyListState = rememberLazyListState(
|
||||||
|
synchronizedLyrics.index,
|
||||||
|
with(density) { size.roundToPx() } / 6)
|
||||||
|
|
||||||
LaunchedEffect(synchronizedLyrics) {
|
LaunchedEffect(synchronizedLyrics) {
|
||||||
val center = with (density) { size.roundToPx() } / 6
|
val center = with(density) { size.roundToPx() } / 6
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(50)
|
delay(50)
|
||||||
if (synchronizedLyrics.update()) {
|
if (synchronizedLyrics.update()) {
|
||||||
lazyListState.animateScrollToItem(synchronizedLyrics.index, center)
|
lazyListState.animateScrollToItem(
|
||||||
|
synchronizedLyrics.index,
|
||||||
|
center
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,89 +288,101 @@ fun Lyrics(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val menuState = LocalMenuState.current
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ellipsis_horizontal),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(DefaultDarkColorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.clickable {
|
||||||
|
menuState.display {
|
||||||
|
Menu {
|
||||||
|
MenuEntry(
|
||||||
|
icon = R.drawable.time,
|
||||||
|
text = "Show ${if (isShowingSynchronizedLyrics) "un" else ""}synchronized lyrics",
|
||||||
|
secondaryText = if (isShowingSynchronizedLyrics) null else "Provided by kugou.com",
|
||||||
|
onClick = {
|
||||||
|
menuState.hide()
|
||||||
|
isShowingSynchronizedLyrics =
|
||||||
|
!isShowingSynchronizedLyrics
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Image(
|
MenuEntry(
|
||||||
painter = painterResource(R.drawable.ellipsis_horizontal),
|
icon = R.drawable.pencil,
|
||||||
contentDescription = null,
|
text = "Edit lyrics",
|
||||||
colorFilter = ColorFilter.tint(DefaultDarkColorPalette.text),
|
onClick = {
|
||||||
modifier = Modifier
|
menuState.hide()
|
||||||
.padding(all = 4.dp)
|
state = state.copy(isEditing = true)
|
||||||
.clickable {
|
}
|
||||||
menuState.display {
|
)
|
||||||
Menu {
|
|
||||||
MenuEntry(
|
|
||||||
icon = R.drawable.time,
|
|
||||||
text = "Show ${if (isShowingSynchronizedLyrics) "un" else ""}synchronized lyrics",
|
|
||||||
secondaryText = if (isShowingSynchronizedLyrics) null else "Provided by kugou.com",
|
|
||||||
onClick = {
|
|
||||||
menuState.hide()
|
|
||||||
isShowingSynchronizedLyrics = !isShowingSynchronizedLyrics
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.pencil,
|
icon = R.drawable.search,
|
||||||
text = "Edit lyrics",
|
text = "Search lyrics online",
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
isEditingLyrics = true
|
val mediaMetadata = mediaMetadataProvider()
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
val intent =
|
||||||
icon = R.drawable.search,
|
Intent(Intent.ACTION_WEB_SEARCH).apply {
|
||||||
text = "Search lyrics online",
|
putExtra(
|
||||||
onClick = {
|
SearchManager.QUERY,
|
||||||
menuState.hide()
|
"${mediaMetadata.title} ${mediaMetadata.artist} lyrics"
|
||||||
val mediaMetadata = mediaMetadataProvider()
|
)
|
||||||
|
|
||||||
val intent =
|
|
||||||
Intent(Intent.ACTION_WEB_SEARCH).apply {
|
|
||||||
putExtra(
|
|
||||||
SearchManager.QUERY,
|
|
||||||
"${mediaMetadata.title} ${mediaMetadata.artist} lyrics"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent.resolveActivity(context.packageManager) != null) {
|
|
||||||
context.startActivity(intent)
|
|
||||||
} else {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
"No browser app found!",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
icon = R.drawable.download,
|
context.startActivity(intent)
|
||||||
text = "Fetch lyrics again",
|
} else {
|
||||||
onClick = {
|
Toast
|
||||||
menuState.hide()
|
.makeText(
|
||||||
|
context,
|
||||||
|
"No browser app found!",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
MenuEntry(
|
||||||
|
icon = R.drawable.download,
|
||||||
|
text = "Fetch lyrics again",
|
||||||
|
onClick = {
|
||||||
|
menuState.hide()
|
||||||
|
if (state.lyrics == null) {
|
||||||
|
fetchLyrics()
|
||||||
|
} else {
|
||||||
query {
|
query {
|
||||||
if (isShowingSynchronizedLyrics) {
|
if (isShowingSynchronizedLyrics) {
|
||||||
Database.updateSynchronizedLyrics(mediaId, null)
|
Database.updateSynchronizedLyrics(
|
||||||
|
mediaId,
|
||||||
|
null
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Database.updateLyrics(mediaId, null)
|
Database.updateLyrics(mediaId, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(all = 8.dp)
|
}
|
||||||
.size(20.dp)
|
.padding(all = 8.dp)
|
||||||
.align(Alignment.BottomEnd)
|
.size(20.dp)
|
||||||
)
|
.align(Alignment.BottomEnd)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class LyricsState(
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val isEditing: Boolean = false,
|
||||||
|
val lyrics: String? = ".",
|
||||||
|
)
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ fun relaunchableEffect(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@NonRestartableComposable
|
@NonRestartableComposable
|
||||||
fun relaunchableEffect2(
|
fun relaunchableEffect(
|
||||||
key1: Any?,
|
key1: Any?,
|
||||||
|
key2: Any?,
|
||||||
block: suspend CoroutineScope.() -> Unit
|
block: suspend CoroutineScope.() -> Unit
|
||||||
): RememberObserver {
|
): () -> Unit {
|
||||||
val applyContext = currentComposer.applyCoroutineContext
|
val applyContext = currentComposer.applyCoroutineContext
|
||||||
return remember(key1) { LaunchedEffectImpl(applyContext, block) }
|
val launchedEffect = remember(key1, key2) { LaunchedEffectImpl(applyContext, block) }
|
||||||
|
return launchedEffect::onRemembered
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user