Hide floating action button when scrolling down and add scroll to top button to each screen
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isScrollingDown
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isScrollingDownToIsFar
|
||||||
|
import it.vfsfitvnm.vimusic.utils.smoothScrollToTop
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyGridState: LazyGridState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
iconId: Int? = null,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val transitionState = remember {
|
||||||
|
MutableTransitionState(false to false)
|
||||||
|
}.apply { targetState = lazyGridState.isScrollingDownToIsFar() }
|
||||||
|
|
||||||
|
FloatingActions(
|
||||||
|
transitionState = transitionState,
|
||||||
|
onScrollToTop = lazyGridState::smoothScrollToTop,
|
||||||
|
iconId = iconId,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyListState: LazyListState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
iconId: Int? = null,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val transitionState = remember {
|
||||||
|
MutableTransitionState(false to false)
|
||||||
|
}.apply { targetState = lazyListState.isScrollingDownToIsFar() }
|
||||||
|
|
||||||
|
FloatingActions(
|
||||||
|
transitionState = transitionState,
|
||||||
|
onScrollToTop = lazyListState::smoothScrollToTop,
|
||||||
|
iconId = iconId,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.FloatingActionsContainerWithScrollToTop(
|
||||||
|
scrollState: ScrollState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
iconId: Int? = null,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val transitionState = remember {
|
||||||
|
MutableTransitionState(false to false)
|
||||||
|
}.apply { targetState = scrollState.isScrollingDown() to false }
|
||||||
|
|
||||||
|
FloatingActions(
|
||||||
|
transitionState = transitionState,
|
||||||
|
iconId = iconId,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.FloatingActions(
|
||||||
|
transitionState: MutableTransitionState<Pair<Boolean, Boolean>>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onScrollToTop: (suspend () -> Unit)? = null,
|
||||||
|
iconId: Int? = null,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val transition = updateTransition(transitionState, "FloatingActionsContainer")
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
modifier = modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
onScrollToTop?.let {
|
||||||
|
transition.AnimatedVisibility(
|
||||||
|
visible = { it.first && it.second },
|
||||||
|
enter = slideInVertically(tween(500, if (iconId == null) 0 else 100)) { it },
|
||||||
|
exit = slideOutVertically(tween(500, 0)) { it },
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
SecondaryButton(
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
onScrollToTop()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
iconId = R.drawable.chevron_up,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iconId?.let {
|
||||||
|
onClick?.let {
|
||||||
|
transition.AnimatedVisibility(
|
||||||
|
visible = { it.first },
|
||||||
|
enter = slideInVertically(tween(500, 0)) { it },
|
||||||
|
exit = slideOutVertically(tween(500, 100)) { it },
|
||||||
|
) {
|
||||||
|
PrimaryButton(
|
||||||
|
iconId = iconId,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,11 @@ import it.vfsfitvnm.vimusic.utils.isLandscape
|
|||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NavigationRail(
|
inline fun NavigationRail(
|
||||||
topIconButtonId: Int,
|
topIconButtonId: Int,
|
||||||
onTopIconButtonClick: () -> Unit,
|
noinline onTopIconButtonClick: () -> Unit,
|
||||||
tabIndex: Int,
|
tabIndex: Int,
|
||||||
onTabIndexChanged: (Int) -> Unit,
|
crossinline onTabIndexChanged: (Int) -> Unit,
|
||||||
content: @Composable ColumnScope.(@Composable (Int, String, Int) -> Unit) -> Unit,
|
content: @Composable ColumnScope.(@Composable (Int, String, Int) -> Unit) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@@ -144,7 +144,7 @@ fun NavigationRail(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Modifier.vertical(enabled: Boolean = true) =
|
fun Modifier.vertical(enabled: Boolean = true) =
|
||||||
if (enabled)
|
if (enabled)
|
||||||
layout { measurable, constraints ->
|
layout { measurable, constraints ->
|
||||||
val placeable = measurable.measure(constraints)
|
val placeable = measurable.measure(constraints)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ 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.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -16,11 +14,10 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
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 it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxScope.PrimaryButton(
|
fun PrimaryButton(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
@DrawableRes iconId: Int,
|
@DrawableRes iconId: Int,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -30,9 +27,6 @@ fun BoxScope.PrimaryButton(
|
|||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.clickable(enabled = isEnabled, onClick = onClick)
|
.clickable(enabled = isEnabled, onClick = onClick)
|
||||||
.background(colorPalette.background2)
|
.background(colorPalette.background2)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.components.themed
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedContentScope
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
import androidx.compose.animation.AnimatedVisibilityScope
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
@@ -19,7 +18,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
|
||||||
@SuppressLint("ModifierParameter")
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun Scaffold(
|
fun Scaffold(
|
||||||
@@ -28,8 +26,6 @@ fun Scaffold(
|
|||||||
tabIndex: Int,
|
tabIndex: Int,
|
||||||
onTabChanged: (Int) -> Unit,
|
onTabChanged: (Int) -> Unit,
|
||||||
tabColumnContent: @Composable ColumnScope.(@Composable (Int, String, Int) -> Unit) -> Unit,
|
tabColumnContent: @Composable ColumnScope.(@Composable (Int, String, Int) -> Unit) -> Unit,
|
||||||
primaryIconButtonId: Int? = null,
|
|
||||||
onPrimaryIconButtonClick: () -> Unit = {},
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable AnimatedVisibilityScope.(Int) -> Unit
|
content: @Composable AnimatedVisibilityScope.(Int) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -69,14 +65,7 @@ fun Scaffold(
|
|||||||
slideIntoContainer(slideDirection, animationSpec) with
|
slideIntoContainer(slideDirection, animationSpec) with
|
||||||
slideOutOfContainer(slideDirection, animationSpec)
|
slideOutOfContainer(slideDirection, animationSpec)
|
||||||
},
|
},
|
||||||
content = content,
|
content = content
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
primaryIconButtonId?.let {
|
|
||||||
PrimaryButton(
|
|
||||||
iconId = primaryIconButtonId,
|
|
||||||
onClick = onPrimaryIconButtonClick
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
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.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 it.vfsfitvnm.vimusic.utils.smoothScrollToTop
|
|
||||||
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::smoothScrollToTop,
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
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.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SecondaryButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
@DrawableRes iconId: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isEnabled: Boolean = true,
|
||||||
|
) {
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(enabled = isEnabled, onClick = onClick)
|
||||||
|
.background(colorPalette.background2)
|
||||||
|
.size(48.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(iconId),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(18.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
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
|
||||||
@@ -24,9 +25,9 @@ import it.vfsfitvnm.vimusic.models.DetailedSong
|
|||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
@@ -68,9 +69,12 @@ fun AlbumSongs(
|
|||||||
|
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
@@ -152,14 +156,16 @@ fun AlbumSongs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyListState = lazyListState,
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = songs.isNotEmpty(),
|
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
if (songs.isNotEmpty()) {
|
||||||
binder?.player?.forcePlayFromBeginning(
|
binder?.stopRadio()
|
||||||
songs.shuffled().map(DetailedSong::asMediaItem)
|
binder?.player?.forcePlayFromBeginning(
|
||||||
)
|
songs.shuffled().map(DetailedSong::asMediaItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -21,9 +22,9 @@ import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
|||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
@@ -63,9 +64,12 @@ fun ArtistLocalSongs(
|
|||||||
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val songThumbnailSizePx = songThumbnailSizeDp.px
|
val songThumbnailSizePx = songThumbnailSizeDp.px
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
@@ -128,14 +132,18 @@ fun ArtistLocalSongs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyListState = lazyListState,
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = !songs.isNullOrEmpty(),
|
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
songs?.let { songs ->
|
||||||
binder?.player?.forcePlayFromBeginning(
|
if (songs.isNotEmpty()) {
|
||||||
songs!!.shuffled().map(DetailedSong::asMediaItem)
|
binder?.stopRadio()
|
||||||
)
|
binder?.player?.forcePlayFromBeginning(
|
||||||
|
songs.shuffled().map(DetailedSong::asMediaItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
@@ -70,6 +70,8 @@ fun ArtistOverview(
|
|||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.padding(top = 24.dp, bottom = 8.dp)
|
.padding(top = 24.dp, bottom = 8.dp)
|
||||||
|
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
Box {
|
Box {
|
||||||
Column(
|
Column(
|
||||||
@@ -77,7 +79,7 @@ fun ArtistOverview(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(scrollState)
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
) {
|
) {
|
||||||
headerContent {
|
headerContent {
|
||||||
@@ -258,7 +260,8 @@ fun ArtistOverview(
|
|||||||
}
|
}
|
||||||
|
|
||||||
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
|
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
|
||||||
PrimaryButton(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
scrollState = scrollState,
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -20,6 +21,7 @@ import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
|
|||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
||||||
@@ -70,8 +72,11 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
|||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val thumbnailSize = thumbnailSizeDp.px
|
val thumbnailSize = thumbnailSizeDp.px
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
@@ -120,6 +125,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
|||||||
song = song,
|
song = song,
|
||||||
onDismiss = menuState::hide
|
onDismiss = menuState::hide
|
||||||
)
|
)
|
||||||
|
|
||||||
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(
|
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(
|
||||||
song = song,
|
song = song,
|
||||||
onDismiss = menuState::hide
|
onDismiss = menuState::hide
|
||||||
@@ -129,7 +135,10 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
|||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
|
binder?.player?.forcePlayAtIndex(
|
||||||
|
songs.map(DetailedSong::asMediaItem),
|
||||||
|
index
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
@@ -137,14 +146,16 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyListState = lazyListState,
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = songs.isNotEmpty(),
|
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
if (songs.isNotEmpty()) {
|
||||||
binder?.player?.forcePlayFromBeginning(
|
binder?.stopRadio()
|
||||||
songs.shuffled().map(DetailedSong::asMediaItem)
|
binder?.player?.forcePlayFromBeginning(
|
||||||
)
|
songs.shuffled().map(DetailedSong::asMediaItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import androidx.compose.animation.core.tween
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
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.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -25,6 +27,7 @@ import it.vfsfitvnm.vimusic.enums.AlbumSortBy
|
|||||||
import it.vfsfitvnm.vimusic.enums.SortOrder
|
import it.vfsfitvnm.vimusic.enums.SortOrder
|
||||||
import it.vfsfitvnm.vimusic.models.Album
|
import it.vfsfitvnm.vimusic.models.Album
|
||||||
import it.vfsfitvnm.vimusic.savers.AlbumListSaver
|
import it.vfsfitvnm.vimusic.savers.AlbumListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
@@ -42,7 +45,8 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeAlbums(
|
fun HomeAlbums(
|
||||||
onAlbumClick: (Album) -> Unit
|
onAlbumClick: (Album) -> Unit,
|
||||||
|
onSearchClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
@@ -68,62 +72,73 @@ fun HomeAlbums(
|
|||||||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||||
)
|
)
|
||||||
|
|
||||||
LazyColumn(
|
val lazyListState = rememberLazyListState()
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
|
||||||
modifier = Modifier
|
Box {
|
||||||
.background(colorPalette.background0)
|
LazyColumn(
|
||||||
.fillMaxSize()
|
state = lazyListState,
|
||||||
) {
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
item(
|
modifier = Modifier
|
||||||
key = "header",
|
.background(colorPalette.background0)
|
||||||
contentType = 0
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Header(title = "Albums") {
|
item(
|
||||||
HeaderIconButton(
|
key = "header",
|
||||||
icon = R.drawable.calendar,
|
contentType = 0
|
||||||
color = if (sortBy == AlbumSortBy.Year) colorPalette.text else colorPalette.textDisabled,
|
) {
|
||||||
onClick = { sortBy = AlbumSortBy.Year }
|
Header(title = "Albums") {
|
||||||
)
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.calendar,
|
||||||
|
color = if (sortBy == AlbumSortBy.Year) colorPalette.text else colorPalette.textDisabled,
|
||||||
|
onClick = { sortBy = AlbumSortBy.Year }
|
||||||
|
)
|
||||||
|
|
||||||
HeaderIconButton(
|
HeaderIconButton(
|
||||||
icon = R.drawable.text,
|
icon = R.drawable.text,
|
||||||
color = if (sortBy == AlbumSortBy.Title) colorPalette.text else colorPalette.textDisabled,
|
color = if (sortBy == AlbumSortBy.Title) colorPalette.text else colorPalette.textDisabled,
|
||||||
onClick = { sortBy = AlbumSortBy.Title }
|
onClick = { sortBy = AlbumSortBy.Title }
|
||||||
)
|
)
|
||||||
|
|
||||||
HeaderIconButton(
|
HeaderIconButton(
|
||||||
icon = R.drawable.time,
|
icon = R.drawable.time,
|
||||||
color = if (sortBy == AlbumSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
|
color = if (sortBy == AlbumSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
|
||||||
onClick = { sortBy = AlbumSortBy.DateAdded }
|
onClick = { sortBy = AlbumSortBy.DateAdded }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(2.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.arrow_up,
|
||||||
|
color = colorPalette.text,
|
||||||
|
onClick = { sortOrder = !sortOrder },
|
||||||
|
modifier = Modifier
|
||||||
|
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = items,
|
||||||
|
key = Album::id
|
||||||
|
) { album ->
|
||||||
|
AlbumItem(
|
||||||
|
album = album,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(2.dp)
|
.clickable(onClick = { onAlbumClick(album) })
|
||||||
)
|
.animateItemPlacement()
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = R.drawable.arrow_up,
|
|
||||||
color = colorPalette.text,
|
|
||||||
onClick = { sortOrder = !sortOrder },
|
|
||||||
modifier = Modifier
|
|
||||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
items = items,
|
lazyListState = lazyListState,
|
||||||
key = Album::id
|
iconId = R.drawable.search,
|
||||||
) { album ->
|
onClick = onSearchClick
|
||||||
AlbumItem(
|
)
|
||||||
album = album,
|
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onAlbumClick(album) })
|
|
||||||
.animateItemPlacement()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
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.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -15,6 +16,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
|||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -29,6 +31,7 @@ import it.vfsfitvnm.vimusic.enums.ArtistSortBy
|
|||||||
import it.vfsfitvnm.vimusic.enums.SortOrder
|
import it.vfsfitvnm.vimusic.enums.SortOrder
|
||||||
import it.vfsfitvnm.vimusic.models.Artist
|
import it.vfsfitvnm.vimusic.models.Artist
|
||||||
import it.vfsfitvnm.vimusic.savers.ArtistListSaver
|
import it.vfsfitvnm.vimusic.savers.ArtistListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.items.ArtistItem
|
import it.vfsfitvnm.vimusic.ui.items.ArtistItem
|
||||||
@@ -46,7 +49,8 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeArtistList(
|
fun HomeArtistList(
|
||||||
onArtistClick: (Artist) -> Unit
|
onArtistClick: (Artist) -> Unit,
|
||||||
|
onSearchClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
@@ -72,61 +76,72 @@ fun HomeArtistList(
|
|||||||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||||
)
|
)
|
||||||
|
|
||||||
LazyVerticalGrid(
|
val lazyGridState = rememberLazyGridState()
|
||||||
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
Box {
|
||||||
verticalArrangement = Arrangement.spacedBy(Dimensions.itemsVerticalPadding * 2),
|
LazyVerticalGrid(
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
state = lazyGridState,
|
||||||
space = Dimensions.itemsVerticalPadding * 2,
|
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
||||||
alignment = Alignment.CenterHorizontally
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
),
|
verticalArrangement = Arrangement.spacedBy(Dimensions.itemsVerticalPadding * 2),
|
||||||
modifier = Modifier
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
.background(colorPalette.background0)
|
space = Dimensions.itemsVerticalPadding * 2,
|
||||||
.fillMaxSize()
|
alignment = Alignment.CenterHorizontally
|
||||||
) {
|
),
|
||||||
item(
|
modifier = Modifier
|
||||||
key = "header",
|
.background(colorPalette.background0)
|
||||||
contentType = 0,
|
.fillMaxSize()
|
||||||
span = { GridItemSpan(maxLineSpan) }
|
|
||||||
) {
|
) {
|
||||||
Header(title = "Artists") {
|
item(
|
||||||
HeaderIconButton(
|
key = "header",
|
||||||
icon = R.drawable.text,
|
contentType = 0,
|
||||||
color = if (sortBy == ArtistSortBy.Name) colorPalette.text else colorPalette.textDisabled,
|
span = { GridItemSpan(maxLineSpan) }
|
||||||
onClick = { sortBy = ArtistSortBy.Name }
|
) {
|
||||||
)
|
Header(title = "Artists") {
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.text,
|
||||||
|
color = if (sortBy == ArtistSortBy.Name) colorPalette.text else colorPalette.textDisabled,
|
||||||
|
onClick = { sortBy = ArtistSortBy.Name }
|
||||||
|
)
|
||||||
|
|
||||||
HeaderIconButton(
|
HeaderIconButton(
|
||||||
icon = R.drawable.time,
|
icon = R.drawable.time,
|
||||||
color = if (sortBy == ArtistSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
|
color = if (sortBy == ArtistSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
|
||||||
onClick = { sortBy = ArtistSortBy.DateAdded }
|
onClick = { sortBy = ArtistSortBy.DateAdded }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(2.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.arrow_up,
|
||||||
|
color = colorPalette.text,
|
||||||
|
onClick = { sortOrder = !sortOrder },
|
||||||
|
modifier = Modifier
|
||||||
|
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(items = items, key = Artist::id) { artist ->
|
||||||
|
ArtistItem(
|
||||||
|
artist = artist,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
alternative = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(2.dp)
|
.clickable(onClick = { onArtistClick(artist) })
|
||||||
)
|
.animateItemPlacement()
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = R.drawable.arrow_up,
|
|
||||||
color = colorPalette.text,
|
|
||||||
onClick = { sortOrder = !sortOrder },
|
|
||||||
modifier = Modifier
|
|
||||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(items = items, key = Artist::id) { artist ->
|
FloatingActionsContainerWithScrollToTop(
|
||||||
ArtistItem(
|
lazyGridState = lazyGridState,
|
||||||
artist = artist,
|
iconId = R.drawable.search,
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
onClick = onSearchClick
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
)
|
||||||
alternative = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onArtistClick(artist) })
|
|
||||||
.animateItemPlacement()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.home
|
package it.vfsfitvnm.vimusic.ui.screens.home
|
||||||
|
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@@ -7,6 +8,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
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.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -14,6 +16,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
|||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -32,6 +35,7 @@ import it.vfsfitvnm.vimusic.enums.SortOrder
|
|||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.query
|
import it.vfsfitvnm.vimusic.query
|
||||||
import it.vfsfitvnm.vimusic.savers.PlaylistPreviewListSaver
|
import it.vfsfitvnm.vimusic.savers.PlaylistPreviewListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
@@ -47,11 +51,13 @@ import it.vfsfitvnm.vimusic.utils.rememberPreference
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomePlaylists(
|
fun HomePlaylists(
|
||||||
onBuiltInPlaylist: (BuiltInPlaylist) -> Unit,
|
onBuiltInPlaylist: (BuiltInPlaylist) -> Unit,
|
||||||
onPlaylistClick: (Playlist) -> Unit,
|
onPlaylistClick: (Playlist) -> Unit,
|
||||||
|
onSearchClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
@@ -95,101 +101,112 @@ fun HomePlaylists(
|
|||||||
val thumbnailSizeDp = 108.dp
|
val thumbnailSizeDp = 108.dp
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
LazyVerticalGrid(
|
val lazyGridState = rememberLazyGridState()
|
||||||
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(Dimensions.itemsVerticalPadding * 2),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
|
||||||
space = Dimensions.itemsVerticalPadding * 2,
|
|
||||||
alignment = Alignment.CenterHorizontally
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
) {
|
|
||||||
item(key = "header", contentType = 0, span = { GridItemSpan(maxLineSpan) }) {
|
|
||||||
Header(title = "Playlists") {
|
|
||||||
SecondaryTextButton(
|
|
||||||
text = "New playlist",
|
|
||||||
onClick = { isCreatingANewPlaylist = true }
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(
|
Box {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
state = lazyGridState,
|
||||||
|
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
||||||
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimensions.itemsVerticalPadding * 2),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
space = Dimensions.itemsVerticalPadding * 2,
|
||||||
|
alignment = Alignment.CenterHorizontally
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
) {
|
||||||
|
item(key = "header", contentType = 0, span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
Header(title = "Playlists") {
|
||||||
|
SecondaryTextButton(
|
||||||
|
text = "New playlist",
|
||||||
|
onClick = { isCreatingANewPlaylist = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.medical,
|
||||||
|
color = if (sortBy == PlaylistSortBy.SongCount) colorPalette.text else colorPalette.textDisabled,
|
||||||
|
onClick = { sortBy = PlaylistSortBy.SongCount }
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.text,
|
||||||
|
color = if (sortBy == PlaylistSortBy.Name) colorPalette.text else colorPalette.textDisabled,
|
||||||
|
onClick = { sortBy = PlaylistSortBy.Name }
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.time,
|
||||||
|
color = if (sortBy == PlaylistSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
|
||||||
|
onClick = { sortBy = PlaylistSortBy.DateAdded }
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(2.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.arrow_up,
|
||||||
|
color = colorPalette.text,
|
||||||
|
onClick = { sortOrder = !sortOrder },
|
||||||
|
modifier = Modifier
|
||||||
|
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item(key = "favorites") {
|
||||||
|
PlaylistItem(
|
||||||
|
icon = R.drawable.heart,
|
||||||
|
colorTint = colorPalette.red,
|
||||||
|
name = "Favorites",
|
||||||
|
songCount = null,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
alternative = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.clickable(onClick = { onBuiltInPlaylist(BuiltInPlaylist.Favorites) })
|
||||||
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
HeaderIconButton(
|
item(key = "offline") {
|
||||||
icon = R.drawable.medical,
|
PlaylistItem(
|
||||||
color = if (sortBy == PlaylistSortBy.SongCount) colorPalette.text else colorPalette.textDisabled,
|
icon = R.drawable.airplane,
|
||||||
onClick = { sortBy = PlaylistSortBy.SongCount }
|
colorTint = colorPalette.blue,
|
||||||
)
|
name = "Offline",
|
||||||
|
songCount = null,
|
||||||
HeaderIconButton(
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
icon = R.drawable.text,
|
alternative = true,
|
||||||
color = if (sortBy == PlaylistSortBy.Name) colorPalette.text else colorPalette.textDisabled,
|
|
||||||
onClick = { sortBy = PlaylistSortBy.Name }
|
|
||||||
)
|
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = R.drawable.time,
|
|
||||||
color = if (sortBy == PlaylistSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
|
|
||||||
onClick = { sortBy = PlaylistSortBy.DateAdded }
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(2.dp)
|
.clickable(onClick = { onBuiltInPlaylist(BuiltInPlaylist.Offline) })
|
||||||
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
HeaderIconButton(
|
items(items = items, key = { it.playlist.id }) { playlistPreview ->
|
||||||
icon = R.drawable.arrow_up,
|
PlaylistItem(
|
||||||
color = colorPalette.text,
|
playlist = playlistPreview,
|
||||||
onClick = { sortOrder = !sortOrder },
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
alternative = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
.clickable(onClick = { onPlaylistClick(playlistPreview.playlist) })
|
||||||
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item(key = "favorites") {
|
FloatingActionsContainerWithScrollToTop(
|
||||||
PlaylistItem(
|
lazyGridState = lazyGridState,
|
||||||
icon = R.drawable.heart,
|
iconId = R.drawable.search,
|
||||||
colorTint = colorPalette.red,
|
onClick = onSearchClick
|
||||||
name = "Favorites",
|
)
|
||||||
songCount = null,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
alternative = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onBuiltInPlaylist(BuiltInPlaylist.Favorites) })
|
|
||||||
.animateItemPlacement()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item(key = "offline") {
|
|
||||||
PlaylistItem(
|
|
||||||
icon = R.drawable.airplane,
|
|
||||||
colorTint = colorPalette.blue,
|
|
||||||
name = "Offline",
|
|
||||||
songCount = null,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
alternative = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onBuiltInPlaylist(BuiltInPlaylist.Offline) })
|
|
||||||
.animateItemPlacement()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items(items = items, key = { it.playlist.id }) { playlistPreview ->
|
|
||||||
PlaylistItem(
|
|
||||||
playlist = playlistPreview,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
|
||||||
alternative = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onPlaylistClick(playlistPreview.playlist) })
|
|
||||||
.animateItemPlacement()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,8 +114,6 @@ fun HomeScreen(onPlaylistUrl: (String) -> Unit) {
|
|||||||
Item(3, "Artists", R.drawable.person)
|
Item(3, "Artists", R.drawable.person)
|
||||||
Item(4, "Albums", R.drawable.disc)
|
Item(4, "Albums", R.drawable.disc)
|
||||||
},
|
},
|
||||||
primaryIconButtonId = R.drawable.search,
|
|
||||||
onPrimaryIconButtonClick = { searchRoute("") }
|
|
||||||
) { currentTabIndex ->
|
) { currentTabIndex ->
|
||||||
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
|
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
|
||||||
when (currentTabIndex) {
|
when (currentTabIndex) {
|
||||||
@@ -123,14 +121,24 @@ fun HomeScreen(onPlaylistUrl: (String) -> Unit) {
|
|||||||
onAlbumClick = { albumRoute(it) },
|
onAlbumClick = { albumRoute(it) },
|
||||||
onArtistClick = { artistRoute(it) },
|
onArtistClick = { artistRoute(it) },
|
||||||
onPlaylistClick = { playlistRoute(it) },
|
onPlaylistClick = { playlistRoute(it) },
|
||||||
|
onSearchClick = { searchRoute("") }
|
||||||
|
)
|
||||||
|
1 -> HomeSongs(
|
||||||
|
onSearchClick = { searchRoute("") }
|
||||||
)
|
)
|
||||||
1 -> HomeSongs()
|
|
||||||
2 -> HomePlaylists(
|
2 -> HomePlaylists(
|
||||||
onBuiltInPlaylist = { builtInPlaylistRoute(it) },
|
onBuiltInPlaylist = { builtInPlaylistRoute(it) },
|
||||||
onPlaylistClick = { localPlaylistRoute(it.id) }
|
onPlaylistClick = { localPlaylistRoute(it.id) },
|
||||||
|
onSearchClick = { searchRoute("") }
|
||||||
|
)
|
||||||
|
3 -> HomeArtistList(
|
||||||
|
onArtistClick = { artistRoute(it.id) },
|
||||||
|
onSearchClick = { searchRoute("") }
|
||||||
|
)
|
||||||
|
4 -> HomeAlbums(
|
||||||
|
onAlbumClick = { albumRoute(it.id) },
|
||||||
|
onSearchClick = { searchRoute("") }
|
||||||
)
|
)
|
||||||
3 -> HomeArtistList(onArtistClick = { artistRoute(it.id) })
|
|
||||||
4 -> HomeAlbums(onAlbumClick = { albumRoute(it.id) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -37,10 +36,10 @@ import it.vfsfitvnm.vimusic.enums.SortOrder
|
|||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
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.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
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
|
||||||
@@ -62,7 +61,9 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeSongs() {
|
fun HomeSongs(
|
||||||
|
onSearchClick: () -> Unit
|
||||||
|
) {
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val menuState = LocalMenuState.current
|
val menuState = LocalMenuState.current
|
||||||
@@ -187,11 +188,10 @@ fun HomeSongs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollToTop(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
modifier = Modifier
|
iconId = R.drawable.search,
|
||||||
.offset(x = Dimensions.navigationRailIconOffset - Dimensions.navigationRailWidth)
|
onClick = onSearchClick
|
||||||
.align(Alignment.BottomStart)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import it.vfsfitvnm.vimusic.savers.nullableSaver
|
|||||||
import it.vfsfitvnm.vimusic.savers.resultSaver
|
import it.vfsfitvnm.vimusic.savers.resultSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
@@ -81,6 +82,7 @@ fun QuickPicks(
|
|||||||
onAlbumClick: (String) -> Unit,
|
onAlbumClick: (String) -> Unit,
|
||||||
onArtistClick: (String) -> Unit,
|
onArtistClick: (String) -> Unit,
|
||||||
onPlaylistClick: (String) -> Unit,
|
onPlaylistClick: (String) -> Unit,
|
||||||
|
onSearchClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
@@ -129,6 +131,8 @@ fun QuickPicks(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
BoxWithConstraints {
|
BoxWithConstraints {
|
||||||
val itemInHorizontalGridWidth = maxWidth * quickPicksLazyGridItemWidthFactor
|
val itemInHorizontalGridWidth = maxWidth * quickPicksLazyGridItemWidthFactor
|
||||||
|
|
||||||
@@ -136,7 +140,7 @@ fun QuickPicks(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(scrollState)
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
) {
|
) {
|
||||||
Header(title = "Quick picks")
|
Header(title = "Quick picks")
|
||||||
@@ -345,5 +349,11 @@ fun QuickPicks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
scrollState = scrollState,
|
||||||
|
iconId = R.drawable.search,
|
||||||
|
onClick = onSearchClick
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ import it.vfsfitvnm.vimusic.savers.nullableSaver
|
|||||||
import it.vfsfitvnm.vimusic.transaction
|
import it.vfsfitvnm.vimusic.transaction
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.IconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.IconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InPlaylistMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InPlaylistMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
|
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
@@ -274,17 +274,18 @@ fun LocalPlaylistSongs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyListState = lazyListState,
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = playlistWithSongs?.songs?.isNotEmpty() == true,
|
|
||||||
onClick = {
|
onClick = {
|
||||||
playlistWithSongs?.songs
|
playlistWithSongs?.songs?.let { songs ->
|
||||||
?.shuffled()
|
if (songs.isNotEmpty()) {
|
||||||
?.map(DetailedSong::asMediaItem)
|
|
||||||
?.let { mediaItems ->
|
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.player?.forcePlayFromBeginning(mediaItems)
|
binder?.player?.forcePlayFromBeginning(
|
||||||
|
songs.shuffled().map(DetailedSong::asMediaItem)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.saveable.autoSaver
|
import androidx.compose.runtime.saveable.autoSaver
|
||||||
@@ -29,12 +30,12 @@ import it.vfsfitvnm.vimusic.savers.nullableSaver
|
|||||||
import it.vfsfitvnm.vimusic.transaction
|
import it.vfsfitvnm.vimusic.transaction
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.adaptiveThumbnailContent
|
import it.vfsfitvnm.vimusic.ui.components.themed.adaptiveThumbnailContent
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
@@ -62,7 +63,7 @@ import kotlinx.coroutines.withContext
|
|||||||
fun PlaylistSongList(
|
fun PlaylistSongList(
|
||||||
browseId: String,
|
browseId: String,
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val menuState = LocalMenuState.current
|
val menuState = LocalMenuState.current
|
||||||
@@ -162,9 +163,12 @@ fun PlaylistSongList(
|
|||||||
|
|
||||||
val thumbnailContent = adaptiveThumbnailContent(playlistPage == null, playlistPage?.thumbnail?.url)
|
val thumbnailContent = adaptiveThumbnailContent(playlistPage == null, playlistPage?.thumbnail?.url)
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
@@ -219,13 +223,17 @@ fun PlaylistSongList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton(
|
FloatingActionsContainerWithScrollToTop(
|
||||||
|
lazyListState = lazyListState,
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
|
|
||||||
onClick = {
|
onClick = {
|
||||||
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
playlistPage?.songsPage?.items?.let { songs ->
|
||||||
binder?.stopRadio()
|
if (songs.isNotEmpty()) {
|
||||||
binder?.player?.forcePlayFromBeginning(mediaItems.shuffled())
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayFromBeginning(
|
||||||
|
songs.shuffled().map(Innertube.SongItem::asMediaItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package it.vfsfitvnm.vimusic.ui.screens.search
|
|||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -21,6 +23,7 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
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.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
@@ -65,68 +68,75 @@ fun LocalSongSearch(
|
|||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
LazyColumn(
|
val lazyListState = rememberLazyListState()
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
item(
|
|
||||||
key = "header",
|
|
||||||
contentType = 0
|
|
||||||
) {
|
|
||||||
Header(
|
|
||||||
titleContent = {
|
|
||||||
BasicTextField(
|
|
||||||
value = textFieldValue,
|
|
||||||
onValueChange = onTextFieldValueChanged,
|
|
||||||
textStyle = typography.xxl.medium.align(TextAlign.End),
|
|
||||||
singleLine = true,
|
|
||||||
maxLines = 1,
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
|
||||||
cursorBrush = SolidColor(colorPalette.text),
|
|
||||||
decorationBox = decorationBox
|
|
||||||
)
|
|
||||||
},
|
|
||||||
actionsContent = {
|
|
||||||
if (textFieldValue.text.isNotEmpty()) {
|
|
||||||
SecondaryTextButton(
|
|
||||||
text = "Clear",
|
|
||||||
onClick = { onTextFieldValueChanged(TextFieldValue()) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items(
|
Box {
|
||||||
items = items,
|
LazyColumn(
|
||||||
key = DetailedSong::id,
|
state = lazyListState,
|
||||||
) { song ->
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
SongItem(
|
modifier = Modifier
|
||||||
song = song,
|
.fillMaxSize()
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
) {
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
item(
|
||||||
modifier = Modifier
|
key = "header",
|
||||||
.combinedClickable(
|
contentType = 0
|
||||||
onLongClick = {
|
) {
|
||||||
menuState.display {
|
Header(
|
||||||
InHistoryMediaItemMenu(
|
titleContent = {
|
||||||
song = song,
|
BasicTextField(
|
||||||
onDismiss = menuState::hide
|
value = textFieldValue,
|
||||||
)
|
onValueChange = onTextFieldValueChanged,
|
||||||
}
|
textStyle = typography.xxl.medium.align(TextAlign.End),
|
||||||
},
|
singleLine = true,
|
||||||
onClick = {
|
maxLines = 1,
|
||||||
val mediaItem = song.asMediaItem
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||||
binder?.stopRadio()
|
cursorBrush = SolidColor(colorPalette.text),
|
||||||
binder?.player?.forcePlay(mediaItem)
|
decorationBox = decorationBox
|
||||||
binder?.setupRadio(
|
)
|
||||||
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
},
|
||||||
|
actionsContent = {
|
||||||
|
if (textFieldValue.text.isNotEmpty()) {
|
||||||
|
SecondaryTextButton(
|
||||||
|
text = "Clear",
|
||||||
|
onClick = { onTextFieldValueChanged(TextFieldValue()) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
.animateItemPlacement()
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = items,
|
||||||
|
key = DetailedSong::id,
|
||||||
|
) { song ->
|
||||||
|
SongItem(
|
||||||
|
song = song,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
InHistoryMediaItemMenu(
|
||||||
|
song = song,
|
||||||
|
onDismiss = menuState::hide
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val mediaItem = song.asMediaItem
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(mediaItem)
|
||||||
|
binder?.setupRadio(
|
||||||
|
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.animateItemPlacement()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FloatingActionsContainerWithScrollToTop(lazyListState = lazyListState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.search
|
package it.vfsfitvnm.vimusic.ui.screens.search
|
||||||
|
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
@@ -12,6 +13,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
@@ -45,6 +47,7 @@ import it.vfsfitvnm.vimusic.query
|
|||||||
import it.vfsfitvnm.vimusic.savers.SearchQuerySaver
|
import it.vfsfitvnm.vimusic.savers.SearchQuerySaver
|
||||||
import it.vfsfitvnm.vimusic.savers.listSaver
|
import it.vfsfitvnm.vimusic.savers.listSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.resultSaver
|
import it.vfsfitvnm.vimusic.savers.resultSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
@@ -62,6 +65,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun OnlineSearch(
|
fun OnlineSearch(
|
||||||
textFieldValue: TextFieldValue,
|
textFieldValue: TextFieldValue,
|
||||||
@@ -112,139 +116,74 @@ fun OnlineSearch(
|
|||||||
FocusRequester()
|
FocusRequester()
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
val lazyListState = rememberLazyListState()
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
|
||||||
modifier = Modifier
|
Box {
|
||||||
.fillMaxSize()
|
LazyColumn(
|
||||||
) {
|
state = lazyListState,
|
||||||
item(
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
key = "header",
|
modifier = Modifier
|
||||||
contentType = 0
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Header(
|
item(
|
||||||
titleContent = {
|
key = "header",
|
||||||
BasicTextField(
|
contentType = 0
|
||||||
value = textFieldValue,
|
|
||||||
onValueChange = onTextFieldValueChanged,
|
|
||||||
textStyle = typography.xxl.medium.align(TextAlign.End),
|
|
||||||
singleLine = true,
|
|
||||||
maxLines = 1,
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
if (textFieldValue.text.isNotEmpty()) {
|
|
||||||
onSearch(textFieldValue.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cursorBrush = SolidColor(colorPalette.text),
|
|
||||||
decorationBox = decorationBox,
|
|
||||||
modifier = Modifier
|
|
||||||
.focusRequester(focusRequester)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
actionsContent = {
|
|
||||||
if (playlistId != null) {
|
|
||||||
val isAlbum = playlistId.startsWith("OLAK5uy_")
|
|
||||||
|
|
||||||
SecondaryTextButton(
|
|
||||||
text = "View ${if (isAlbum) "album" else "playlist"}",
|
|
||||||
onClick = { onViewPlaylist(textFieldValue.text) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (textFieldValue.text.isNotEmpty()) {
|
|
||||||
SecondaryTextButton(
|
|
||||||
text = "Clear",
|
|
||||||
onClick = { onTextFieldValueChanged(TextFieldValue()) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items(
|
|
||||||
items = history,
|
|
||||||
key = SearchQuery::id
|
|
||||||
) { searchQuery ->
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onSearch(searchQuery.query) })
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
) {
|
) {
|
||||||
Spacer(
|
Header(
|
||||||
modifier = Modifier
|
titleContent = {
|
||||||
.padding(horizontal = 8.dp)
|
BasicTextField(
|
||||||
.size(20.dp)
|
value = textFieldValue,
|
||||||
.paint(
|
onValueChange = onTextFieldValueChanged,
|
||||||
painter = timeIconPainter,
|
textStyle = typography.xxl.medium.align(TextAlign.End),
|
||||||
colorFilter = ColorFilter.tint(colorPalette.textDisabled)
|
singleLine = true,
|
||||||
)
|
maxLines = 1,
|
||||||
)
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
BasicText(
|
onSearch = {
|
||||||
text = searchQuery.query,
|
if (textFieldValue.text.isNotEmpty()) {
|
||||||
style = typography.s.secondary,
|
onSearch(textFieldValue.text)
|
||||||
modifier = Modifier
|
}
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
Image(
|
|
||||||
painter = closeIconPainter,
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.textDisabled),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(
|
|
||||||
indication = rippleIndication,
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onClick = {
|
|
||||||
query {
|
|
||||||
Database.delete(searchQuery)
|
|
||||||
}
|
}
|
||||||
}
|
),
|
||||||
|
cursorBrush = SolidColor(colorPalette.text),
|
||||||
|
decorationBox = decorationBox,
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester)
|
||||||
)
|
)
|
||||||
.padding(horizontal = 8.dp)
|
},
|
||||||
.size(20.dp)
|
actionsContent = {
|
||||||
)
|
if (playlistId != null) {
|
||||||
|
val isAlbum = playlistId.startsWith("OLAK5uy_")
|
||||||
|
|
||||||
Image(
|
SecondaryTextButton(
|
||||||
painter = arrowForwardIconPainter,
|
text = "View ${if (isAlbum) "album" else "playlist"}",
|
||||||
contentDescription = null,
|
onClick = { onViewPlaylist(textFieldValue.text) }
|
||||||
colorFilter = ColorFilter.tint(colorPalette.textDisabled),
|
)
|
||||||
modifier = Modifier
|
}
|
||||||
.clickable(
|
|
||||||
indication = rippleIndication,
|
Spacer(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
modifier = Modifier
|
||||||
onClick = {
|
.weight(1f)
|
||||||
onTextFieldValueChanged(
|
|
||||||
TextFieldValue(
|
|
||||||
text = searchQuery.query,
|
|
||||||
selection = TextRange(searchQuery.query.length)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.rotate(225f)
|
|
||||||
.padding(horizontal = 8.dp)
|
if (textFieldValue.text.isNotEmpty()) {
|
||||||
.size(22.dp)
|
SecondaryTextButton(
|
||||||
|
text = "Clear",
|
||||||
|
onClick = { onTextFieldValueChanged(TextFieldValue()) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suggestionsResult?.getOrNull()?.let { suggestions ->
|
items(
|
||||||
items(items = suggestions) { suggestion ->
|
items = history,
|
||||||
|
key = SearchQuery::id
|
||||||
|
) { searchQuery ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = { onSearch(suggestion) })
|
.clickable(onClick = { onSearch(searchQuery.query) })
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(all = 16.dp)
|
.padding(all = 16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -252,16 +191,38 @@ fun OnlineSearch(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.size(20.dp)
|
.size(20.dp)
|
||||||
|
.paint(
|
||||||
|
painter = timeIconPainter,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.textDisabled)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = suggestion,
|
text = searchQuery.query,
|
||||||
style = typography.s.secondary,
|
style = typography.s.secondary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = closeIconPainter,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.textDisabled),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = {
|
||||||
|
query {
|
||||||
|
Database.delete(searchQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
)
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
painter = arrowForwardIconPainter,
|
painter = arrowForwardIconPainter,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -273,8 +234,8 @@ fun OnlineSearch(
|
|||||||
onClick = {
|
onClick = {
|
||||||
onTextFieldValueChanged(
|
onTextFieldValueChanged(
|
||||||
TextFieldValue(
|
TextFieldValue(
|
||||||
text = suggestion,
|
text = searchQuery.query,
|
||||||
selection = TextRange(suggestion.length)
|
selection = TextRange(searchQuery.query.length)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -285,21 +246,71 @@ fun OnlineSearch(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: suggestionsResult?.exceptionOrNull()?.let {
|
|
||||||
item {
|
suggestionsResult?.getOrNull()?.let { suggestions ->
|
||||||
Box(
|
items(items = suggestions) { suggestion ->
|
||||||
modifier = Modifier
|
Row(
|
||||||
.fillMaxSize()
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = "An error has occurred.",
|
|
||||||
style = typography.s.secondary.center,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.Center)
|
.clickable(onClick = { onSearch(suggestion) })
|
||||||
)
|
.fillMaxWidth()
|
||||||
|
.padding(all = 16.dp)
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = suggestion,
|
||||||
|
style = typography.s.secondary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = arrowForwardIconPainter,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.textDisabled),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = {
|
||||||
|
onTextFieldValueChanged(
|
||||||
|
TextFieldValue(
|
||||||
|
text = suggestion,
|
||||||
|
selection = TextRange(suggestion.length)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.rotate(225f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.size(22.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: suggestionsResult?.exceptionOrNull()?.let {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = "An error has occurred.",
|
||||||
|
style = typography.s.secondary.center,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FloatingActionsContainerWithScrollToTop(lazyListState = lazyListState)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.searchresult
|
package it.vfsfitvnm.vimusic.ui.screens.searchresult
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
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.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -19,6 +20,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
@@ -70,51 +72,55 @@ inline fun <T : Innertube.Item> ItemsPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
Box {
|
||||||
state = lazyListState,
|
LazyColumn(
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
state = lazyListState,
|
||||||
modifier = modifier
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
.fillMaxSize()
|
modifier = modifier
|
||||||
) {
|
.fillMaxSize()
|
||||||
item(
|
|
||||||
key = "header",
|
|
||||||
contentType = "header",
|
|
||||||
) {
|
) {
|
||||||
headerContent(null)
|
item(
|
||||||
}
|
key = "header",
|
||||||
|
contentType = "header",
|
||||||
items(
|
) {
|
||||||
items = itemsPage?.items ?: emptyList(),
|
headerContent(null)
|
||||||
key = Innertube.Item::key,
|
|
||||||
itemContent = itemContent
|
|
||||||
)
|
|
||||||
|
|
||||||
if (itemsPage != null && itemsPage?.items.isNullOrEmpty()) {
|
|
||||||
item(key = "empty") {
|
|
||||||
BasicText(
|
|
||||||
text = emptyItemsText,
|
|
||||||
style = typography.xs.secondary.center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 16.dp, vertical = 32.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!(itemsPage != null && itemsPage?.continuation == null)) {
|
items(
|
||||||
item(key = "loading") {
|
items = itemsPage?.items ?: emptyList(),
|
||||||
val isFirstLoad = itemsPage?.items.isNullOrEmpty()
|
key = Innertube.Item::key,
|
||||||
ShimmerHost(
|
itemContent = itemContent
|
||||||
modifier = Modifier
|
)
|
||||||
.run {
|
|
||||||
if (isFirstLoad) fillParentMaxSize() else this
|
if (itemsPage != null && itemsPage?.items.isNullOrEmpty()) {
|
||||||
|
item(key = "empty") {
|
||||||
|
BasicText(
|
||||||
|
text = emptyItemsText,
|
||||||
|
style = typography.xs.secondary.center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp, vertical = 32.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(itemsPage != null && itemsPage?.continuation == null)) {
|
||||||
|
item(key = "loading") {
|
||||||
|
val isFirstLoad = itemsPage?.items.isNullOrEmpty()
|
||||||
|
ShimmerHost(
|
||||||
|
modifier = Modifier
|
||||||
|
.run {
|
||||||
|
if (isFirstLoad) fillParentMaxSize() else this
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
repeat(if (isFirstLoad) initialPlaceholderCount else continuationPlaceholderCount) {
|
||||||
|
itemPlaceholderContent()
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
repeat(if (isFirstLoad) initialPlaceholderCount else continuationPlaceholderCount) {
|
|
||||||
itemPlaceholderContent()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FloatingActionsContainerWithScrollToTop(lazyListState = lazyListState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.utils
|
||||||
|
|
||||||
|
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.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
|
||||||
|
suspend fun LazyGridState.smoothScrollToTop() {
|
||||||
|
if (firstVisibleItemIndex > layoutInfo.visibleItemsInfo.size) {
|
||||||
|
scrollToItem(layoutInfo.visibleItemsInfo.size)
|
||||||
|
}
|
||||||
|
animateScrollToItem(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyGridState.isScrollingDownToIsFar(): Pair<Boolean, Boolean> {
|
||||||
|
var previousIndex by remember(this) {
|
||||||
|
mutableStateOf(firstVisibleItemIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousScrollOffset by remember(this) {
|
||||||
|
mutableStateOf(firstVisibleItemScrollOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return remember(this) {
|
||||||
|
derivedStateOf {
|
||||||
|
if (previousIndex != firstVisibleItemIndex) {
|
||||||
|
previousIndex > firstVisibleItemIndex
|
||||||
|
} else {
|
||||||
|
previousScrollOffset >= firstVisibleItemScrollOffset
|
||||||
|
}.also {
|
||||||
|
previousIndex = firstVisibleItemIndex
|
||||||
|
previousScrollOffset = firstVisibleItemScrollOffset
|
||||||
|
} to (firstVisibleItemIndex > layoutInfo.visibleItemsInfo.size)
|
||||||
|
}
|
||||||
|
}.value
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
package it.vfsfitvnm.vimusic.utils
|
package it.vfsfitvnm.vimusic.utils
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
|
||||||
suspend fun LazyListState.smoothScrollToTop() {
|
suspend fun LazyListState.smoothScrollToTop() {
|
||||||
if (firstVisibleItemIndex > layoutInfo.visibleItemsInfo.size) {
|
if (firstVisibleItemIndex > layoutInfo.visibleItemsInfo.size) {
|
||||||
@@ -8,3 +14,27 @@ suspend fun LazyListState.smoothScrollToTop() {
|
|||||||
}
|
}
|
||||||
animateScrollToItem(0)
|
animateScrollToItem(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyListState.isScrollingDownToIsFar(): Pair<Boolean, Boolean> {
|
||||||
|
var previousIndex by remember(this) {
|
||||||
|
mutableStateOf(firstVisibleItemIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousScrollOffset by remember(this) {
|
||||||
|
mutableStateOf(firstVisibleItemScrollOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return remember(this) {
|
||||||
|
derivedStateOf {
|
||||||
|
if (previousIndex != firstVisibleItemIndex) {
|
||||||
|
previousIndex > firstVisibleItemIndex
|
||||||
|
} else {
|
||||||
|
previousScrollOffset >= firstVisibleItemScrollOffset
|
||||||
|
}.also {
|
||||||
|
previousIndex = firstVisibleItemIndex
|
||||||
|
previousScrollOffset = firstVisibleItemScrollOffset
|
||||||
|
} to (firstVisibleItemIndex > layoutInfo.visibleItemsInfo.size)
|
||||||
|
}
|
||||||
|
}.value
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.utils
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScrollState.isScrollingDown(): Boolean {
|
||||||
|
var previousValue by remember(this) {
|
||||||
|
mutableStateOf(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return remember(this) {
|
||||||
|
derivedStateOf {
|
||||||
|
(previousValue >= value).also {
|
||||||
|
previousValue = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.value
|
||||||
|
}
|
||||||
13
app/src/main/res/drawable/chevron_up.xml
Normal file
13
app/src/main/res/drawable/chevron_up.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="512dp"
|
||||||
|
android:height="512dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:pathData="M112,328l144,-144l144,144"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="48"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user