Add scroll to top button in HomeSongList
This commit is contained in:
parent
eeec55c369
commit
9d1ed51d61
2 changed files with 211 additions and 93 deletions
|
@ -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.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.runtime.Composable
|
||||
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.ui.components.themed.Header
|
||||
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.LocalAppearance
|
||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||
|
@ -88,114 +92,127 @@ fun HomeSongList() {
|
|||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(colorPalette.background0)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
item(
|
||||
key = "header",
|
||||
contentType = 0
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||
) {
|
||||
Header(title = "Songs") {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
targetSortBy: SongSortBy
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(if (sortBy == targetSortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
item(
|
||||
key = "header",
|
||||
contentType = 0
|
||||
) {
|
||||
Header(title = "Songs") {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
targetSortBy: SongSortBy
|
||||
) {
|
||||
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
|
||||
.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)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, song -> song.id }
|
||||
) { index, song ->
|
||||
SongItem(
|
||||
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
|
||||
.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)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, song -> song.id }
|
||||
) { index, song ->
|
||||
SongItem(
|
||||
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()
|
||||
)
|
||||
}
|
||||
ScrollToTop(
|
||||
lazyListState = lazyListState,
|
||||
modifier = Modifier
|
||||
.offset(x = -Dimensions.verticalBarWidth)
|
||||
.align(Alignment.BottomStart)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue