Add search lyrics online and edit lyrics features

This commit is contained in:
vfsfitvnm
2022-06-15 16:46:49 +02:00
parent 3aeeb6c601
commit 32fdbf437c
3 changed files with 158 additions and 44 deletions

View File

@@ -16,6 +16,7 @@ import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
@@ -27,6 +28,7 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import it.vfsfitvnm.vimusic.ui.components.ChunkyButton import it.vfsfitvnm.vimusic.ui.components.ChunkyButton
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
@@ -42,6 +44,8 @@ fun TextFieldDialog(
cancelText: String = "Cancel", cancelText: String = "Cancel",
doneText: String = "Done", doneText: String = "Done",
initialTextInput: String = "", initialTextInput: String = "",
singleLine: Boolean = true,
maxLines: Int = 1,
onCancel: () -> Unit = onDismiss, onCancel: () -> Unit = onDismiss,
isTextInputValid: (String) -> Boolean = { it.isNotEmpty() } isTextInputValid: (String) -> Boolean = { it.isNotEmpty() }
) { ) {
@@ -70,8 +74,8 @@ fun TextFieldDialog(
textFieldValue = it textFieldValue = it
}, },
textStyle = typography.xs.semiBold.center, textStyle = typography.xs.semiBold.center,
singleLine = true, singleLine = singleLine,
maxLines = 1, maxLines = maxLines,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onDone = { onDone = {
@@ -106,6 +110,7 @@ fun TextFieldDialog(
}, },
modifier = Modifier modifier = Modifier
.padding(all = 16.dp) .padding(all = 16.dp)
.weight(weight = 1f, fill = false)
.focusRequester(focusRequester) .focusRequester(focusRequester)
) )
@@ -194,6 +199,7 @@ fun ConfirmationDialog(
} }
} }
@OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
inline fun DefaultDialog( inline fun DefaultDialog(
noinline onDismiss: () -> Unit, noinline onDismiss: () -> Unit,
@@ -201,7 +207,10 @@ inline fun DefaultDialog(
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
crossinline content: @Composable ColumnScope.() -> Unit crossinline content: @Composable ColumnScope.() -> Unit
) { ) {
Dialog(onDismissRequest = onDismiss) { Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Column( Column(
horizontalAlignment = horizontalAlignment, horizontalAlignment = horizontalAlignment,
modifier = modifier modifier = modifier

View File

@@ -0,0 +1,118 @@
package it.vfsfitvnm.vimusic.ui.views
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.Message
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.Outcome
@Composable
fun LyricsView(
lyricsOutcome: Outcome<String>,
onInitialize: () -> Unit,
onSearchOnline: () -> Unit,
onLyricsUpdate: (String) -> Unit,
nestedScrollConnectionProvider: () -> NestedScrollConnection,
) {
val typography = LocalTypography.current
var isEditingLyrics by remember {
mutableStateOf(false)
}
if (isEditingLyrics) {
TextFieldDialog(
hintText = "Enter the lyrics",
initialTextInput = lyricsOutcome.valueOrNull ?: "",
singleLine = false,
maxLines = Int.MAX_VALUE,
isTextInputValid = { true },
onDismiss = {
isEditingLyrics = false
},
onDone = onLyricsUpdate
)
}
OutcomeItem(
outcome = lyricsOutcome,
onInitialize = onInitialize,
onLoading = {
LyricsShimmer(
modifier = Modifier
.fillMaxSize()
.shimmer()
)
}
) { lyrics ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = 64.dp)
.nestedScroll(remember { nestedScrollConnectionProvider() })
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(vertical = 16.dp)
.padding(horizontal = 48.dp)
) {
if (lyrics.isEmpty()) {
Message(
text = "Lyrics not available",
icon = R.drawable.text,
)
} else {
BasicText(
text = lyrics,
style = typography.xs.center,
)
}
Row(
modifier = Modifier
.padding(top = 32.dp)
) {
BasicText(
text = "Search online",
style = typography.xs.secondary.copy(textDecoration = TextDecoration.Underline),
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = onSearchOnline
)
.padding(horizontal = 8.dp)
)
BasicText(
text = "Edit",
style = typography.xs.secondary.copy(textDecoration = TextDecoration.Underline),
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
isEditingLyrics = true
}
.padding(horizontal = 8.dp)
)
}
}
}
}

View File

@@ -1,5 +1,7 @@
package it.vfsfitvnm.vimusic.ui.views package it.vfsfitvnm.vimusic.ui.views
import android.app.SearchManager
import android.content.Intent
import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
@@ -9,30 +11,24 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.route.Route import it.vfsfitvnm.route.Route
import it.vfsfitvnm.route.RouteHandler import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.route.empty import it.vfsfitvnm.route.empty
import it.vfsfitvnm.route.rememberRoute import it.vfsfitvnm.route.rememberRoute
import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.models.Song
import it.vfsfitvnm.vimusic.ui.components.BottomSheet import it.vfsfitvnm.vimusic.ui.components.BottomSheet
import it.vfsfitvnm.vimusic.ui.components.BottomSheetState import it.vfsfitvnm.vimusic.ui.components.BottomSheetState
import it.vfsfitvnm.vimusic.ui.components.Message
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
import it.vfsfitvnm.vimusic.ui.screens.rememberLyricsRoute import it.vfsfitvnm.vimusic.ui.screens.rememberLyricsRoute
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
@@ -46,6 +42,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ExperimentalAnimationApi @ExperimentalAnimationApi
@Composable @Composable
fun PlayerBottomSheet( fun PlayerBottomSheet(
@@ -153,6 +150,10 @@ fun PlayerBottomSheet(
} }
} }
) { ) {
var lyricsOutcome by remember(song) {
mutableStateOf(song?.lyrics?.let { Outcome.Success(it) } ?: Outcome.Initial)
}
RouteHandler( RouteHandler(
route = route, route = route,
onRouteChanged = { onRouteChanged = {
@@ -174,15 +175,13 @@ fun PlayerBottomSheet(
.background(colorPalette.elevatedBackground) .background(colorPalette.elevatedBackground)
.fillMaxSize() .fillMaxSize()
) { ) {
var lyricsOutcome by remember(song) {
mutableStateOf(song?.lyrics?.let { Outcome.Success(it) } ?: Outcome.Initial)
}
lyricsRoute { lyricsRoute {
OutcomeItem( val context = LocalContext.current
outcome = lyricsOutcome,
LyricsView(
lyricsOutcome = lyricsOutcome,
nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
onInitialize = { onInitialize = {
println("onInitialize!!")
coroutineScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
lyricsOutcome = Outcome.Loading lyricsOutcome = Outcome.Loading
@@ -209,34 +208,22 @@ fun PlayerBottomSheet(
} }
} }
}, },
onLoading = { onSearchOnline = {
LyricsShimmer( player.mediaMetadata.let {
modifier = Modifier context.startActivity(Intent(Intent.ACTION_WEB_SEARCH).apply {
.shimmer() putExtra(
) SearchManager.QUERY,
"${it.title} ${it.artist} lyrics"
)
})
}
},
onLyricsUpdate = {
coroutineScope.launch(Dispatchers.IO) {
Database.update((song ?: Database.insert(player.mediaItem!!)).copy(lyrics = it))
}
} }
) { lyrics -> )
if (lyrics.isEmpty()) {
Message(
text = "Lyrics not available",
icon = R.drawable.text,
modifier = Modifier
.padding(top = 64.dp)
)
} else {
BasicText(
text = lyrics,
style = typography.xs.center,
modifier = Modifier
.padding(top = 64.dp)
.nestedScroll(remember { layoutState.nestedScrollConnection() })
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(vertical = 16.dp)
.padding(horizontal = 48.dp)
)
}
}
} }
host { host {