Add scroll to top button in HomeSongList

This commit is contained in:
vfsfitvnm
2022-09-27 12:07:56 +02:00
parent eeec55c369
commit 9d1ed51d61
2 changed files with 211 additions and 93 deletions

View File

@@ -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)
)
}
}
}

View File

@@ -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()
)
}
} }
} }