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

View file

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

View file

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

View file

@ -10,6 +10,11 @@ val Timeline.mediaItems: List<MediaItem>
getWindow(index, Timeline.Window()).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 val Player.shouldBePlaying: Boolean
get() = !(playbackState == Player.STATE_ENDED || !playWhenReady) get() = !(playbackState == Player.STATE_ENDED || !playWhenReady)

View file

@ -154,6 +154,44 @@ fun rememberMediaItemIndex(player: Player): State<Int> {
return mediaItemIndexState 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 @Composable
fun rememberVolume(player: Player): State<Float> { fun rememberVolume(player: Player): State<Float> {
val volumeState = remember(player) { val volumeState = remember(player) {