Make PlayerBottomSheet composable smart recompose
This commit is contained in:
parent
707fee1b29
commit
bc76533512
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue