Improve SearchResultScreen UI
This commit is contained in:
parent
8db6f7a13e
commit
e71e34c0d7
6 changed files with 450 additions and 231 deletions
|
@ -63,15 +63,15 @@ import it.vfsfitvnm.vimusic.ui.components.collapsedAnchor
|
||||||
import it.vfsfitvnm.vimusic.ui.components.dismissedAnchor
|
import it.vfsfitvnm.vimusic.ui.components.dismissedAnchor
|
||||||
import it.vfsfitvnm.vimusic.ui.components.expandedAnchor
|
import it.vfsfitvnm.vimusic.ui.components.expandedAnchor
|
||||||
import it.vfsfitvnm.vimusic.ui.components.rememberBottomSheetState
|
import it.vfsfitvnm.vimusic.ui.components.rememberBottomSheetState
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.home.HomeScreen
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen
|
import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.home.HomeScreen
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.player.PlayerView
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Appearance
|
import it.vfsfitvnm.vimusic.ui.styling.Appearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.colorPaletteOf
|
import it.vfsfitvnm.vimusic.ui.styling.colorPaletteOf
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.dynamicColorPaletteOf
|
import it.vfsfitvnm.vimusic.ui.styling.dynamicColorPaletteOf
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.typographyOf
|
import it.vfsfitvnm.vimusic.ui.styling.typographyOf
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.player.PlayerView
|
|
||||||
import it.vfsfitvnm.vimusic.utils.colorPaletteModeKey
|
import it.vfsfitvnm.vimusic.utils.colorPaletteModeKey
|
||||||
import it.vfsfitvnm.vimusic.utils.colorPaletteNameKey
|
import it.vfsfitvnm.vimusic.utils.colorPaletteNameKey
|
||||||
import it.vfsfitvnm.vimusic.utils.getEnum
|
import it.vfsfitvnm.vimusic.utils.getEnum
|
||||||
|
|
|
@ -2,6 +2,7 @@ package it.vfsfitvnm.vimusic.ui.screens.searchresult
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
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.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
@ -22,11 +23,10 @@ import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
inline fun <I : YouTube.Item> ItemSearchResultTab(
|
inline fun <I : YouTube.Item> ItemSearchResult(
|
||||||
query: String,
|
query: String,
|
||||||
filter: String,
|
filter: String,
|
||||||
crossinline onSearchAgain: () -> Unit,
|
crossinline onSearchAgain: () -> Unit,
|
||||||
isArtists: Boolean = false,
|
|
||||||
viewModel: ItemSearchResultViewModel<I> = viewModel(
|
viewModel: ItemSearchResultViewModel<I> = viewModel(
|
||||||
key = query + filter,
|
key = query + filter,
|
||||||
factory = object : ViewModelProvider.Factory {
|
factory = object : ViewModelProvider.Factory {
|
||||||
|
@ -36,7 +36,8 @@ inline fun <I : YouTube.Item> ItemSearchResultTab(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
crossinline itemContent: @Composable (LazyItemScope.(I) -> Unit)
|
crossinline itemContent: @Composable LazyItemScope.(I) -> Unit,
|
||||||
|
noinline itemShimmer: @Composable BoxScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
|
@ -45,7 +46,7 @@ inline fun <I : YouTube.Item> ItemSearchResultTab(
|
||||||
) {
|
) {
|
||||||
item(
|
item(
|
||||||
key = "header",
|
key = "header",
|
||||||
contentType = 0
|
contentType = 0,
|
||||||
) {
|
) {
|
||||||
Header(
|
Header(
|
||||||
title = query,
|
title = query,
|
||||||
|
@ -60,6 +61,7 @@ inline fun <I : YouTube.Item> ItemSearchResultTab(
|
||||||
|
|
||||||
items(
|
items(
|
||||||
items = viewModel.items,
|
items = viewModel.items,
|
||||||
|
key = { it.key!! },
|
||||||
itemContent = itemContent
|
itemContent = itemContent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,7 +75,8 @@ inline fun <I : YouTube.Item> ItemSearchResultTab(
|
||||||
item {
|
item {
|
||||||
SearchResultLoadingOrError(
|
SearchResultLoadingOrError(
|
||||||
errorMessage = throwable.javaClass.canonicalName,
|
errorMessage = throwable.javaClass.canonicalName,
|
||||||
onRetry = viewModel::fetch
|
onRetry = viewModel::fetch,
|
||||||
|
shimmerContent = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} ?: viewModel.continuationResult?.let {
|
} ?: viewModel.continuationResult?.let {
|
||||||
|
@ -88,7 +91,7 @@ inline fun <I : YouTube.Item> ItemSearchResultTab(
|
||||||
} ?: item(key = "loading") {
|
} ?: item(key = "loading") {
|
||||||
SearchResultLoadingOrError(
|
SearchResultLoadingOrError(
|
||||||
itemCount = if (viewModel.items.isEmpty()) 8 else 3,
|
itemCount = if (viewModel.items.isEmpty()) 8 else 3,
|
||||||
isLoadingArtists = isArtists
|
shimmerContent = itemShimmer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package it.vfsfitvnm.vimusic.ui.screens.searchresult
|
package it.vfsfitvnm.vimusic.ui.screens.searchresult
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
@ -12,8 +11,11 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ItemSearchResultViewModel<T : YouTube.Item>(private val query: String, private val filter: String) : ViewModel() {
|
class ItemSearchResultViewModel<T : YouTube.Item>(
|
||||||
val items = mutableStateListOf<T>()
|
private val query: String,
|
||||||
|
private val filter: String
|
||||||
|
) : ViewModel() {
|
||||||
|
var items by mutableStateOf(listOf<T>())
|
||||||
|
|
||||||
var continuationResult by mutableStateOf<Result<String?>?>(null)
|
var continuationResult by mutableStateOf<Result<String?>?>(null)
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ class ItemSearchResultViewModel<T : YouTube.Item>(private val query: String, pri
|
||||||
YouTube.search(query, filter, token)
|
YouTube.search(query, filter, token)
|
||||||
}?.map { searchResult ->
|
}?.map { searchResult ->
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
items.addAll(searchResult.items as List<T>)
|
items = items.plus(searchResult.items as List<T>).distinctBy(YouTube.Item::key)
|
||||||
searchResult.continuation
|
searchResult.continuation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
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.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -21,17 +21,23 @@ import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.playlistRoute
|
import it.vfsfitvnm.vimusic.ui.screens.playlistRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SmallAlbumItem
|
import it.vfsfitvnm.vimusic.ui.views.AlbumItem
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SmallArtistItem
|
import it.vfsfitvnm.vimusic.ui.views.AlbumItemShimmer
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SmallPlaylistItem
|
import it.vfsfitvnm.vimusic.ui.views.ArtistItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.views.ArtistItemShimmer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.views.PlaylistItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.views.PlaylistItemShimmer
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SmallSongItem
|
import it.vfsfitvnm.vimusic.ui.views.SmallSongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SmallVideoItem
|
import it.vfsfitvnm.vimusic.ui.views.SmallSongItemShimmer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.views.VideoItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.views.VideoItemShimmer
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.searchResultScreenTabIndexKey
|
import it.vfsfitvnm.vimusic.utils.searchResultScreenTabIndexKey
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
||||||
|
@ -76,125 +82,139 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
||||||
when (tabIndex) {
|
when (tabIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val thumbnailSizePx = Dimensions.thumbnails.song.px
|
|
||||||
|
|
||||||
ItemSearchResultTab<YouTube.Item.Song>(
|
|
||||||
query = query,
|
|
||||||
filter = searchFilter,
|
|
||||||
onSearchAgain = onSearchAgain
|
|
||||||
) { song ->
|
|
||||||
SmallSongItem(
|
|
||||||
song = song,
|
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
|
||||||
onClick = {
|
|
||||||
binder?.stopRadio()
|
|
||||||
binder?.player?.forcePlay(song.asMediaItem)
|
|
||||||
binder?.setupRadio(song.info.endpoint)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
1 -> {
|
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
ItemSearchResultTab<YouTube.Item.Album>(
|
ItemSearchResult<YouTube.Item.Song>(
|
||||||
query = query,
|
|
||||||
filter = searchFilter,
|
|
||||||
onSearchAgain = onSearchAgain
|
|
||||||
) { album ->
|
|
||||||
SmallAlbumItem(
|
|
||||||
album = album,
|
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(
|
|
||||||
indication = rememberRipple(bounded = true),
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onClick = { albumRoute(album.info.endpoint?.browseId) }
|
|
||||||
)
|
|
||||||
.padding(
|
|
||||||
vertical = Dimensions.itemsVerticalPadding,
|
|
||||||
horizontal = 16.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
2 -> {
|
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
|
||||||
|
|
||||||
ItemSearchResultTab<YouTube.Item.Artist>(
|
|
||||||
query = query,
|
query = query,
|
||||||
filter = searchFilter,
|
filter = searchFilter,
|
||||||
onSearchAgain = onSearchAgain,
|
onSearchAgain = onSearchAgain,
|
||||||
isArtists = true
|
itemContent = { song ->
|
||||||
) { artist ->
|
SmallSongItem(
|
||||||
SmallArtistItem(
|
song = song,
|
||||||
artist = artist,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
onClick = {
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
binder?.stopRadio()
|
||||||
modifier = Modifier
|
binder?.player?.forcePlay(song.asMediaItem)
|
||||||
.clickable(
|
binder?.setupRadio(song.info.endpoint)
|
||||||
indication = rememberRipple(bounded = true),
|
}
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
)
|
||||||
onClick = { artistRoute(artist.info.endpoint?.browseId) }
|
},
|
||||||
)
|
itemShimmer = {
|
||||||
.padding(
|
SmallSongItemShimmer(thumbnailSizeDp = thumbnailSizeDp)
|
||||||
vertical = Dimensions.itemsVerticalPadding,
|
}
|
||||||
horizontal = 16.dp
|
)
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
1 -> {
|
||||||
|
val thumbnailSizeDp = 108.dp
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
|
ItemSearchResult<YouTube.Item.Album>(
|
||||||
|
query = query,
|
||||||
|
filter = searchFilter,
|
||||||
|
onSearchAgain = onSearchAgain,
|
||||||
|
itemContent = { album ->
|
||||||
|
AlbumItem(
|
||||||
|
album = album,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = { albumRoute(album.info.endpoint?.browseId) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
},
|
||||||
|
itemShimmer = {
|
||||||
|
AlbumItemShimmer(thumbnailSizeDp = thumbnailSizeDp)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
val thumbnailSizeDp = 64.dp
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
|
ItemSearchResult<YouTube.Item.Artist>(
|
||||||
|
query = query,
|
||||||
|
filter = searchFilter,
|
||||||
|
onSearchAgain = onSearchAgain,
|
||||||
|
itemContent = { artist ->
|
||||||
|
ArtistItem(
|
||||||
|
artist = artist,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = { artistRoute(artist.info.endpoint?.browseId) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
itemShimmer = {
|
||||||
|
ArtistItemShimmer(thumbnailSizeDp = thumbnailSizeDp)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
3 -> {
|
3 -> {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val thumbnailSizePx = Dimensions.thumbnails.song.px
|
val thumbnailHeightDp = 72.dp
|
||||||
|
val thumbnailWidthDp = 128.dp
|
||||||
|
|
||||||
ItemSearchResultTab<YouTube.Item.Video>(
|
ItemSearchResult<YouTube.Item.Video>(
|
||||||
query = query,
|
query = query,
|
||||||
filter = searchFilter,
|
filter = searchFilter,
|
||||||
onSearchAgain = onSearchAgain
|
onSearchAgain = onSearchAgain,
|
||||||
) { video ->
|
itemContent = { video ->
|
||||||
SmallVideoItem(
|
VideoItem(
|
||||||
video = video,
|
video = video,
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
thumbnailWidthDp = thumbnailWidthDp,
|
||||||
onClick = {
|
thumbnailHeightDp = thumbnailHeightDp,
|
||||||
binder?.stopRadio()
|
onClick = {
|
||||||
binder?.player?.forcePlay(video.asMediaItem)
|
binder?.stopRadio()
|
||||||
binder?.setupRadio(video.info.endpoint)
|
binder?.player?.forcePlay(video.asMediaItem)
|
||||||
}
|
binder?.setupRadio(video.info.endpoint)
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
|
},
|
||||||
|
itemShimmer = {
|
||||||
|
VideoItemShimmer(
|
||||||
|
thumbnailHeightDp = thumbnailHeightDp,
|
||||||
|
thumbnailWidthDp = thumbnailWidthDp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
4, 5 -> {
|
4, 5 -> {
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = 108.dp
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
ItemSearchResultTab<YouTube.Item.Playlist>(
|
ItemSearchResult<YouTube.Item.Playlist>(
|
||||||
query = query,
|
query = query,
|
||||||
filter = searchFilter,
|
filter = searchFilter,
|
||||||
onSearchAgain = onSearchAgain
|
onSearchAgain = onSearchAgain,
|
||||||
) { playlist ->
|
itemContent = { playlist ->
|
||||||
SmallPlaylistItem(
|
PlaylistItem(
|
||||||
playlist = playlist,
|
playlist = playlist,
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rememberRipple(bounded = true),
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { playlistRoute(playlist.info.endpoint?.browseId) }
|
onClick = { playlistRoute(playlist.info.endpoint?.browseId) }
|
||||||
)
|
)
|
||||||
.padding(
|
)
|
||||||
vertical = Dimensions.itemsVerticalPadding,
|
},
|
||||||
horizontal = 16.dp
|
itemShimmer = {
|
||||||
)
|
PlaylistItemShimmer(thumbnailSizeDp = thumbnailSizeDp)
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package it.vfsfitvnm.vimusic.ui.views
|
package it.vfsfitvnm.vimusic.ui.views
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
@ -10,8 +15,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
@ -21,14 +29,18 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
|
import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
|
||||||
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
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.onOverlay
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.overlay
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
@ -44,6 +56,8 @@ fun SmallSongItemShimmer(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = Dimensions.itemsVerticalPadding)
|
||||||
) {
|
) {
|
||||||
Spacer(
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -58,29 +72,6 @@ fun SmallSongItemShimmer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SmallArtistItemShimmer(
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = CircleShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
TextPlaceholder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun SmallSongItem(
|
fun SmallSongItem(
|
||||||
|
@ -102,74 +93,80 @@ fun SmallSongItem(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun SmallVideoItem(
|
fun VideoItem(
|
||||||
video: YouTube.Item.Video,
|
video: YouTube.Item.Video,
|
||||||
thumbnailSizePx: Int,
|
thumbnailHeightDp: Dp,
|
||||||
|
thumbnailWidthDp: Dp,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
SongItem(
|
val menuState = LocalMenuState.current
|
||||||
thumbnailModel = video.thumbnail?.size(thumbnailSizePx),
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
title = video.info.name,
|
|
||||||
authors = (if (video.isOfficialMusicVideo) video.authors else video.views)
|
|
||||||
.joinToString("") { it.name },
|
|
||||||
durationText = video.durationText,
|
|
||||||
onClick = onClick,
|
|
||||||
menuContent = {
|
|
||||||
NonQueuedMediaItemMenu(mediaItem = video.asMediaItem)
|
|
||||||
},
|
|
||||||
modifier = modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun SmallPlaylistItem(
|
|
||||||
playlist: YouTube.Item.Playlist,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val (_, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = video.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
AsyncImage(
|
Box {
|
||||||
model = playlist.thumbnail?.size(thumbnailSizePx),
|
AsyncImage(
|
||||||
contentDescription = null,
|
model = video.thumbnail?.url,
|
||||||
contentScale = ContentScale.Crop,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
contentScale = ContentScale.Crop,
|
||||||
.clip(ThumbnailRoundness.shape)
|
modifier = Modifier
|
||||||
.size(thumbnailSizeDp)
|
.clip(thumbnailShape)
|
||||||
)
|
.size(width = thumbnailWidthDp, height = thumbnailHeightDp)
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
video.durationText?.let { durationText ->
|
||||||
modifier = Modifier
|
BasicText(
|
||||||
.weight(1f)
|
text = durationText,
|
||||||
) {
|
style = typography.xxs.medium.color(colorPalette.onOverlay),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.background(color = colorPalette.overlay, shape = RoundedCornerShape(2.dp))
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = playlist.info.name,
|
text = video.info.name,
|
||||||
style = typography.xs.semiBold,
|
style = typography.xs.semiBold,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = playlist.channel?.name ?: "",
|
text = video.authors.joinToString("") { it.name },
|
||||||
style = typography.xs.semiBold.secondary,
|
style = typography.xs.semiBold.secondary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
playlist.songCount?.let { songCount ->
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "$songCount songs",
|
text = video.views.firstOrNull()?.name ?: "",
|
||||||
style = typography.xxs.secondary,
|
style = typography.xxs.medium.secondary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
@ -177,60 +174,208 @@ fun SmallPlaylistItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun SmallAlbumItem(
|
fun VideoItemShimmer(
|
||||||
|
thumbnailHeightDp: Dp,
|
||||||
|
thumbnailWidthDp: Dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = Dimensions.itemsVerticalPadding)
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(width = thumbnailWidthDp, height = thumbnailHeightDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItem(
|
||||||
|
playlist: YouTube.Item.Playlist,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
AsyncImage(
|
||||||
|
model = playlist.thumbnail?.size(thumbnailSizePx),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
playlist.songCount?.let { songCount ->
|
||||||
|
BasicText(
|
||||||
|
text = "$songCount",
|
||||||
|
style = typography.xxs.medium.color(colorPalette.onOverlay),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.background(color = colorPalette.overlay, shape = RoundedCornerShape(2.dp))
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
BasicText(
|
||||||
|
text = playlist.info.name,
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = playlist.channel?.name ?: "",
|
||||||
|
style = typography.xs.semiBold.secondary,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItemShimmer(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AlbumItem(
|
||||||
album: YouTube.Item.Album,
|
album: YouTube.Item.Album,
|
||||||
thumbnailSizePx: Int,
|
thumbnailSizePx: Int,
|
||||||
thumbnailSizeDp: Dp,
|
thumbnailSizeDp: Dp,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val (_, typography) = LocalAppearance.current
|
val (_, typography, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = album.thumbnail?.size(thumbnailSizePx),
|
model = album.thumbnail?.size(thumbnailSizePx),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(ThumbnailRoundness.shape)
|
.clip(thumbnailShape)
|
||||||
.size(thumbnailSizeDp)
|
.size(thumbnailSizeDp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = album.info.name,
|
text = album.info.name,
|
||||||
style = typography.xs.semiBold,
|
style = typography.xs.semiBold,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = album.authors?.joinToString("") { it.name } ?: "",
|
text = album.authors?.joinToString("") { it.name } ?: "",
|
||||||
style = typography.xs.semiBold.secondary,
|
style = typography.xs.semiBold.secondary,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
album.year?.let { year ->
|
album.year?.let { year ->
|
||||||
BasicText(
|
BasicText(
|
||||||
text = year,
|
text = year,
|
||||||
style = typography.xxs.secondary,
|
style = typography.xxs.semiBold.secondary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SmallArtistItem(
|
fun AlbumItemShimmer(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistItem(
|
||||||
artist: YouTube.Item.Artist,
|
artist: YouTube.Item.Artist,
|
||||||
thumbnailSizePx: Int,
|
thumbnailSizePx: Int,
|
||||||
thumbnailSizeDp: Dp,
|
thumbnailSizeDp: Dp,
|
||||||
|
@ -240,8 +385,10 @@ fun SmallArtistItem(
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = artist.thumbnail?.size(thumbnailSizePx),
|
model = artist.thumbnail?.size(thumbnailSizePx),
|
||||||
|
@ -251,23 +398,49 @@ fun SmallArtistItem(
|
||||||
.size(thumbnailSizeDp)
|
.size(thumbnailSizeDp)
|
||||||
)
|
)
|
||||||
|
|
||||||
BasicText(
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
text = artist.info.name,
|
BasicText(
|
||||||
style = typography.xs.semiBold,
|
text = artist.info.name,
|
||||||
maxLines = 1,
|
style = typography.xs.semiBold,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistItemShimmer(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.background(color = colorPalette.shimmer, shape = CircleShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchResultLoadingOrError(
|
fun SearchResultLoadingOrError(
|
||||||
itemCount: Int = 0,
|
itemCount: Int = 0,
|
||||||
isLoadingArtists: Boolean = false,
|
|
||||||
errorMessage: String? = null,
|
errorMessage: String? = null,
|
||||||
onRetry: (() -> Unit)? = null
|
onRetry: (() -> Unit)? = null,
|
||||||
|
shimmerContent: @Composable BoxScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
LoadingOrError(
|
LoadingOrError(
|
||||||
errorMessage = errorMessage,
|
errorMessage = errorMessage,
|
||||||
|
@ -275,23 +448,28 @@ fun SearchResultLoadingOrError(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
repeat(itemCount) { index ->
|
repeat(itemCount) { index ->
|
||||||
if (isLoadingArtists) {
|
Box(
|
||||||
SmallArtistItemShimmer(
|
modifier = Modifier
|
||||||
thumbnailSizeDp = Dimensions.thumbnails.song,
|
.alpha(1f - index * 0.125f),
|
||||||
modifier = Modifier
|
content = shimmerContent
|
||||||
.alpha(1f - index * 0.125f)
|
)
|
||||||
.fillMaxWidth()
|
// if (isLoadingArtists) {
|
||||||
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
// SmallArtistItemShimmer(
|
||||||
)
|
// thumbnailSizeDp = Dimensions.thumbnails.song,
|
||||||
} else {
|
// modifier = Modifier
|
||||||
SmallSongItemShimmer(
|
// .alpha(1f - index * 0.125f)
|
||||||
thumbnailSizeDp = Dimensions.thumbnails.song,
|
// .fillMaxWidth()
|
||||||
modifier = Modifier
|
// .padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
.alpha(1f - index * 0.125f)
|
// )
|
||||||
.fillMaxWidth()
|
// } else {
|
||||||
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
// SmallSongItemShimmer(
|
||||||
)
|
// thumbnailSizeDp = Dimensions.thumbnails.song,
|
||||||
}
|
// modifier = Modifier
|
||||||
|
// .alpha(1f - index * 0.125f)
|
||||||
|
// .fillMaxWidth()
|
||||||
|
// .padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,7 @@ object YouTube {
|
||||||
|
|
||||||
sealed class Item {
|
sealed class Item {
|
||||||
abstract val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
abstract val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
||||||
|
abstract val key: String?
|
||||||
|
|
||||||
data class Song(
|
data class Song(
|
||||||
val info: Info<NavigationEndpoint.Endpoint.Watch>,
|
val info: Info<NavigationEndpoint.Endpoint.Watch>,
|
||||||
|
@ -185,6 +186,9 @@ object YouTube {
|
||||||
val durationText: String?,
|
val durationText: String?,
|
||||||
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
||||||
) : Item() {
|
) : Item() {
|
||||||
|
override val key: String?
|
||||||
|
get() = info.endpoint?.videoId
|
||||||
|
|
||||||
companion object : FromMusicShelfRendererContent<Song> {
|
companion object : FromMusicShelfRendererContent<Song> {
|
||||||
val Filter = Filter("EgWKAQIIAWoKEAkQBRAKEAMQBA%3D%3D")
|
val Filter = Filter("EgWKAQIIAWoKEAkQBRAKEAMQBA%3D%3D")
|
||||||
|
|
||||||
|
@ -232,6 +236,9 @@ object YouTube {
|
||||||
val durationText: String?,
|
val durationText: String?,
|
||||||
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
||||||
) : Item() {
|
) : Item() {
|
||||||
|
override val key: String?
|
||||||
|
get() = info.endpoint?.videoId
|
||||||
|
|
||||||
val isOfficialMusicVideo: Boolean
|
val isOfficialMusicVideo: Boolean
|
||||||
get() = info
|
get() = info
|
||||||
.endpoint
|
.endpoint
|
||||||
|
@ -278,6 +285,9 @@ object YouTube {
|
||||||
val year: String?,
|
val year: String?,
|
||||||
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
||||||
) : Item() {
|
) : Item() {
|
||||||
|
override val key: String?
|
||||||
|
get() = info.endpoint?.browseId
|
||||||
|
|
||||||
companion object : FromMusicShelfRendererContent<Album> {
|
companion object : FromMusicShelfRendererContent<Album> {
|
||||||
val Filter = Filter("EgWKAQIYAWoKEAkQChAFEAMQBA%3D%3D")
|
val Filter = Filter("EgWKAQIYAWoKEAkQChAFEAMQBA%3D%3D")
|
||||||
|
|
||||||
|
@ -312,6 +322,9 @@ object YouTube {
|
||||||
val info: Info<NavigationEndpoint.Endpoint.Browse>,
|
val info: Info<NavigationEndpoint.Endpoint.Browse>,
|
||||||
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
||||||
) : Item() {
|
) : Item() {
|
||||||
|
override val key: String?
|
||||||
|
get() = info.endpoint?.browseId
|
||||||
|
|
||||||
companion object : FromMusicShelfRendererContent<Artist> {
|
companion object : FromMusicShelfRendererContent<Artist> {
|
||||||
val Filter = Filter("EgWKAQIgAWoKEAkQChAFEAMQBA%3D%3D")
|
val Filter = Filter("EgWKAQIgAWoKEAkQChAFEAMQBA%3D%3D")
|
||||||
|
|
||||||
|
@ -341,6 +354,9 @@ object YouTube {
|
||||||
val songCount: Int?,
|
val songCount: Int?,
|
||||||
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?
|
||||||
) : Item() {
|
) : Item() {
|
||||||
|
override val key: String?
|
||||||
|
get() = info.endpoint?.browseId
|
||||||
|
|
||||||
companion object : FromMusicShelfRendererContent<Playlist> {
|
companion object : FromMusicShelfRendererContent<Playlist> {
|
||||||
override fun from(content: MusicShelfRenderer.Content): Playlist {
|
override fun from(content: MusicShelfRenderer.Content): Playlist {
|
||||||
val (mainRuns, otherRuns) = content.runs
|
val (mainRuns, otherRuns) = content.runs
|
||||||
|
|
Loading…
Reference in a new issue