Hide floating action button when scrolling down and add scroll to top button to each screen

This commit is contained in:
vfsfitvnm
2022-10-06 16:14:08 +02:00
parent 78c44988d7
commit b30b282628
25 changed files with 902 additions and 573 deletions

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -24,9 +25,9 @@ import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
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.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.items.SongItem
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
@@ -68,9 +69,12 @@ fun AlbumSongs(
val thumbnailSizeDp = Dimensions.thumbnails.song
val lazyListState = rememberLazyListState()
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
@@ -152,14 +156,16 @@ fun AlbumSongs(
}
}
PrimaryButton(
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
iconId = R.drawable.shuffle,
isEnabled = songs.isNotEmpty(),
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(DetailedSong::asMediaItem)
)
if (songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(DetailedSong::asMediaItem)
)
}
}
)
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.ui.components.LocalMenuState
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.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.items.SongItem
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
@@ -63,9 +64,12 @@ fun ArtistLocalSongs(
val songThumbnailSizeDp = Dimensions.thumbnails.song
val songThumbnailSizePx = songThumbnailSizeDp.px
val lazyListState = rememberLazyListState()
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
@@ -128,14 +132,18 @@ fun ArtistLocalSongs(
}
}
PrimaryButton(
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
iconId = R.drawable.shuffle,
isEnabled = !songs.isNullOrEmpty(),
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs!!.shuffled().map(DetailedSong::asMediaItem)
)
songs?.let { songs ->
if (songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(DetailedSong::asMediaItem)
)
}
}
}
)
}

View File

@@ -26,9 +26,9 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
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.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
@@ -70,6 +70,8 @@ fun ArtistOverview(
.padding(horizontal = 16.dp)
.padding(top = 24.dp, bottom = 8.dp)
val scrollState = rememberScrollState()
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
Box {
Column(
@@ -77,7 +79,7 @@ fun ArtistOverview(
modifier = Modifier
.background(colorPalette.background0)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.verticalScroll(scrollState)
.padding(LocalPlayerAwarePaddingValues.current)
) {
headerContent {
@@ -258,7 +260,8 @@ fun ArtistOverview(
}
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
PrimaryButton(
FloatingActionsContainerWithScrollToTop(
scrollState = scrollState,
iconId = R.drawable.shuffle,
onClick = {
binder?.stopRadio()

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.savers.DetailedSongListSaver
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.InFavoritesMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
@@ -70,8 +72,11 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
val thumbnailSizeDp = Dimensions.thumbnails.song
val thumbnailSize = thumbnailSizeDp.px
val lazyListState = rememberLazyListState()
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
@@ -120,6 +125,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
song = song,
onDismiss = menuState::hide
)
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(
song = song,
onDismiss = menuState::hide
@@ -129,7 +135,10 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
},
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
binder?.player?.forcePlayAtIndex(
songs.map(DetailedSong::asMediaItem),
index
)
}
)
.animateItemPlacement()
@@ -137,14 +146,16 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
}
}
PrimaryButton(
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
iconId = R.drawable.shuffle,
isEnabled = songs.isNotEmpty(),
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(DetailedSong::asMediaItem)
)
if (songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(DetailedSong::asMediaItem)
)
}
}
)
}

View File

@@ -7,11 +7,13 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.models.Album
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.HeaderIconButton
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
@@ -42,7 +45,8 @@ import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@Composable
fun HomeAlbums(
onAlbumClick: (Album) -> Unit
onAlbumClick: (Album) -> Unit,
onSearchClick: () -> Unit,
) {
val (colorPalette) = LocalAppearance.current
@@ -68,62 +72,73 @@ fun HomeAlbums(
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
)
LazyColumn(
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
.fillMaxSize()
) {
item(
key = "header",
contentType = 0
val lazyListState = rememberLazyListState()
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
.fillMaxSize()
) {
Header(title = "Albums") {
HeaderIconButton(
icon = R.drawable.calendar,
color = if (sortBy == AlbumSortBy.Year) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = AlbumSortBy.Year }
)
item(
key = "header",
contentType = 0
) {
Header(title = "Albums") {
HeaderIconButton(
icon = R.drawable.calendar,
color = if (sortBy == AlbumSortBy.Year) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = AlbumSortBy.Year }
)
HeaderIconButton(
icon = R.drawable.text,
color = if (sortBy == AlbumSortBy.Title) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = AlbumSortBy.Title }
)
HeaderIconButton(
icon = R.drawable.text,
color = if (sortBy == AlbumSortBy.Title) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = AlbumSortBy.Title }
)
HeaderIconButton(
icon = R.drawable.time,
color = if (sortBy == AlbumSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = AlbumSortBy.DateAdded }
)
HeaderIconButton(
icon = R.drawable.time,
color = if (sortBy == AlbumSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
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
.width(2.dp)
)
HeaderIconButton(
icon = R.drawable.arrow_up,
color = colorPalette.text,
onClick = { sortOrder = !sortOrder },
modifier = Modifier
.graphicsLayer { rotationZ = sortOrderIconRotation }
.clickable(onClick = { onAlbumClick(album) })
.animateItemPlacement()
)
}
}
items(
items = items,
key = Album::id
) { album ->
AlbumItem(
album = album,
thumbnailSizePx = thumbnailSizePx,
thumbnailSizeDp = thumbnailSizeDp,
modifier = Modifier
.clickable(onClick = { onAlbumClick(album) })
.animateItemPlacement()
)
}
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
iconId = R.drawable.search,
onClick = onSearchClick
)
}
}

View File

@@ -8,6 +8,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.models.Artist
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.HeaderIconButton
import it.vfsfitvnm.vimusic.ui.items.ArtistItem
@@ -46,7 +49,8 @@ import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@Composable
fun HomeArtistList(
onArtistClick: (Artist) -> Unit
onArtistClick: (Artist) -> Unit,
onSearchClick: () -> Unit,
) {
val (colorPalette) = LocalAppearance.current
@@ -72,61 +76,72 @@ fun HomeArtistList(
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
)
LazyVerticalGrid(
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
.background(colorPalette.background0)
.fillMaxSize()
) {
item(
key = "header",
contentType = 0,
span = { GridItemSpan(maxLineSpan) }
val lazyGridState = rememberLazyGridState()
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
.background(colorPalette.background0)
.fillMaxSize()
) {
Header(title = "Artists") {
HeaderIconButton(
icon = R.drawable.text,
color = if (sortBy == ArtistSortBy.Name) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = ArtistSortBy.Name }
)
item(
key = "header",
contentType = 0,
span = { GridItemSpan(maxLineSpan) }
) {
Header(title = "Artists") {
HeaderIconButton(
icon = R.drawable.text,
color = if (sortBy == ArtistSortBy.Name) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = ArtistSortBy.Name }
)
HeaderIconButton(
icon = R.drawable.time,
color = if (sortBy == ArtistSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
onClick = { sortBy = ArtistSortBy.DateAdded }
)
HeaderIconButton(
icon = R.drawable.time,
color = if (sortBy == ArtistSortBy.DateAdded) colorPalette.text else colorPalette.textDisabled,
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
.width(2.dp)
)
HeaderIconButton(
icon = R.drawable.arrow_up,
color = colorPalette.text,
onClick = { sortOrder = !sortOrder },
modifier = Modifier
.graphicsLayer { rotationZ = sortOrderIconRotation }
.clickable(onClick = { onArtistClick(artist) })
.animateItemPlacement()
)
}
}
items(items = items, key = Artist::id) { artist ->
ArtistItem(
artist = artist,
thumbnailSizePx = thumbnailSizePx,
thumbnailSizeDp = thumbnailSizeDp,
alternative = true,
modifier = Modifier
.clickable(onClick = { onArtistClick(artist) })
.animateItemPlacement()
)
}
FloatingActionsContainerWithScrollToTop(
lazyGridState = lazyGridState,
iconId = R.drawable.search,
onClick = onSearchClick
)
}
}

View File

@@ -1,5 +1,6 @@
package it.vfsfitvnm.vimusic.ui.screens.home
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@@ -7,6 +8,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.query
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.HeaderIconButton
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.flow.flowOn
@ExperimentalAnimationApi
@ExperimentalFoundationApi
@Composable
fun HomePlaylists(
onBuiltInPlaylist: (BuiltInPlaylist) -> Unit,
onPlaylistClick: (Playlist) -> Unit,
onSearchClick: () -> Unit,
) {
val (colorPalette) = LocalAppearance.current
@@ -95,101 +101,112 @@ fun HomePlaylists(
val thumbnailSizeDp = 108.dp
val thumbnailSizePx = thumbnailSizeDp.px
LazyVerticalGrid(
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 }
)
val lazyGridState = rememberLazyGridState()
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
.weight(1f)
.clickable(onClick = { onBuiltInPlaylist(BuiltInPlaylist.Favorites) })
.animateItemPlacement()
)
}
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(
item(key = "offline") {
PlaylistItem(
icon = R.drawable.airplane,
colorTint = colorPalette.blue,
name = "Offline",
songCount = null,
thumbnailSizeDp = thumbnailSizeDp,
alternative = true,
modifier = Modifier
.width(2.dp)
.clickable(onClick = { onBuiltInPlaylist(BuiltInPlaylist.Offline) })
.animateItemPlacement()
)
}
HeaderIconButton(
icon = R.drawable.arrow_up,
color = colorPalette.text,
onClick = { sortOrder = !sortOrder },
items(items = items, key = { it.playlist.id }) { playlistPreview ->
PlaylistItem(
playlist = playlistPreview,
thumbnailSizeDp = thumbnailSizeDp,
thumbnailSizePx = thumbnailSizePx,
alternative = true,
modifier = Modifier
.graphicsLayer { rotationZ = sortOrderIconRotation }
.clickable(onClick = { onPlaylistClick(playlistPreview.playlist) })
.animateItemPlacement()
)
}
}
item(key = "favorites") {
PlaylistItem(
icon = R.drawable.heart,
colorTint = colorPalette.red,
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()
)
}
FloatingActionsContainerWithScrollToTop(
lazyGridState = lazyGridState,
iconId = R.drawable.search,
onClick = onSearchClick
)
}
}

View File

@@ -114,8 +114,6 @@ fun HomeScreen(onPlaylistUrl: (String) -> Unit) {
Item(3, "Artists", R.drawable.person)
Item(4, "Albums", R.drawable.disc)
},
primaryIconButtonId = R.drawable.search,
onPrimaryIconButtonClick = { searchRoute("") }
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
when (currentTabIndex) {
@@ -123,14 +121,24 @@ fun HomeScreen(onPlaylistUrl: (String) -> Unit) {
onAlbumClick = { albumRoute(it) },
onArtistClick = { artistRoute(it) },
onPlaylistClick = { playlistRoute(it) },
onSearchClick = { searchRoute("") }
)
1 -> HomeSongs(
onSearchClick = { searchRoute("") }
)
1 -> HomeSongs()
2 -> HomePlaylists(
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) })
}
}
}

View File

@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
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.savers.DetailedSongListSaver
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.HeaderIconButton
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.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
@@ -62,7 +61,9 @@ import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@Composable
fun HomeSongs() {
fun HomeSongs(
onSearchClick: () -> Unit
) {
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
val menuState = LocalMenuState.current
@@ -187,11 +188,10 @@ fun HomeSongs() {
}
}
ScrollToTop(
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
modifier = Modifier
.offset(x = Dimensions.navigationRailIconOffset - Dimensions.navigationRailWidth)
.align(Alignment.BottomStart)
iconId = R.drawable.search,
onClick = onSearchClick
)
}
}

View File

@@ -43,6 +43,7 @@ import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
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.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
@@ -81,6 +82,7 @@ fun QuickPicks(
onAlbumClick: (String) -> Unit,
onArtistClick: (String) -> Unit,
onPlaylistClick: (String) -> Unit,
onSearchClick: () -> Unit,
) {
val (colorPalette, typography) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
@@ -129,6 +131,8 @@ fun QuickPicks(
)
}
val scrollState = rememberScrollState()
BoxWithConstraints {
val itemInHorizontalGridWidth = maxWidth * quickPicksLazyGridItemWidthFactor
@@ -136,7 +140,7 @@ fun QuickPicks(
modifier = Modifier
.background(colorPalette.background0)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.verticalScroll(scrollState)
.padding(LocalPlayerAwarePaddingValues.current)
) {
Header(title = "Quick picks")
@@ -345,5 +349,11 @@ fun QuickPicks(
}
}
}
FloatingActionsContainerWithScrollToTop(
scrollState = scrollState,
iconId = R.drawable.search,
onClick = onSearchClick
)
}
}

View File

@@ -35,13 +35,13 @@ import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
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.HeaderIconButton
import it.vfsfitvnm.vimusic.ui.components.themed.IconButton
import it.vfsfitvnm.vimusic.ui.components.themed.InPlaylistMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
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.TextFieldDialog
import it.vfsfitvnm.vimusic.ui.items.SongItem
@@ -274,17 +274,18 @@ fun LocalPlaylistSongs(
}
}
PrimaryButton(
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
iconId = R.drawable.shuffle,
isEnabled = playlistWithSongs?.songs?.isNotEmpty() == true,
onClick = {
playlistWithSongs?.songs
?.shuffled()
?.map(DetailedSong::asMediaItem)
?.let { mediaItems ->
playlistWithSongs?.songs?.let { songs ->
if (songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(mediaItems)
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(DetailedSong::asMediaItem)
)
}
}
}
)
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.ui.components.LocalMenuState
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.HeaderIconButton
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
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.adaptiveThumbnailContent
import it.vfsfitvnm.vimusic.ui.items.SongItem
@@ -62,7 +63,7 @@ import kotlinx.coroutines.withContext
fun PlaylistSongList(
browseId: String,
) {
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
val (colorPalette) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
val context = LocalContext.current
val menuState = LocalMenuState.current
@@ -162,9 +163,12 @@ fun PlaylistSongList(
val thumbnailContent = adaptiveThumbnailContent(playlistPage == null, playlistPage?.thumbnail?.url)
val lazyListState = rememberLazyListState()
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
@@ -219,13 +223,17 @@ fun PlaylistSongList(
}
}
PrimaryButton(
FloatingActionsContainerWithScrollToTop(
lazyListState = lazyListState,
iconId = R.drawable.shuffle,
isEnabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
onClick = {
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(mediaItems.shuffled())
playlistPage?.songsPage?.items?.let { songs ->
if (songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs.shuffled().map(Innertube.SongItem::asMediaItem)
)
}
}
}
)

View File

@@ -3,9 +3,11 @@ package it.vfsfitvnm.vimusic.ui.screens.search
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
@@ -21,6 +23,7 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
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.InHistoryMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
@@ -65,68 +68,75 @@ fun LocalSongSearch(
val thumbnailSizeDp = Dimensions.thumbnails.song
val thumbnailSizePx = thumbnailSizeDp.px
LazyColumn(
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()) }
)
}
}
)
}
val lazyListState = rememberLazyListState()
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)
Box {
LazyColumn(
state = lazyListState,
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()) }
)
}
)
.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)
}
}

View File

@@ -1,5 +1,6 @@
package it.vfsfitvnm.vimusic.ui.screens.search
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.BasicTextField
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.listSaver
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.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
@@ -62,6 +65,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@Composable
fun OnlineSearch(
textFieldValue: TextFieldValue,
@@ -112,139 +116,74 @@ fun OnlineSearch(
FocusRequester()
}
LazyColumn(
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.fillMaxSize()
) {
item(
key = "header",
contentType = 0
val lazyListState = rememberLazyListState()
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.fillMaxSize()
) {
Header(
titleContent = {
BasicTextField(
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)
item(
key = "header",
contentType = 0
) {
Spacer(
modifier = Modifier
.padding(horizontal = 8.dp)
.size(20.dp)
.paint(
painter = timeIconPainter,
colorFilter = ColorFilter.tint(colorPalette.textDisabled)
)
)
BasicText(
text = searchQuery.query,
style = typography.s.secondary,
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)
Header(
titleContent = {
BasicTextField(
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)
)
.padding(horizontal = 8.dp)
.size(20.dp)
)
},
actionsContent = {
if (playlistId != null) {
val isAlbum = playlistId.startsWith("OLAK5uy_")
Image(
painter = arrowForwardIconPainter,
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.textDisabled),
modifier = Modifier
.clickable(
indication = rippleIndication,
interactionSource = remember { MutableInteractionSource() },
onClick = {
onTextFieldValueChanged(
TextFieldValue(
text = searchQuery.query,
selection = TextRange(searchQuery.query.length)
)
)
}
SecondaryTextButton(
text = "View ${if (isAlbum) "album" else "playlist"}",
onClick = { onViewPlaylist(textFieldValue.text) }
)
}
Spacer(
modifier = Modifier
.weight(1f)
)
.rotate(225f)
.padding(horizontal = 8.dp)
.size(22.dp)
if (textFieldValue.text.isNotEmpty()) {
SecondaryTextButton(
text = "Clear",
onClick = { onTextFieldValueChanged(TextFieldValue()) }
)
}
}
)
}
}
suggestionsResult?.getOrNull()?.let { suggestions ->
items(items = suggestions) { suggestion ->
items(
items = history,
key = SearchQuery::id
) { searchQuery ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable(onClick = { onSearch(suggestion) })
.clickable(onClick = { onSearch(searchQuery.query) })
.fillMaxWidth()
.padding(all = 16.dp)
) {
@@ -252,16 +191,38 @@ fun OnlineSearch(
modifier = Modifier
.padding(horizontal = 8.dp)
.size(20.dp)
.paint(
painter = timeIconPainter,
colorFilter = ColorFilter.tint(colorPalette.textDisabled)
)
)
BasicText(
text = suggestion,
text = searchQuery.query,
style = typography.s.secondary,
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)
}
}
)
.padding(horizontal = 8.dp)
.size(20.dp)
)
Image(
painter = arrowForwardIconPainter,
contentDescription = null,
@@ -273,8 +234,8 @@ fun OnlineSearch(
onClick = {
onTextFieldValueChanged(
TextFieldValue(
text = suggestion,
selection = TextRange(suggestion.length)
text = searchQuery.query,
selection = TextRange(searchQuery.query.length)
)
)
}
@@ -285,21 +246,71 @@ fun OnlineSearch(
)
}
}
} ?: suggestionsResult?.exceptionOrNull()?.let {
item {
Box(
modifier = Modifier
.fillMaxSize()
) {
BasicText(
text = "An error has occurred.",
style = typography.s.secondary.center,
suggestionsResult?.getOrNull()?.let { suggestions ->
items(items = suggestions) { suggestion ->
Row(
verticalAlignment = Alignment.CenterVertically,
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) {

View File

@@ -1,6 +1,7 @@
package it.vfsfitvnm.vimusic.ui.screens.searchresult
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.savers.nullableSaver
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.utils.center
import it.vfsfitvnm.vimusic.utils.produceSaveableState
@@ -70,51 +72,55 @@ inline fun <T : Innertube.Item> ItemsPage(
}
}
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = modifier
.fillMaxSize()
) {
item(
key = "header",
contentType = "header",
Box {
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = modifier
.fillMaxSize()
) {
headerContent(null)
}
items(
items = itemsPage?.items ?: emptyList(),
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()
)
item(
key = "header",
contentType = "header",
) {
headerContent(null)
}
}
if (!(itemsPage != null && itemsPage?.continuation == null)) {
item(key = "loading") {
val isFirstLoad = itemsPage?.items.isNullOrEmpty()
ShimmerHost(
modifier = Modifier
.run {
if (isFirstLoad) fillParentMaxSize() else this
items(
items = itemsPage?.items ?: emptyList(),
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)) {
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)
}
}