Add swipe gesture to PlayerBottomSheet (#79)

This commit is contained in:
vfsfitvnm
2022-07-06 21:28:03 +02:00
parent e3f218d904
commit 6ac02b9043

View File

@@ -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)
)
} }
} }
} }