Add scroll to top button in HomeSongList
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScrollToTop(
|
||||||
|
lazyListState: LazyListState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val showScrollTopButton by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
lazyListState.firstVisibleItemIndex > lazyListState.layoutInfo.visibleItemsInfo.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollToTop(
|
||||||
|
isVisible = showScrollTopButton,
|
||||||
|
onClick = { lazyListState.animateScrollToItem(0) },
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScrollToTop(
|
||||||
|
lazyGridState: LazyGridState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val showScrollTopButton by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
lazyGridState.firstVisibleItemIndex > lazyGridState.layoutInfo.visibleItemsInfo.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollToTop(
|
||||||
|
isVisible = showScrollTopButton,
|
||||||
|
onClick = { lazyGridState.animateScrollToItem(0) },
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScrollToTop(
|
||||||
|
isVisible: Boolean,
|
||||||
|
onClick: suspend () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isVisible,
|
||||||
|
enter = slideInVertically { it },
|
||||||
|
exit = slideOutVertically { it },
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 16.dp)
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
.clickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.size(32.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.chevron_down),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(LocalAppearance.current.colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.rotate(180f)
|
||||||
|
.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,17 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -44,6 +47,7 @@ import it.vfsfitvnm.vimusic.models.DetailedSong
|
|||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.ScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
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
|
||||||
@@ -88,114 +92,127 @@ fun HomeSongList() {
|
|||||||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||||
)
|
)
|
||||||
|
|
||||||
LazyColumn(
|
val lazyListState = rememberLazyListState()
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
|
||||||
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
item(
|
LazyColumn(
|
||||||
key = "header",
|
state = lazyListState,
|
||||||
contentType = 0
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
) {
|
) {
|
||||||
Header(title = "Songs") {
|
item(
|
||||||
@Composable
|
key = "header",
|
||||||
fun Item(
|
contentType = 0
|
||||||
@DrawableRes iconId: Int,
|
) {
|
||||||
targetSortBy: SongSortBy
|
Header(title = "Songs") {
|
||||||
) {
|
@Composable
|
||||||
Image(
|
fun Item(
|
||||||
painter = painterResource(iconId),
|
@DrawableRes iconId: Int,
|
||||||
contentDescription = null,
|
targetSortBy: SongSortBy
|
||||||
colorFilter = ColorFilter.tint(if (sortBy == targetSortBy) colorPalette.text else colorPalette.textDisabled),
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(iconId),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(if (sortBy == targetSortBy) colorPalette.text else colorPalette.textDisabled),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { sortBy = targetSortBy }
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.size(18.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Item(
|
||||||
|
iconId = R.drawable.trending,
|
||||||
|
targetSortBy = SongSortBy.PlayTime
|
||||||
|
)
|
||||||
|
|
||||||
|
Item(
|
||||||
|
iconId = R.drawable.text,
|
||||||
|
targetSortBy = SongSortBy.Title
|
||||||
|
)
|
||||||
|
|
||||||
|
Item(
|
||||||
|
iconId = R.drawable.time,
|
||||||
|
targetSortBy = SongSortBy.DateAdded
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable { sortBy = targetSortBy }
|
.width(2.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.arrow_up),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { sortOrder = !sortOrder }
|
||||||
.padding(all = 4.dp)
|
.padding(all = 4.dp)
|
||||||
.size(18.dp)
|
.size(18.dp)
|
||||||
|
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item(
|
itemsIndexed(
|
||||||
iconId = R.drawable.trending,
|
items = items,
|
||||||
targetSortBy = SongSortBy.PlayTime
|
key = { _, song -> song.id }
|
||||||
)
|
) { index, song ->
|
||||||
|
SongItem(
|
||||||
Item(
|
song = song,
|
||||||
iconId = R.drawable.text,
|
thumbnailSize = thumbnailSize,
|
||||||
targetSortBy = SongSortBy.Title
|
onClick = {
|
||||||
)
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(items.map(DetailedSong::asMediaItem), index)
|
||||||
Item(
|
},
|
||||||
iconId = R.drawable.time,
|
menuContent = {
|
||||||
targetSortBy = SongSortBy.DateAdded
|
InHistoryMediaItemMenu(song = song)
|
||||||
)
|
},
|
||||||
|
onThumbnailContent = {
|
||||||
Spacer(
|
AnimatedVisibility(
|
||||||
|
visible = sortBy == SongSortBy.PlayTime,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = song.formattedTotalPlayTime,
|
||||||
|
style = typography.xxs.semiBold.center.color(Color.White),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black.copy(alpha = 0.75f)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shape = ThumbnailRoundness.shape
|
||||||
|
)
|
||||||
|
.padding(
|
||||||
|
horizontal = 8.dp,
|
||||||
|
vertical = 4.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(2.dp)
|
.animateItemPlacement()
|
||||||
)
|
|
||||||
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.arrow_up),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable { sortOrder = !sortOrder }
|
|
||||||
.padding(all = 4.dp)
|
|
||||||
.size(18.dp)
|
|
||||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsIndexed(
|
ScrollToTop(
|
||||||
items = items,
|
lazyListState = lazyListState,
|
||||||
key = { _, song -> song.id }
|
modifier = Modifier
|
||||||
) { index, song ->
|
.offset(x = -Dimensions.verticalBarWidth)
|
||||||
SongItem(
|
.align(Alignment.BottomStart)
|
||||||
song = song,
|
)
|
||||||
thumbnailSize = thumbnailSize,
|
|
||||||
onClick = {
|
|
||||||
binder?.stopRadio()
|
|
||||||
binder?.player?.forcePlayAtIndex(items.map(DetailedSong::asMediaItem), index)
|
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
InHistoryMediaItemMenu(song = song)
|
|
||||||
},
|
|
||||||
onThumbnailContent = {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = sortBy == SongSortBy.PlayTime,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = song.formattedTotalPlayTime,
|
|
||||||
style = typography.xxs.semiBold.center.color(Color.White),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
brush = Brush.verticalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
Color.Black.copy(alpha = 0.75f)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
shape = ThumbnailRoundness.shape
|
|
||||||
)
|
|
||||||
.padding(
|
|
||||||
horizontal = 8.dp,
|
|
||||||
vertical = 4.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.animateItemPlacement()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user