Add swipe gesture to PlayerBottomSheet (#79)
This commit is contained in:
@@ -3,11 +3,9 @@ package it.vfsfitvnm.vimusic.ui.views
|
|||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
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
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.with
|
|
||||||
import androidx.compose.foundation.background
|
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
|
||||||
@@ -21,23 +19,19 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
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.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
import it.vfsfitvnm.vimusic.models.Song
|
import it.vfsfitvnm.vimusic.models.Song
|
||||||
import it.vfsfitvnm.vimusic.query
|
import it.vfsfitvnm.vimusic.query
|
||||||
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.screens.rememberLyricsRoute
|
import it.vfsfitvnm.vimusic.ui.components.HorizontalTabPager
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.rememberTabPagerState
|
||||||
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
|
||||||
import it.vfsfitvnm.vimusic.utils.PlayerState
|
import it.vfsfitvnm.vimusic.utils.PlayerState
|
||||||
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.medium
|
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -58,9 +52,7 @@ fun PlayerBottomSheet(
|
|||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val lyricsRoute = rememberLyricsRoute()
|
val tabPagerState = rememberTabPagerState(initialPageIndex = 0, pageCount = 2)
|
||||||
|
|
||||||
var route by rememberRoute()
|
|
||||||
|
|
||||||
var nextResult by remember(playerState?.mediaItem?.mediaId) {
|
var nextResult by remember(playerState?.mediaItem?.mediaId) {
|
||||||
mutableStateOf<Result<YouTube.NextResult>?>(null)
|
mutableStateOf<Result<YouTube.NextResult>?>(null)
|
||||||
@@ -100,10 +92,10 @@ fun PlayerBottomSheet(
|
|||||||
@Composable
|
@Composable
|
||||||
fun Element(
|
fun Element(
|
||||||
text: String,
|
text: String,
|
||||||
targetRoute: Route?
|
pageIndex: Int
|
||||||
) {
|
) {
|
||||||
val color by animateColorAsState(
|
val color by animateColorAsState(
|
||||||
if (targetRoute == route) {
|
if (tabPagerState.pageIndex == pageIndex) {
|
||||||
colorPalette.text
|
colorPalette.text
|
||||||
} else {
|
} else {
|
||||||
colorPalette.textDisabled
|
colorPalette.textDisabled
|
||||||
@@ -111,7 +103,7 @@ fun PlayerBottomSheet(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val scale by animateFloatAsState(
|
val scale by animateFloatAsState(
|
||||||
if (targetRoute == route) {
|
if (pageIndex == pageIndex) {
|
||||||
1f
|
1f
|
||||||
} else {
|
} else {
|
||||||
0.9f
|
0.9f
|
||||||
@@ -120,17 +112,22 @@ fun PlayerBottomSheet(
|
|||||||
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = text,
|
text = text,
|
||||||
style = typography.xs.medium.color(color).center,
|
style = typography.xs.color(color).center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rememberRipple(bounded = true),
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
onClick = {
|
||||||
route = targetRoute
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
layoutState.expand()
|
||||||
layoutState.expand()
|
if (layoutState.isCollapsed) {
|
||||||
|
tabPagerState.pageIndex = pageIndex
|
||||||
|
} else {
|
||||||
|
tabPagerState.animateScrollTo(pageIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
.scale(scale)
|
.scale(scale)
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
@@ -139,116 +136,102 @@ fun PlayerBottomSheet(
|
|||||||
|
|
||||||
Element(
|
Element(
|
||||||
text = "UP NEXT",
|
text = "UP NEXT",
|
||||||
targetRoute = null
|
pageIndex = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
Element(
|
Element(
|
||||||
text = "LYRICS",
|
text = "LYRICS",
|
||||||
targetRoute = lyricsRoute
|
pageIndex = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
var lyricsResult by remember(song) {
|
HorizontalTabPager(
|
||||||
mutableStateOf(song?.lyrics?.let { Result.success(it) })
|
state = tabPagerState,
|
||||||
}
|
|
||||||
|
|
||||||
RouteHandler(
|
|
||||||
route = route,
|
|
||||||
onRouteChanged = {
|
|
||||||
route = it
|
|
||||||
},
|
|
||||||
handleBackPress = false,
|
|
||||||
transitionSpec = {
|
|
||||||
when (targetState.route) {
|
|
||||||
lyricsRoute -> slideIntoContainer(AnimatedContentScope.SlideDirection.Left) with
|
|
||||||
slideOutOfContainer(AnimatedContentScope.SlideDirection.Left)
|
|
||||||
else -> when (initialState.route) {
|
|
||||||
lyricsRoute -> slideIntoContainer(AnimatedContentScope.SlideDirection.Right) with
|
|
||||||
slideOutOfContainer(AnimatedContentScope.SlideDirection.Right)
|
|
||||||
else -> empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.elevatedBackground)
|
.background(colorPalette.elevatedBackground)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) { index ->
|
||||||
lyricsRoute {
|
when (index) {
|
||||||
val player = LocalPlayerServiceBinder.current?.player
|
0 -> {
|
||||||
|
CurrentPlaylistView(
|
||||||
|
playerState = playerState,
|
||||||
|
layoutState = layoutState,
|
||||||
|
onGlobalRouteEmitted = onGlobalRouteEmitted,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 64.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
val player = LocalPlayerServiceBinder.current?.player
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
val context = LocalContext.current
|
var lyricsResult by remember(song) {
|
||||||
|
mutableStateOf(song?.lyrics?.let { Result.success(it) })
|
||||||
|
}
|
||||||
|
|
||||||
LyricsView(
|
LyricsView(
|
||||||
lyrics = lyricsResult?.getOrNull(),
|
lyrics = lyricsResult?.getOrNull(),
|
||||||
nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
|
nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
|
||||||
onInitialize = {
|
onInitialize = {
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
val mediaItem = player?.currentMediaItem!!
|
val mediaItem = player?.currentMediaItem!!
|
||||||
|
|
||||||
if (nextResult == null) {
|
if (nextResult == null) {
|
||||||
val mediaItemIndex = player.currentMediaItemIndex
|
val mediaItemIndex = player.currentMediaItemIndex
|
||||||
|
|
||||||
nextResult = withContext(Dispatchers.IO) {
|
nextResult = withContext(Dispatchers.IO) {
|
||||||
YouTube.next(
|
YouTube.next(
|
||||||
mediaItem.mediaId,
|
mediaItem.mediaId,
|
||||||
mediaItem.mediaMetadata.extras?.getString("playlistId"),
|
mediaItem.mediaMetadata.extras?.getString("playlistId"),
|
||||||
mediaItemIndex
|
mediaItemIndex
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lyricsResult = nextResult?.map { nextResult ->
|
||||||
|
nextResult.lyrics?.text()?.getOrNull() ?: ""
|
||||||
|
}?.map { lyrics ->
|
||||||
|
query {
|
||||||
|
song?.let {
|
||||||
|
Database.update(song.copy(lyrics = lyrics))
|
||||||
|
} ?: Database.insert(mediaItem) { song ->
|
||||||
|
song.copy(lyrics = lyrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lyrics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onSearchOnline = {
|
||||||
|
val mediaMetadata = player?.mediaMetadata ?: return@LyricsView
|
||||||
|
|
||||||
lyricsResult = nextResult?.map { nextResult ->
|
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
|
||||||
nextResult.lyrics?.text()?.getOrNull() ?: ""
|
putExtra(SearchManager.QUERY, "${mediaMetadata.title} ${mediaMetadata.artist} lyrics")
|
||||||
}?.map { lyrics ->
|
}
|
||||||
query {
|
|
||||||
song?.let {
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
Database.update(song.copy(lyrics = lyrics))
|
context.startActivity(intent)
|
||||||
} ?: Database.insert(mediaItem) { song ->
|
} else {
|
||||||
|
Toast.makeText(context, "No browser app found!", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLyricsUpdate = { lyrics ->
|
||||||
|
val mediaItem = player?.currentMediaItem
|
||||||
|
query {
|
||||||
|
song?.let {
|
||||||
|
Database.update(song.copy(lyrics = lyrics))
|
||||||
|
} ?: mediaItem?.let {
|
||||||
|
Database.insert(mediaItem) { song ->
|
||||||
song.copy(lyrics = lyrics)
|
song.copy(lyrics = lyrics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lyrics
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
onSearchOnline = {
|
}
|
||||||
val mediaMetadata = player?.mediaMetadata ?: return@LyricsView
|
else -> {}
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLyricsUpdate = { lyrics ->
|
|
||||||
val mediaItem = player?.currentMediaItem
|
|
||||||
query {
|
|
||||||
song?.let {
|
|
||||||
Database.update(song.copy(lyrics = lyrics))
|
|
||||||
} ?: mediaItem?.let {
|
|
||||||
Database.insert(mediaItem) { song ->
|
|
||||||
song.copy(lyrics = lyrics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
host {
|
|
||||||
CurrentPlaylistView(
|
|
||||||
playerState = playerState,
|
|
||||||
layoutState = layoutState,
|
|
||||||
onGlobalRouteEmitted = onGlobalRouteEmitted,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 64.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user