mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
ui: use the album grid for artist icons too
Load up to four thumbnail covers per visible artist row on demand and render the same 1-4 layout inside the circular artist icon. Lists stay on the expensive per-row query for now.
This commit is contained in:
@@ -0,0 +1,230 @@
|
|||||||
|
package com.subtracks.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Outline
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import com.subtracks.data.model.Artist
|
||||||
|
import com.subtracks.data.model.CoverArtRef
|
||||||
|
import com.subtracks.data.repo.LibraryRepository
|
||||||
|
import com.subtracks.data.repo.SourceRepository
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
internal suspend fun loadArtistArtCovers(
|
||||||
|
library: LibraryRepository,
|
||||||
|
source: SourceRepository,
|
||||||
|
artistId: String,
|
||||||
|
): List<CoverArtRef> {
|
||||||
|
val sourceId = library.activeSourceId.first() ?: return emptyList()
|
||||||
|
val own = library.artistOwnCoverArt(sourceId, artistId)
|
||||||
|
val ids =
|
||||||
|
if (own != null) {
|
||||||
|
listOf(own)
|
||||||
|
} else {
|
||||||
|
library.artistFallbackCoverArts(sourceId, artistId)
|
||||||
|
}
|
||||||
|
return ids.take(4).mapNotNull { source.coverArt(it, thumbnail = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistIcon(
|
||||||
|
artist: Artist,
|
||||||
|
fallback: CoverArtRef?,
|
||||||
|
loadCovers: suspend (String) -> List<CoverArtRef>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val covers by produceState(initialValue = emptyList<CoverArtRef>(), artist.id) {
|
||||||
|
value = loadCovers(artist.id)
|
||||||
|
}
|
||||||
|
if (covers.size >= 2) {
|
||||||
|
ArtistArtGrid(covers = covers, name = artist.name, modifier = modifier)
|
||||||
|
} else {
|
||||||
|
CoverArt(
|
||||||
|
ref = covers.firstOrNull() ?: fallback,
|
||||||
|
name = artist.name,
|
||||||
|
showPlaceholder = covers.isEmpty() && fallback == null,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistArtGrid(
|
||||||
|
covers: List<CoverArtRef>,
|
||||||
|
name: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
singleThumbnail: CoverArtRef? = null,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
covers.size >= 4 -> {
|
||||||
|
Column(modifier) {
|
||||||
|
Row(Modifier.weight(1f).fillMaxWidth()) {
|
||||||
|
covers.take(2).forEach { ref ->
|
||||||
|
CoverArt(ref = ref, name = name, modifier = Modifier.weight(1f).fillMaxHeight())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(Modifier.weight(1f).fillMaxWidth()) {
|
||||||
|
covers.drop(2).take(2).forEach { ref ->
|
||||||
|
CoverArt(ref = ref, name = name, modifier = Modifier.weight(1f).fillMaxHeight())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
covers.size == 3 -> {
|
||||||
|
Box(modifier) {
|
||||||
|
covers.forEachIndexed { index, ref ->
|
||||||
|
CoverArt(
|
||||||
|
ref = ref,
|
||||||
|
name = name,
|
||||||
|
modifier = Modifier.matchParentSize().clip(YRegionShape(index)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
covers.size == 2 -> {
|
||||||
|
Row(modifier) {
|
||||||
|
covers.forEach { ref ->
|
||||||
|
CoverArt(ref = ref, name = name, modifier = Modifier.weight(1f).fillMaxHeight())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
covers.size == 1 -> {
|
||||||
|
CoverArt(
|
||||||
|
ref = covers.first(),
|
||||||
|
name = name,
|
||||||
|
thumbnailRef = singleThumbnail,
|
||||||
|
showPlaceholder = false,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class YRegionShape(
|
||||||
|
private val region: Int,
|
||||||
|
) : Shape {
|
||||||
|
override fun createOutline(
|
||||||
|
size: Size,
|
||||||
|
layoutDirection: LayoutDirection,
|
||||||
|
density: Density,
|
||||||
|
): Outline {
|
||||||
|
val center = Offset(size.width / 2f, size.height / 2f)
|
||||||
|
val left = rayPoint(size, 210.0)
|
||||||
|
val right = rayPoint(size, 330.0)
|
||||||
|
val bottom = Offset(size.width / 2f, size.height)
|
||||||
|
val path = Path()
|
||||||
|
when (region) {
|
||||||
|
0 -> {
|
||||||
|
path.moveTo(center.x, center.y)
|
||||||
|
path.lineTo(left.x, left.y)
|
||||||
|
addClockwise(path, size, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
path.moveTo(center.x, center.y)
|
||||||
|
path.lineTo(bottom.x, bottom.y)
|
||||||
|
addClockwise(path, size, bottom, left)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
path.moveTo(center.x, center.y)
|
||||||
|
path.lineTo(right.x, right.y)
|
||||||
|
addClockwise(path, size, right, bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path.lineTo(center.x, center.y)
|
||||||
|
path.close()
|
||||||
|
return Outline.Generic(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rayPoint(
|
||||||
|
size: Size,
|
||||||
|
angleDegrees: Double,
|
||||||
|
): Offset {
|
||||||
|
val cx = size.width / 2.0
|
||||||
|
val cy = size.height / 2.0
|
||||||
|
val radians = angleDegrees * PI / 180.0
|
||||||
|
val dx = cos(radians)
|
||||||
|
val dy = sin(radians)
|
||||||
|
val tx =
|
||||||
|
when {
|
||||||
|
dx > 0 -> (size.width - cx) / dx
|
||||||
|
dx < 0 -> -cx / dx
|
||||||
|
else -> Double.POSITIVE_INFINITY
|
||||||
|
}
|
||||||
|
val ty =
|
||||||
|
when {
|
||||||
|
dy > 0 -> (size.height - cy) / dy
|
||||||
|
dy < 0 -> -cy / dy
|
||||||
|
else -> Double.POSITIVE_INFINITY
|
||||||
|
}
|
||||||
|
val t = min(tx, ty)
|
||||||
|
return Offset((cx + dx * t).toFloat(), (cy + dy * t).toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addClockwise(
|
||||||
|
path: Path,
|
||||||
|
size: Size,
|
||||||
|
from: Offset,
|
||||||
|
to: Offset,
|
||||||
|
) {
|
||||||
|
val w = size.width
|
||||||
|
val h = size.height
|
||||||
|
val corners =
|
||||||
|
listOf(
|
||||||
|
Offset(0f, 0f),
|
||||||
|
Offset(w, 0f),
|
||||||
|
Offset(w, h),
|
||||||
|
Offset(0f, h),
|
||||||
|
)
|
||||||
|
val fromParam = perimeterParam(from, size)
|
||||||
|
val toParam = perimeterParam(to, size)
|
||||||
|
if (fromParam <= toParam) {
|
||||||
|
corners
|
||||||
|
.filter { perimeterParam(it, size) > fromParam && perimeterParam(it, size) < toParam }
|
||||||
|
.forEach { path.lineTo(it.x, it.y) }
|
||||||
|
} else {
|
||||||
|
corners
|
||||||
|
.filter { perimeterParam(it, size) > fromParam }
|
||||||
|
.forEach { path.lineTo(it.x, it.y) }
|
||||||
|
corners
|
||||||
|
.filter { perimeterParam(it, size) < toParam }
|
||||||
|
.forEach { path.lineTo(it.x, it.y) }
|
||||||
|
}
|
||||||
|
path.lineTo(to.x, to.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun perimeterParam(
|
||||||
|
point: Offset,
|
||||||
|
size: Size,
|
||||||
|
): Float {
|
||||||
|
val w = size.width
|
||||||
|
val h = size.height
|
||||||
|
return when {
|
||||||
|
point.y <= 0f -> point.x
|
||||||
|
point.x >= w -> w + point.y
|
||||||
|
point.y >= h -> w + h + (w - point.x)
|
||||||
|
else -> 2f * w + h + (h - point.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@ import com.subtracks.data.model.Playlist
|
|||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
import com.subtracks.data.source.StarType
|
import com.subtracks.data.source.StarType
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.ArtistIcon
|
||||||
import com.subtracks.ui.components.ContextMenuHost
|
import com.subtracks.ui.components.ContextMenuHost
|
||||||
import com.subtracks.ui.components.CoverArt
|
import com.subtracks.ui.components.CoverArt
|
||||||
import com.subtracks.ui.components.ItemActions
|
import com.subtracks.ui.components.ItemActions
|
||||||
@@ -108,6 +109,7 @@ fun HomeListRoute(
|
|||||||
genres = genres,
|
genres = genres,
|
||||||
decades = decades,
|
decades = decades,
|
||||||
coverArt = viewModel::coverArt,
|
coverArt = viewModel::coverArt,
|
||||||
|
artistCovers = viewModel::artistArtCovers,
|
||||||
playingSongId = playback.item?.id,
|
playingSongId = playback.item?.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAlbumClick = onAlbumClick,
|
onAlbumClick = onAlbumClick,
|
||||||
@@ -133,6 +135,7 @@ fun HomeListScreen(
|
|||||||
genres: List<String> = emptyList(),
|
genres: List<String> = emptyList(),
|
||||||
decades: List<Long> = emptyList(),
|
decades: List<Long> = emptyList(),
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef? = { _, _ -> null },
|
coverArt: (String?, Boolean) -> CoverArtRef? = { _, _ -> null },
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
playingSongId: String? = null,
|
playingSongId: String? = null,
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onAlbumClick: (Album) -> Unit = {},
|
onAlbumClick: (Album) -> Unit = {},
|
||||||
@@ -192,6 +195,7 @@ fun HomeListScreen(
|
|||||||
ArtistsList(
|
ArtistsList(
|
||||||
artists = artists,
|
artists = artists,
|
||||||
coverArt = coverArt,
|
coverArt = coverArt,
|
||||||
|
artistCovers = artistCovers,
|
||||||
info = { artistListInfo(request, it, now, resources) },
|
info = { artistListInfo(request, it, now, resources) },
|
||||||
onArtistClick = onArtistClick,
|
onArtistClick = onArtistClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
@@ -246,6 +250,7 @@ fun HomeListScreen(
|
|||||||
ArtistsList(
|
ArtistsList(
|
||||||
artists = artists,
|
artists = artists,
|
||||||
coverArt = coverArt,
|
coverArt = coverArt,
|
||||||
|
artistCovers = artistCovers,
|
||||||
info = { artistListInfo(request, it, now, resources) },
|
info = { artistListInfo(request, it, now, resources) },
|
||||||
onArtistClick = onArtistClick,
|
onArtistClick = onArtistClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
@@ -444,6 +449,7 @@ internal fun AlbumListRow(
|
|||||||
private fun ArtistsList(
|
private fun ArtistsList(
|
||||||
artists: LazyPagingItems<Artist>,
|
artists: LazyPagingItems<Artist>,
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef>,
|
||||||
info: (Artist) -> String?,
|
info: (Artist) -> String?,
|
||||||
onArtistClick: (Artist) -> Unit,
|
onArtistClick: (Artist) -> Unit,
|
||||||
onLongClick: (MenuTarget) -> Unit,
|
onLongClick: (MenuTarget) -> Unit,
|
||||||
@@ -463,6 +469,7 @@ private fun ArtistsList(
|
|||||||
ArtistListRow(
|
ArtistListRow(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
coverArt = coverArt,
|
coverArt = coverArt,
|
||||||
|
artistCovers = artistCovers,
|
||||||
info = info(artist),
|
info = info(artist),
|
||||||
onClick = { onArtistClick(artist) },
|
onClick = { onArtistClick(artist) },
|
||||||
onLongClick = { onLongClick(MenuTarget.Artist(artist, coverArt(artist.coverArt, true))) },
|
onLongClick = { onLongClick(MenuTarget.Artist(artist, coverArt(artist.coverArt, true))) },
|
||||||
@@ -479,6 +486,7 @@ internal fun ArtistListRow(
|
|||||||
info: String?,
|
info: String?,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -488,9 +496,10 @@ internal fun ArtistListRow(
|
|||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
CoverArt(
|
ArtistIcon(
|
||||||
ref = coverArt(artist.coverArt, true),
|
artist = artist,
|
||||||
name = artist.name,
|
fallback = coverArt(artist.coverArt, true),
|
||||||
|
loadCovers = artistCovers,
|
||||||
modifier = Modifier.size(ALBUM_ROW_COVER).clip(CircleShape),
|
modifier = Modifier.size(ALBUM_ROW_COVER).clip(CircleShape),
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(12.dp))
|
Spacer(Modifier.width(12.dp))
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.subtracks.data.prefs.PlaylistSort
|
|||||||
import com.subtracks.data.repo.LibraryRepository
|
import com.subtracks.data.repo.LibraryRepository
|
||||||
import com.subtracks.data.repo.SourceRepository
|
import com.subtracks.data.repo.SourceRepository
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.loadArtistArtCovers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@@ -156,6 +157,8 @@ class HomeListViewModel(
|
|||||||
thumbnail: Boolean,
|
thumbnail: Boolean,
|
||||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||||
|
|
||||||
|
suspend fun artistArtCovers(artistId: String): List<CoverArtRef> = loadArtistArtCovers(libraryRepository, sourceRepository, artistId)
|
||||||
|
|
||||||
fun play(song: Song) {
|
fun play(song: Song) {
|
||||||
val genre = request.genre
|
val genre = request.genre
|
||||||
when {
|
when {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import com.subtracks.data.model.Playlist
|
|||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
import com.subtracks.data.source.StarType
|
import com.subtracks.data.source.StarType
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.ArtistIcon
|
||||||
import com.subtracks.ui.components.ContextMenuHost
|
import com.subtracks.ui.components.ContextMenuHost
|
||||||
import com.subtracks.ui.components.CoverArt
|
import com.subtracks.ui.components.CoverArt
|
||||||
import com.subtracks.ui.components.EmptyState
|
import com.subtracks.ui.components.EmptyState
|
||||||
@@ -137,6 +138,7 @@ fun HomeRoute(
|
|||||||
playlists = viewModel.downloadedPlaylists.collectAsLazyPagingItems(),
|
playlists = viewModel.downloadedPlaylists.collectAsLazyPagingItems(),
|
||||||
songs = viewModel.downloadedSongs.collectAsStateWithLifecycle(initialValue = emptyList()).value,
|
songs = viewModel.downloadedSongs.collectAsStateWithLifecycle(initialValue = emptyList()).value,
|
||||||
coverArt = viewModel::coverArt,
|
coverArt = viewModel::coverArt,
|
||||||
|
artistCovers = viewModel::artistArtCovers,
|
||||||
playingSongId = playback.item?.id,
|
playingSongId = playback.item?.id,
|
||||||
topInset = topInset,
|
topInset = topInset,
|
||||||
bottomInset = bottomInset,
|
bottomInset = bottomInset,
|
||||||
@@ -152,6 +154,7 @@ fun HomeRoute(
|
|||||||
HomeScreen(
|
HomeScreen(
|
||||||
feed = feed,
|
feed = feed,
|
||||||
coverArt = viewModel::coverArt,
|
coverArt = viewModel::coverArt,
|
||||||
|
artistCovers = viewModel::artistArtCovers,
|
||||||
playingSongId = playback.item?.id,
|
playingSongId = playback.item?.id,
|
||||||
topInset = topInset,
|
topInset = topInset,
|
||||||
bottomInset = bottomInset,
|
bottomInset = bottomInset,
|
||||||
@@ -173,6 +176,7 @@ fun HomeScreen(
|
|||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
playingSongId: String?,
|
playingSongId: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
topInset: Dp = 0.dp,
|
topInset: Dp = 0.dp,
|
||||||
bottomInset: Dp = 0.dp,
|
bottomInset: Dp = 0.dp,
|
||||||
onAlbumClick: (Album) -> Unit = {},
|
onAlbumClick: (Album) -> Unit = {},
|
||||||
@@ -215,6 +219,7 @@ fun HomeScreen(
|
|||||||
HomeSection.RecentlyPlayedArtists,
|
HomeSection.RecentlyPlayedArtists,
|
||||||
feed.recentlyPlayedArtists,
|
feed.recentlyPlayedArtists,
|
||||||
coverArt,
|
coverArt,
|
||||||
|
artistCovers,
|
||||||
onArtistClick,
|
onArtistClick,
|
||||||
onLongClick,
|
onLongClick,
|
||||||
onMore,
|
onMore,
|
||||||
@@ -249,6 +254,7 @@ fun HomeScreen(
|
|||||||
HomeSection.MostPlayedArtists,
|
HomeSection.MostPlayedArtists,
|
||||||
feed.mostPlayedArtists,
|
feed.mostPlayedArtists,
|
||||||
coverArt,
|
coverArt,
|
||||||
|
artistCovers,
|
||||||
onArtistClick,
|
onArtistClick,
|
||||||
onLongClick,
|
onLongClick,
|
||||||
onMore,
|
onMore,
|
||||||
@@ -296,6 +302,7 @@ fun OfflineHomeScreen(
|
|||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
playingSongId: String?,
|
playingSongId: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
topInset: Dp = 0.dp,
|
topInset: Dp = 0.dp,
|
||||||
bottomInset: Dp = 0.dp,
|
bottomInset: Dp = 0.dp,
|
||||||
onAlbumClick: (Album) -> Unit = {},
|
onAlbumClick: (Album) -> Unit = {},
|
||||||
@@ -312,7 +319,7 @@ fun OfflineHomeScreen(
|
|||||||
) {
|
) {
|
||||||
homeTitle()
|
homeTitle()
|
||||||
downloadedAlbumRow(albums, coverArt, onAlbumClick, onLongClick) { onMore(OfflineListKind.Albums) }
|
downloadedAlbumRow(albums, coverArt, onAlbumClick, onLongClick) { onMore(OfflineListKind.Albums) }
|
||||||
downloadedArtistRow(artists, coverArt, onArtistClick, onLongClick) { onMore(OfflineListKind.Artists) }
|
downloadedArtistRow(artists, coverArt, artistCovers, onArtistClick, onLongClick) { onMore(OfflineListKind.Artists) }
|
||||||
downloadedPlaylistRow(playlists, coverArt, onPlaylistClick, onLongClick) { onMore(OfflineListKind.Playlists) }
|
downloadedPlaylistRow(playlists, coverArt, onPlaylistClick, onLongClick) { onMore(OfflineListKind.Playlists) }
|
||||||
songList(
|
songList(
|
||||||
key = "downloaded-songs",
|
key = "downloaded-songs",
|
||||||
@@ -365,6 +372,7 @@ private fun LazyListScope.downloadedAlbumRow(
|
|||||||
private fun LazyListScope.downloadedArtistRow(
|
private fun LazyListScope.downloadedArtistRow(
|
||||||
artists: LazyPagingItems<Artist>,
|
artists: LazyPagingItems<Artist>,
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef>,
|
||||||
onArtistClick: (Artist) -> Unit,
|
onArtistClick: (Artist) -> Unit,
|
||||||
onLongClick: (MenuTarget) -> Unit,
|
onLongClick: (MenuTarget) -> Unit,
|
||||||
onMore: () -> Unit,
|
onMore: () -> Unit,
|
||||||
@@ -386,6 +394,7 @@ private fun LazyListScope.downloadedArtistRow(
|
|||||||
ArtistTile(
|
ArtistTile(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
ref = ref,
|
ref = ref,
|
||||||
|
artistCovers = artistCovers,
|
||||||
onClick = { onArtistClick(artist) },
|
onClick = { onArtistClick(artist) },
|
||||||
onLongClick = { onLongClick(MenuTarget.Artist(artist, ref)) },
|
onLongClick = { onLongClick(MenuTarget.Artist(artist, ref)) },
|
||||||
)
|
)
|
||||||
@@ -607,6 +616,7 @@ private fun LazyListScope.artistRow(
|
|||||||
section: HomeSection,
|
section: HomeSection,
|
||||||
artists: List<Artist>,
|
artists: List<Artist>,
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef>,
|
||||||
onArtistClick: (Artist) -> Unit,
|
onArtistClick: (Artist) -> Unit,
|
||||||
onLongClick: (MenuTarget) -> Unit,
|
onLongClick: (MenuTarget) -> Unit,
|
||||||
onMore: (HomeSection) -> Unit,
|
onMore: (HomeSection) -> Unit,
|
||||||
@@ -629,6 +639,7 @@ private fun LazyListScope.artistRow(
|
|||||||
ArtistTile(
|
ArtistTile(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
ref = ref,
|
ref = ref,
|
||||||
|
artistCovers = artistCovers,
|
||||||
onClick = { onArtistClick(artist) },
|
onClick = { onArtistClick(artist) },
|
||||||
onLongClick = { onLongClick(MenuTarget.Artist(artist, ref)) },
|
onLongClick = { onLongClick(MenuTarget.Artist(artist, ref)) },
|
||||||
)
|
)
|
||||||
@@ -780,6 +791,7 @@ private fun AlbumTile(
|
|||||||
private fun ArtistTile(
|
private fun ArtistTile(
|
||||||
artist: Artist,
|
artist: Artist,
|
||||||
ref: CoverArtRef?,
|
ref: CoverArtRef?,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef>,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -787,9 +799,10 @@ private fun ArtistTile(
|
|||||||
modifier = Modifier.width(ARTIST_TILE_SIZE).combinedClickable(onClick = onClick, onLongClick = onLongClick),
|
modifier = Modifier.width(ARTIST_TILE_SIZE).combinedClickable(onClick = onClick, onLongClick = onLongClick),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
CoverArt(
|
ArtistIcon(
|
||||||
ref = ref,
|
artist = artist,
|
||||||
name = artist.name,
|
fallback = ref,
|
||||||
|
loadCovers = artistCovers,
|
||||||
modifier = Modifier.size(ARTIST_TILE_SIZE).clip(CircleShape),
|
modifier = Modifier.size(ARTIST_TILE_SIZE).clip(CircleShape),
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(6.dp))
|
Spacer(Modifier.height(6.dp))
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.subtracks.data.repo.rediscoverCutoff
|
|||||||
import com.subtracks.data.sync.SyncManager
|
import com.subtracks.data.sync.SyncManager
|
||||||
import com.subtracks.data.sync.SyncStatus
|
import com.subtracks.data.sync.SyncStatus
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.loadArtistArtCovers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -127,6 +128,8 @@ class HomeViewModel(
|
|||||||
thumbnail: Boolean,
|
thumbnail: Boolean,
|
||||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||||
|
|
||||||
|
suspend fun artistArtCovers(artistId: String): List<CoverArtRef> = loadArtistArtCovers(libraryRepository, sourceRepository, artistId)
|
||||||
|
|
||||||
fun playStarred(song: Song) {
|
fun playStarred(song: Song) {
|
||||||
viewModelScope.playStarredList(libraryRepository, playbackController, song)
|
viewModelScope.playStarredList(libraryRepository, playbackController, song)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ import com.subtracks.data.model.CoverArtRef
|
|||||||
import com.subtracks.data.model.ListDownloadStatus
|
import com.subtracks.data.model.ListDownloadStatus
|
||||||
import com.subtracks.data.source.StarType
|
import com.subtracks.data.source.StarType
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.ArtistArtGrid
|
||||||
import com.subtracks.ui.components.CloseWhenDownloadsGone
|
import com.subtracks.ui.components.CloseWhenDownloadsGone
|
||||||
import com.subtracks.ui.components.ContextMenuHost
|
import com.subtracks.ui.components.ContextMenuHost
|
||||||
import com.subtracks.ui.components.CoverArt
|
import com.subtracks.ui.components.CoverArt
|
||||||
@@ -402,13 +403,22 @@ fun ArtistDetailScreen(
|
|||||||
.onSizeChanged { imageHeightPx = it.height.toFloat() },
|
.onSizeChanged { imageHeightPx = it.height.toFloat() },
|
||||||
) {
|
) {
|
||||||
Box(Modifier.fillMaxWidth()) {
|
Box(Modifier.fillMaxWidth()) {
|
||||||
ArtistHeroArt(
|
if (artCovers.isNotEmpty()) {
|
||||||
|
ArtistArtGrid(
|
||||||
covers = artCovers,
|
covers = artCovers,
|
||||||
fallback = art,
|
|
||||||
fallbackThumbnail = artThumbnail,
|
|
||||||
name = displayName,
|
name = displayName,
|
||||||
|
singleThumbnail = artThumbnail,
|
||||||
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
CoverArt(
|
||||||
|
ref = art,
|
||||||
|
name = displayName,
|
||||||
|
thumbnailRef = artThumbnail,
|
||||||
|
showPlaceholder = art == null,
|
||||||
|
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
||||||
|
)
|
||||||
|
}
|
||||||
if (nameScrimAlpha > 0f) {
|
if (nameScrimAlpha > 0f) {
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -833,181 +843,6 @@ internal fun splitBioCollapsed(
|
|||||||
return BioLines(lines, more = true)
|
return BioLines(lines, more = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ArtistHeroArt(
|
|
||||||
covers: List<CoverArtRef>,
|
|
||||||
fallback: CoverArtRef?,
|
|
||||||
fallbackThumbnail: CoverArtRef?,
|
|
||||||
name: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
when {
|
|
||||||
covers.size >= 4 -> {
|
|
||||||
Column(modifier) {
|
|
||||||
Row(Modifier.weight(1f).fillMaxWidth()) {
|
|
||||||
covers.take(2).forEach { ref ->
|
|
||||||
CoverArt(ref = ref, name = name, modifier = Modifier.weight(1f).fillMaxHeight())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(Modifier.weight(1f).fillMaxWidth()) {
|
|
||||||
covers.drop(2).take(2).forEach { ref ->
|
|
||||||
CoverArt(ref = ref, name = name, modifier = Modifier.weight(1f).fillMaxHeight())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
covers.size == 3 -> {
|
|
||||||
Box(modifier) {
|
|
||||||
covers.forEachIndexed { index, ref ->
|
|
||||||
CoverArt(
|
|
||||||
ref = ref,
|
|
||||||
name = name,
|
|
||||||
modifier = Modifier.matchParentSize().clip(YRegionShape(index)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
covers.size == 2 -> {
|
|
||||||
Row(modifier) {
|
|
||||||
covers.forEach { ref ->
|
|
||||||
CoverArt(ref = ref, name = name, modifier = Modifier.weight(1f).fillMaxHeight())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
covers.size == 1 -> {
|
|
||||||
CoverArt(
|
|
||||||
ref = covers.first(),
|
|
||||||
name = name,
|
|
||||||
thumbnailRef = fallbackThumbnail,
|
|
||||||
showPlaceholder = false,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
CoverArt(
|
|
||||||
ref = fallback,
|
|
||||||
name = name,
|
|
||||||
thumbnailRef = fallbackThumbnail,
|
|
||||||
showPlaceholder = fallback == null,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class YRegionShape(
|
|
||||||
private val region: Int,
|
|
||||||
) : Shape {
|
|
||||||
override fun createOutline(
|
|
||||||
size: Size,
|
|
||||||
layoutDirection: LayoutDirection,
|
|
||||||
density: Density,
|
|
||||||
): Outline {
|
|
||||||
val center = Offset(size.width / 2f, size.height / 2f)
|
|
||||||
val left = rayPoint(size, 210.0)
|
|
||||||
val right = rayPoint(size, 330.0)
|
|
||||||
val bottom = Offset(size.width / 2f, size.height)
|
|
||||||
val path = Path()
|
|
||||||
when (region) {
|
|
||||||
0 -> {
|
|
||||||
path.moveTo(center.x, center.y)
|
|
||||||
path.lineTo(left.x, left.y)
|
|
||||||
addClockwise(path, size, left, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
1 -> {
|
|
||||||
path.moveTo(center.x, center.y)
|
|
||||||
path.lineTo(bottom.x, bottom.y)
|
|
||||||
addClockwise(path, size, bottom, left)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
path.moveTo(center.x, center.y)
|
|
||||||
path.lineTo(right.x, right.y)
|
|
||||||
addClockwise(path, size, right, bottom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path.lineTo(center.x, center.y)
|
|
||||||
path.close()
|
|
||||||
return Outline.Generic(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun rayPoint(
|
|
||||||
size: Size,
|
|
||||||
angleDegrees: Double,
|
|
||||||
): Offset {
|
|
||||||
val cx = size.width / 2.0
|
|
||||||
val cy = size.height / 2.0
|
|
||||||
val radians = angleDegrees * PI / 180.0
|
|
||||||
val dx = cos(radians)
|
|
||||||
val dy = sin(radians)
|
|
||||||
val tx =
|
|
||||||
when {
|
|
||||||
dx > 0 -> (size.width - cx) / dx
|
|
||||||
dx < 0 -> -cx / dx
|
|
||||||
else -> Double.POSITIVE_INFINITY
|
|
||||||
}
|
|
||||||
val ty =
|
|
||||||
when {
|
|
||||||
dy > 0 -> (size.height - cy) / dy
|
|
||||||
dy < 0 -> -cy / dy
|
|
||||||
else -> Double.POSITIVE_INFINITY
|
|
||||||
}
|
|
||||||
val t = min(tx, ty)
|
|
||||||
return Offset((cx + dx * t).toFloat(), (cy + dy * t).toFloat())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addClockwise(
|
|
||||||
path: Path,
|
|
||||||
size: Size,
|
|
||||||
from: Offset,
|
|
||||||
to: Offset,
|
|
||||||
) {
|
|
||||||
val w = size.width
|
|
||||||
val h = size.height
|
|
||||||
val corners =
|
|
||||||
listOf(
|
|
||||||
Offset(0f, 0f),
|
|
||||||
Offset(w, 0f),
|
|
||||||
Offset(w, h),
|
|
||||||
Offset(0f, h),
|
|
||||||
)
|
|
||||||
val fromParam = perimeterParam(from, size)
|
|
||||||
val toParam = perimeterParam(to, size)
|
|
||||||
if (fromParam <= toParam) {
|
|
||||||
corners
|
|
||||||
.filter { perimeterParam(it, size) > fromParam && perimeterParam(it, size) < toParam }
|
|
||||||
.forEach { path.lineTo(it.x, it.y) }
|
|
||||||
} else {
|
|
||||||
corners
|
|
||||||
.filter { perimeterParam(it, size) > fromParam }
|
|
||||||
.forEach { path.lineTo(it.x, it.y) }
|
|
||||||
corners
|
|
||||||
.filter { perimeterParam(it, size) < toParam }
|
|
||||||
.forEach { path.lineTo(it.x, it.y) }
|
|
||||||
}
|
|
||||||
path.lineTo(to.x, to.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun perimeterParam(
|
|
||||||
point: Offset,
|
|
||||||
size: Size,
|
|
||||||
): Float {
|
|
||||||
val w = size.width
|
|
||||||
val h = size.height
|
|
||||||
return when {
|
|
||||||
point.y <= 0f -> point.x
|
|
||||||
point.x >= w -> w + point.y
|
|
||||||
point.y >= h -> w + h + (w - point.x)
|
|
||||||
else -> 2f * w + h + (h - point.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ArtistSectionHeader(
|
private fun ArtistSectionHeader(
|
||||||
text: String,
|
text: String,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import com.subtracks.R
|
|||||||
import com.subtracks.data.model.Artist
|
import com.subtracks.data.model.Artist
|
||||||
import com.subtracks.data.model.CoverArtRef
|
import com.subtracks.data.model.CoverArtRef
|
||||||
import com.subtracks.data.model.ListDownloadStatus
|
import com.subtracks.data.model.ListDownloadStatus
|
||||||
import com.subtracks.ui.components.CoverArt
|
import com.subtracks.ui.components.ArtistIcon
|
||||||
import com.subtracks.ui.components.EmptyState
|
import com.subtracks.ui.components.EmptyState
|
||||||
import com.subtracks.ui.components.FilteredEmptyState
|
import com.subtracks.ui.components.FilteredEmptyState
|
||||||
import com.subtracks.ui.components.ListDownloadIndicator
|
import com.subtracks.ui.components.ListDownloadIndicator
|
||||||
@@ -55,6 +55,7 @@ fun ArtistsContent(
|
|||||||
onSync: () -> Unit = {},
|
onSync: () -> Unit = {},
|
||||||
downloadStatuses: Map<String, ListDownloadStatus> = emptyMap(),
|
downloadStatuses: Map<String, ListDownloadStatus> = emptyMap(),
|
||||||
scrollToTopKey: Any? = null,
|
scrollToTopKey: Any? = null,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
items.itemCount == 0 && items.loadState.refresh is LoadState.Loading -> {
|
items.itemCount == 0 && items.loadState.refresh is LoadState.Loading -> {
|
||||||
@@ -104,9 +105,10 @@ fun ArtistsContent(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
CoverArt(
|
ArtistIcon(
|
||||||
ref = coverArt(artist.coverArt, true),
|
artist = artist,
|
||||||
name = artist.name,
|
fallback = coverArt(artist.coverArt, true),
|
||||||
|
loadCovers = artistCovers,
|
||||||
modifier = Modifier.size(48.dp).clip(CircleShape),
|
modifier = Modifier.size(48.dp).clip(CircleShape),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ fun LibraryRoute(
|
|||||||
artists = viewModel.artists,
|
artists = viewModel.artists,
|
||||||
playlists = viewModel.playlists,
|
playlists = viewModel.playlists,
|
||||||
coverArt = viewModel::coverArt,
|
coverArt = viewModel::coverArt,
|
||||||
|
artistCovers = viewModel::artistArtCovers,
|
||||||
onAlbumClick = onAlbumClick,
|
onAlbumClick = onAlbumClick,
|
||||||
onArtistClick = onArtistClick,
|
onArtistClick = onArtistClick,
|
||||||
onPlaylistClick = onPlaylistClick,
|
onPlaylistClick = onPlaylistClick,
|
||||||
@@ -396,6 +397,7 @@ fun LibraryScreen(
|
|||||||
onSync: () -> Unit,
|
onSync: () -> Unit,
|
||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
onOpenSearch: () -> Unit = {},
|
onOpenSearch: () -> Unit = {},
|
||||||
offline: Boolean = false,
|
offline: Boolean = false,
|
||||||
onExitOffline: () -> Unit = {},
|
onExitOffline: () -> Unit = {},
|
||||||
@@ -539,6 +541,7 @@ fun LibraryScreen(
|
|||||||
onSync = onSync,
|
onSync = onSync,
|
||||||
downloadStatuses = artistDownloads,
|
downloadStatuses = artistDownloads,
|
||||||
scrollToTopKey = scrollToTopKey,
|
scrollToTopKey = scrollToTopKey,
|
||||||
|
artistCovers = artistCovers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.subtracks.data.repo.SourceRepository
|
|||||||
import com.subtracks.data.sync.SyncManager
|
import com.subtracks.data.sync.SyncManager
|
||||||
import com.subtracks.data.sync.SyncStatus
|
import com.subtracks.data.sync.SyncStatus
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.loadArtistArtCovers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -211,6 +212,8 @@ class LibraryViewModel(
|
|||||||
thumbnail: Boolean,
|
thumbnail: Boolean,
|
||||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||||
|
|
||||||
|
suspend fun artistArtCovers(artistId: String): List<CoverArtRef> = loadArtistArtCovers(libraryRepository, sourceRepository, artistId)
|
||||||
|
|
||||||
fun sync() {
|
fun sync() {
|
||||||
if (syncManager.status.value !is SyncStatus.Running) syncManager.requestSync()
|
if (syncManager.status.value !is SyncStatus.Running) syncManager.requestSync()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ fun SearchRoute(
|
|||||||
if (next != 0) filterMask = next
|
if (next != 0) filterMask = next
|
||||||
},
|
},
|
||||||
coverArt = viewModel::coverArt,
|
coverArt = viewModel::coverArt,
|
||||||
|
artistCovers = viewModel::artistArtCovers,
|
||||||
playingSongId = playback.item?.id,
|
playingSongId = playback.item?.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAlbumClick = {
|
onAlbumClick = {
|
||||||
@@ -146,6 +147,7 @@ fun SearchScreen(
|
|||||||
filters: Set<SearchFilter> = ALL_FILTERS,
|
filters: Set<SearchFilter> = ALL_FILTERS,
|
||||||
onToggleFilter: (SearchFilter) -> Unit = {},
|
onToggleFilter: (SearchFilter) -> Unit = {},
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef? = { _, _ -> null },
|
coverArt: (String?, Boolean) -> CoverArtRef? = { _, _ -> null },
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef> = { emptyList() },
|
||||||
playingSongId: String? = null,
|
playingSongId: String? = null,
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onAlbumClick: (Album) -> Unit = {},
|
onAlbumClick: (Album) -> Unit = {},
|
||||||
@@ -215,7 +217,7 @@ fun SearchScreen(
|
|||||||
contentPadding = PaddingValues(bottom = 24.dp),
|
contentPadding = PaddingValues(bottom = 24.dp),
|
||||||
) {
|
) {
|
||||||
if (showPlaylists) playlistResults(results.playlists, playlistsLabel, coverArt, onPlaylistClick, onLongClick)
|
if (showPlaylists) playlistResults(results.playlists, playlistsLabel, coverArt, onPlaylistClick, onLongClick)
|
||||||
if (showArtists) artistResults(results.artists, artistsLabel, coverArt, onArtistClick, onLongClick)
|
if (showArtists) artistResults(results.artists, artistsLabel, coverArt, artistCovers, onArtistClick, onLongClick)
|
||||||
if (showAlbums) albumResults(results.albums, albumsLabel, coverArt, onAlbumClick, onLongClick)
|
if (showAlbums) albumResults(results.albums, albumsLabel, coverArt, onAlbumClick, onLongClick)
|
||||||
if (showSongs) songResults(results.songs, songsLabel, coverArt, playingSongId, onSongClick, onLongClick)
|
if (showSongs) songResults(results.songs, songsLabel, coverArt, playingSongId, onSongClick, onLongClick)
|
||||||
}
|
}
|
||||||
@@ -309,6 +311,7 @@ private fun LazyListScope.artistResults(
|
|||||||
artists: List<Artist>,
|
artists: List<Artist>,
|
||||||
header: String,
|
header: String,
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
|
artistCovers: suspend (String) -> List<CoverArtRef>,
|
||||||
onClick: (Artist) -> Unit,
|
onClick: (Artist) -> Unit,
|
||||||
onLongClick: (MenuTarget) -> Unit,
|
onLongClick: (MenuTarget) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -318,6 +321,7 @@ private fun LazyListScope.artistResults(
|
|||||||
ArtistListRow(
|
ArtistListRow(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
coverArt = coverArt,
|
coverArt = coverArt,
|
||||||
|
artistCovers = artistCovers,
|
||||||
info = null,
|
info = null,
|
||||||
onClick = { onClick(artist) },
|
onClick = { onClick(artist) },
|
||||||
onLongClick = { onLongClick(MenuTarget.Artist(artist, coverArt(artist.coverArt, true))) },
|
onLongClick = { onLongClick(MenuTarget.Artist(artist, coverArt(artist.coverArt, true))) },
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.subtracks.data.model.Song
|
|||||||
import com.subtracks.data.repo.LibraryRepository
|
import com.subtracks.data.repo.LibraryRepository
|
||||||
import com.subtracks.data.repo.SourceRepository
|
import com.subtracks.data.repo.SourceRepository
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
|
import com.subtracks.ui.components.loadArtistArtCovers
|
||||||
import com.subtracks.ui.home.playSongInContext
|
import com.subtracks.ui.home.playSongInContext
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -74,6 +75,8 @@ class SearchViewModel(
|
|||||||
thumbnail: Boolean,
|
thumbnail: Boolean,
|
||||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||||
|
|
||||||
|
suspend fun artistArtCovers(artistId: String): List<CoverArtRef> = loadArtistArtCovers(libraryRepository, sourceRepository, artistId)
|
||||||
|
|
||||||
fun play(song: Song) {
|
fun play(song: Song) {
|
||||||
viewModelScope.playSongInContext(libraryRepository, playbackController, song)
|
viewModelScope.playSongInContext(libraryRepository, playbackController, song)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -613,6 +613,13 @@ class AppScreenshotTest {
|
|||||||
artists = remember { flowOf(PagingData.from(Fixtures.artists)) },
|
artists = remember { flowOf(PagingData.from(Fixtures.artists)) },
|
||||||
playlists = remember { flowOf(PagingData.from(Fixtures.playlists)) },
|
playlists = remember { flowOf(PagingData.from(Fixtures.playlists)) },
|
||||||
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
|
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
|
||||||
|
artistCovers = { id ->
|
||||||
|
Fixtures.albums
|
||||||
|
.filter { it.artistId == id }
|
||||||
|
.take(4)
|
||||||
|
.mapNotNull { it.coverArt }
|
||||||
|
.map { CoverArtRef(it, "test:$it") }
|
||||||
|
},
|
||||||
onAlbumClick = {},
|
onAlbumClick = {},
|
||||||
onArtistClick = {},
|
onArtistClick = {},
|
||||||
onPlaylistClick = {},
|
onPlaylistClick = {},
|
||||||
|
|||||||
Reference in New Issue
Block a user