Add search lyrics online and edit lyrics features
This commit is contained in:
parent
3aeeb6c601
commit
32fdbf437c
|
@ -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
|
||||
|
|
118
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/LyricsView.kt
Normal file
118
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/LyricsView.kt
Normal 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue