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.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import it.vfsfitvnm.vimusic.ui.components.ChunkyButton
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
@ -42,6 +44,8 @@ fun TextFieldDialog(
cancelText: String = "Cancel",
doneText: String = "Done",
initialTextInput: String = "",
singleLine: Boolean = true,
maxLines: Int = 1,
onCancel: () -> Unit = onDismiss,
isTextInputValid: (String) -> Boolean = { it.isNotEmpty() }
) {
@ -70,8 +74,8 @@ fun TextFieldDialog(
textFieldValue = it
},
textStyle = typography.xs.semiBold.center,
singleLine = true,
maxLines = 1,
singleLine = singleLine,
maxLines = maxLines,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
@ -106,6 +110,7 @@ fun TextFieldDialog(
},
modifier = Modifier
.padding(all = 16.dp)
.weight(weight = 1f, fill = false)
.focusRequester(focusRequester)
)
@ -194,6 +199,7 @@ fun ConfirmationDialog(
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
inline fun DefaultDialog(
noinline onDismiss: () -> Unit,
@ -201,7 +207,10 @@ inline fun DefaultDialog(
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
crossinline content: @Composable ColumnScope.() -> Unit
) {
Dialog(onDismissRequest = onDismiss) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Column(
horizontalAlignment = horizontalAlignment,
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
import android.app.SearchManager
import android.content.Intent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
@ -9,30 +11,24 @@ import androidx.compose.foundation.background
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.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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 com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.route.Route
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.route.empty
import it.vfsfitvnm.route.rememberRoute
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Song
import it.vfsfitvnm.vimusic.ui.components.BottomSheet
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.screens.rememberLyricsRoute
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
@ -46,6 +42,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
fun PlayerBottomSheet(
@ -153,6 +150,10 @@ fun PlayerBottomSheet(
}
}
) {
var lyricsOutcome by remember(song) {
mutableStateOf(song?.lyrics?.let { Outcome.Success(it) } ?: Outcome.Initial)
}
RouteHandler(
route = route,
onRouteChanged = {
@ -174,15 +175,13 @@ fun PlayerBottomSheet(
.background(colorPalette.elevatedBackground)
.fillMaxSize()
) {
var lyricsOutcome by remember(song) {
mutableStateOf(song?.lyrics?.let { Outcome.Success(it) } ?: Outcome.Initial)
}
lyricsRoute {
OutcomeItem(
outcome = lyricsOutcome,
val context = LocalContext.current
LyricsView(
lyricsOutcome = lyricsOutcome,
nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
onInitialize = {
println("onInitialize!!")
coroutineScope.launch(Dispatchers.Main) {
lyricsOutcome = Outcome.Loading
@ -209,34 +208,22 @@ fun PlayerBottomSheet(
}
}
},
onLoading = {
LyricsShimmer(
modifier = Modifier
.shimmer()
)
onSearchOnline = {
player.mediaMetadata.let {
context.startActivity(Intent(Intent.ACTION_WEB_SEARCH).apply {
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 {