Make PlayerBottomSheet composable smart recompose

This commit is contained in:
vfsfitvnm 2022-07-22 10:26:12 +02:00
parent 707fee1b29
commit bc76533512
5 changed files with 77 additions and 43 deletions

View file

@ -10,12 +10,11 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@ -30,7 +29,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.reordering.rememberReorderingState
import it.vfsfitvnm.reordering.verticalDragAfterLongPressToReorder
@ -45,16 +43,11 @@ import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LightColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.PlayerState
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.*
@ExperimentalAnimationApi
@Composable
fun CurrentPlaylistView(
playerState: PlayerState?,
layoutState: BottomSheetState,
onGlobalRouteEmitted: () -> Unit,
modifier: Modifier = Modifier,
@ -63,16 +56,18 @@ fun CurrentPlaylistView(
val hapticFeedback = LocalHapticFeedback.current
val (colorPalette, typography) = LocalAppearance.current
binder?.player ?: return
val thumbnailSize = Dimensions.thumbnails.song.px
val isPaused by derivedStateOf {
playerState?.playbackState == Player.STATE_ENDED || playerState?.playWhenReady == false
}
val mediaItemIndex by rememberMediaItemIndex(binder.player)
val windows by rememberWindows(binder.player)
val shouldBePlaying by rememberShouldBePlaying(binder.player)
val lazyListState =
rememberLazyListState(initialFirstVisibleItemIndex = playerState?.mediaItemIndex ?: 0)
rememberLazyListState(initialFirstVisibleItemIndex = mediaItemIndex)
val reorderingState = rememberReorderingState(playerState?.mediaItems ?: emptyList())
val reorderingState = rememberReorderingState(windows)
Box {
LazyColumn(
@ -84,32 +79,31 @@ fun CurrentPlaylistView(
layoutState.nestedScrollConnection(lazyListState.firstVisibleItemIndex == 0 && lazyListState.firstVisibleItemScrollOffset == 0)
})
) {
itemsIndexed(
items = playerState?.mediaItems ?: emptyList()
) { index, mediaItem ->
val isPlayingThisMediaItem by derivedStateOf {
playerState?.mediaItemIndex == index
}
items(
items = windows,
key = { it.uid.hashCode() }
) { window ->
val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex
SongItem(
mediaItem = mediaItem,
mediaItem = window.mediaItem,
thumbnailSize = thumbnailSize,
onClick = {
if (isPlayingThisMediaItem) {
if (isPaused) {
binder?.player?.play()
if (shouldBePlaying) {
binder.player.pause()
} else {
binder?.player?.pause()
binder.player.play()
}
} else {
binder?.player?.playWhenReady = true
binder?.player?.seekToDefaultPosition(index)
binder.player.playWhenReady = true
binder.player.seekToDefaultPosition(window.firstPeriodIndex)
}
},
menuContent = {
QueuedMediaItemMenu(
mediaItem = mediaItem,
indexInQueue = if (isPlayingThisMediaItem) null else index,
mediaItem = window.mediaItem,
indexInQueue = if (isPlayingThisMediaItem) null else window.firstPeriodIndex,
onGlobalRouteEmitted = onGlobalRouteEmitted
)
},
@ -128,7 +122,13 @@ fun CurrentPlaylistView(
)
.size(Dimensions.thumbnails.song)
) {
if (isPaused) {
if (shouldBePlaying) {
MusicBars(
color = LightColorPalette.background,
modifier = Modifier
.height(24.dp)
)
} else {
Image(
painter = painterResource(R.drawable.play),
contentDescription = null,
@ -136,12 +136,6 @@ fun CurrentPlaylistView(
modifier = Modifier
.size(24.dp)
)
} else {
MusicBars(
color = LightColorPalette.background,
modifier = Modifier
.height(24.dp)
)
}
}
}
@ -159,23 +153,24 @@ fun CurrentPlaylistView(
},
backgroundColor = colorPalette.background,
modifier = Modifier
// .animateItemPlacement()
.verticalDragAfterLongPressToReorder(
reorderingState = reorderingState,
index = index,
index = window.firstPeriodIndex,
onDragStart = {
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
},
onDragEnd = { reachedIndex ->
binder?.player?.moveMediaItem(index, reachedIndex)
binder.player.moveMediaItem(window.firstPeriodIndex, reachedIndex)
}
)
)
}
item {
if (binder?.isLoadingRadio == true) {
if (binder.isLoadingRadio) {
Column(
modifier = Modifier
.shimmer()
@ -226,7 +221,7 @@ fun CurrentPlaylistView(
modifier = Modifier
)
BasicText(
text = "${playerState?.mediaItems?.size ?: 0} songs",
text = "${windows.size} songs",
style = typography.xxs.semiBold.secondary,
modifier = Modifier
)

View file

@ -22,7 +22,6 @@ import it.vfsfitvnm.vimusic.utils.PlayerState
@ExperimentalAnimationApi
@Composable
fun PlayerBottomSheet(
playerState: PlayerState?,
layoutState: BottomSheetState,
onShowLyrics: () -> Unit,
onShowStatsForNerds: () -> Unit,
@ -101,7 +100,6 @@ fun PlayerBottomSheet(
}
) {
CurrentPlaylistView(
playerState = playerState,
layoutState = layoutState,
onGlobalRouteEmitted = onGlobalRouteEmitted,
modifier = Modifier

View file

@ -311,7 +311,6 @@ fun PlayerView(
}
PlayerBottomSheet(
playerState = playerState,
layoutState = rememberBottomSheetState(64.dp, layoutState.upperBound),
onShowLyrics = {
isShowingStatsForNerds = false
@ -441,7 +440,6 @@ private fun Thumbnail(
}
}
@Composable
private fun Lyrics(
mediaId: String,

View file

@ -10,6 +10,11 @@ val Timeline.mediaItems: List<MediaItem>
getWindow(index, Timeline.Window()).mediaItem
}
val Timeline.windows: List<Timeline.Window>
get() = (0 until windowCount).map { index ->
getWindow(index, Timeline.Window())
}
val Player.shouldBePlaying: Boolean
get() = !(playbackState == Player.STATE_ENDED || !playWhenReady)

View file

@ -154,6 +154,44 @@ fun rememberMediaItemIndex(player: Player): State<Int> {
return mediaItemIndexState
}
@Composable
fun rememberWindows(player: Player): State<List<Timeline.Window>> {
val windowsState = remember(player) {
mutableStateOf(player.currentTimeline.windows)
}
DisposableEffect(player) {
player.listener(object : Player.Listener {
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
windowsState.value = timeline.windows
}
})
}
return windowsState
}
@Composable
fun rememberShouldBePlaying(player: Player): State<Boolean> {
val state = remember(player) {
mutableStateOf(!(player.playbackState == Player.STATE_ENDED || !player.playWhenReady))
}
DisposableEffect(player) {
player.listener(object : Player.Listener {
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
state.value = !(player.playbackState == Player.STATE_ENDED || !playWhenReady)
}
override fun onPlaybackStateChanged(playbackState: Int) {
state.value = !(playbackState == Player.STATE_ENDED || !player.playWhenReady)
}
})
}
return state
}
@Composable
fun rememberVolume(player: Player): State<Float> {
val volumeState = remember(player) {