Show lyrics menu even when the fetch fails (#221)

This commit is contained in:
vfsfitvnm 2022-08-17 11:41:10 +02:00
parent 1682228ece
commit 59f66c5b65
2 changed files with 122 additions and 109 deletions

View file

@ -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.TextFieldDialog
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.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.PureBlackColorPalette
import it.vfsfitvnm.vimusic.ui.styling.onOverlayShimmer
import it.vfsfitvnm.vimusic.utils.SynchronizedLyrics
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.isShowingSynchronizedLyricsKey
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.relaunchableEffect
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.verticalFadingEdge
import it.vfsfitvnm.youtubemusic.YouTube
@ -89,29 +90,22 @@ fun Lyrics(
nestedScrollConnectionProvider: () -> NestedScrollConnection,
modifier: Modifier = Modifier
) {
val (colorPalette, typography) = LocalAppearance.current
val context = LocalContext.current
AnimatedVisibility(
visible = isDisplayed,
enter = fadeIn(),
exit = fadeOut(),
) {
val (colorPalette, typography) = LocalAppearance.current
val context = LocalContext.current
val menuState = LocalMenuState.current
var isShowingSynchronizedLyrics by rememberPreference(isShowingSynchronizedLyricsKey, false)
var isLoading by remember(mediaId, isShowingSynchronizedLyrics) {
mutableStateOf(false)
var state by remember(mediaId, isShowingSynchronizedLyrics) {
mutableStateOf(LyricsState())
}
var isEditingLyrics by remember(mediaId, isShowingSynchronizedLyrics) {
mutableStateOf(false)
}
var lyrics by remember(mediaId, isShowingSynchronizedLyrics) {
mutableStateOf<String?>(".")
}
LaunchedEffect(mediaId, isShowingSynchronizedLyrics) {
val fetchLyrics = relaunchableEffect(mediaId, isShowingSynchronizedLyrics) {
if (isShowingSynchronizedLyrics) {
Database.synchronizedLyrics(mediaId)
} else {
@ -119,7 +113,7 @@ fun Lyrics(
}.distinctUntilChanged().map flowMap@{ lyrics ->
if (lyrics != null) return@flowMap lyrics
isLoading = true
state = state.copy(isLoading = true)
if (isShowingSynchronizedLyrics) {
val mediaMetadata = mediaMetadataProvider()
@ -134,32 +128,33 @@ fun Lyrics(
}
}
KuGou.lyrics(mediaMetadata.artist?.toString() ?: "", mediaMetadata.title?.toString() ?: "", duration / 1000)?.map {
it?.value
}
KuGou.lyrics(
artist = mediaMetadata.artist?.toString() ?: "",
title = mediaMetadata.title?.toString() ?: "",
duration = duration / 1000
)?.map { it?.value }
} else {
YouTube.next(mediaId, null)?.map { nextResult -> nextResult.lyrics?.text()?.getOrNull() }
YouTube.next(mediaId, null)
?.map { nextResult -> nextResult.lyrics?.text()?.getOrNull() }
}?.map { newLyrics ->
onLyricsUpdate(isShowingSynchronizedLyrics, mediaId, newLyrics ?: "")
isLoading = false
state = state.copy(isLoading = false)
return@flowMap newLyrics ?: ""
}
isLoading = false
state = state.copy(isLoading = false)
null
}.flowOn(Dispatchers.IO).collect { lyrics = it }
}.flowOn(Dispatchers.IO).collect { state = state.copy(lyrics = it) }
}
if (isEditingLyrics) {
if (state.isEditing) {
TextFieldDialog(
hintText = "Enter the lyrics",
initialTextInput = lyrics ?: "",
initialTextInput = state.lyrics ?: "",
singleLine = false,
maxLines = 10,
isTextInputValid = { true },
onDismiss = {
isEditingLyrics = false
},
onDismiss = { state = state.copy(isEditing = false) },
onDone = {
query {
if (isShowingSynchronizedLyrics) {
@ -178,16 +173,14 @@ fun Lyrics(
modifier = modifier
.pointerInput(Unit) {
detectTapGestures(
onTap = {
onDismiss()
}
onTap = { onDismiss() }
)
}
.fillMaxSize()
.background(Color.Black.copy(0.8f))
) {
AnimatedVisibility(
visible = !isLoading && lyrics == null,
visible = !state.isLoading && state.lyrics == null,
enter = slideInVertically { -it },
exit = slideOutVertically { -it },
modifier = Modifier
@ -204,7 +197,7 @@ fun Lyrics(
}
AnimatedVisibility(
visible = lyrics?.let(String::isEmpty) ?: false,
visible = state.lyrics?.let(String::isEmpty) ?: false,
enter = slideInVertically { -it },
exit = slideOutVertically { -it },
modifier = Modifier
@ -220,7 +213,7 @@ fun Lyrics(
)
}
if (isLoading) {
if (state.isLoading) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
@ -235,11 +228,12 @@ fun Lyrics(
}
}
} else {
lyrics?.let { lyrics ->
state.lyrics?.let { lyrics ->
if (lyrics.isNotEmpty() && lyrics != ".") {
if (isShowingSynchronizedLyrics) {
val density = LocalDensity.current
val player = LocalPlayerServiceBinder.current?.player ?: return@AnimatedVisibility
val player = LocalPlayerServiceBinder.current?.player
?: return@AnimatedVisibility
val synchronizedLyrics = remember(lyrics) {
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) {
val center = with (density) { size.roundToPx() } / 6
val center = with(density) { size.roundToPx() } / 6
while (isActive) {
delay(50)
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(
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
}
)
MenuEntry(
icon = R.drawable.pencil,
text = "Edit lyrics",
onClick = {
menuState.hide()
state = state.copy(isEditing = true)
}
)
MenuEntry(
icon = R.drawable.pencil,
text = "Edit lyrics",
onClick = {
menuState.hide()
isEditingLyrics = true
}
)
MenuEntry(
icon = R.drawable.search,
text = "Search lyrics online",
onClick = {
menuState.hide()
val mediaMetadata = mediaMetadataProvider()
MenuEntry(
icon = R.drawable.search,
text = "Search lyrics online",
onClick = {
menuState.hide()
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()
val intent =
Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(
SearchManager.QUERY,
"${mediaMetadata.title} ${mediaMetadata.artist} lyrics"
)
}
}
)
MenuEntry(
icon = R.drawable.download,
text = "Fetch lyrics again",
onClick = {
menuState.hide()
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
Toast
.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 {
if (isShowingSynchronizedLyrics) {
Database.updateSynchronizedLyrics(mediaId, null)
Database.updateSynchronizedLyrics(
mediaId,
null
)
} else {
Database.updateLyrics(mediaId, null)
}
}
}
)
}
}
)
}
}
.padding(all = 8.dp)
.size(20.dp)
.align(Alignment.BottomEnd)
)
}
}
.padding(all = 8.dp)
.size(20.dp)
.align(Alignment.BottomEnd)
)
}
}
}
}
private data class LyricsState(
val isLoading: Boolean = false,
val isEditing: Boolean = false,
val lyrics: String? = ".",
)

View file

@ -25,10 +25,12 @@ fun relaunchableEffect(
@Composable
@NonRestartableComposable
fun relaunchableEffect2(
fun relaunchableEffect(
key1: Any?,
key2: Any?,
block: suspend CoroutineScope.() -> Unit
): RememberObserver {
): () -> Unit {
val applyContext = currentComposer.applyCoroutineContext
return remember(key1) { LaunchedEffectImpl(applyContext, block) }
val launchedEffect = remember(key1, key2) { LaunchedEffectImpl(applyContext, block) }
return launchedEffect::onRemembered
}