mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
rework the artist detail header
- drop the passive star badge from the artists list - add an album count, star, more and placeholder bio under the artist image - move the more action down from the top bar into that row - put a shuffle button beside the artist name, which can now wrap to two lines - remove the now-unused StarredBadge component
This commit is contained in:
@@ -17,11 +17,8 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.Star
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -304,22 +301,6 @@ fun FilteredEmptyState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StarredBadge(
|
|
||||||
starred: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
size: Dp = 14.dp,
|
|
||||||
) {
|
|
||||||
if (starred) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Star,
|
|
||||||
contentDescription = "Starred",
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = modifier.size(size),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Modifier.statusBarScrim(): Modifier {
|
fun Modifier.statusBarScrim(): Modifier {
|
||||||
val top = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
|
val top = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
@@ -18,7 +19,9 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
@@ -29,6 +32,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.rounded.MoreHoriz
|
import androidx.compose.material.icons.rounded.MoreHoriz
|
||||||
|
import androidx.compose.material.icons.rounded.Shuffle
|
||||||
|
import androidx.compose.material.icons.rounded.Star
|
||||||
|
import androidx.compose.material.icons.rounded.StarBorder
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -53,10 +59,12 @@ import androidx.compose.ui.graphics.Brush
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shadow
|
import androidx.compose.ui.graphics.Shadow
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
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.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.subtracks.data.model.Album
|
import com.subtracks.data.model.Album
|
||||||
@@ -140,6 +148,8 @@ fun ArtistDetailRoute(
|
|||||||
onAlbumClick = onAlbumClick,
|
onAlbumClick = onAlbumClick,
|
||||||
onAlbumLongClick = { contextMenuHost?.show(it, actions) },
|
onAlbumLongClick = { contextMenuHost?.show(it, actions) },
|
||||||
onMore = { artist?.let { contextMenuHost?.show(MenuTarget.Artist(it, artThumbnail ?: art), actions) } },
|
onMore = { artist?.let { contextMenuHost?.show(MenuTarget.Artist(it, artThumbnail ?: art), actions) } },
|
||||||
|
starred = artist?.starred != null,
|
||||||
|
onToggleStar = artist?.let { a -> { setStar(StarType.Artist, a.id, a.starred == null) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +166,9 @@ fun ArtistDetailScreen(
|
|||||||
onAlbumClick: (Album) -> Unit,
|
onAlbumClick: (Album) -> Unit,
|
||||||
onAlbumLongClick: (MenuTarget) -> Unit = {},
|
onAlbumLongClick: (MenuTarget) -> Unit = {},
|
||||||
onMore: () -> Unit = {},
|
onMore: () -> Unit = {},
|
||||||
|
starred: Boolean = false,
|
||||||
|
onToggleStar: (() -> Unit)? = null,
|
||||||
|
onShuffle: () -> Unit = {},
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyGridState()
|
val listState = rememberLazyGridState()
|
||||||
@@ -230,41 +243,84 @@ fun ArtistDetailScreen(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
Box(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onSizeChanged { imageHeightPx = it.height.toFloat() }
|
.onSizeChanged { imageHeightPx = it.height.toFloat() },
|
||||||
.padding(bottom = 12.dp),
|
|
||||||
) {
|
) {
|
||||||
CoverArt(
|
Box(Modifier.fillMaxWidth()) {
|
||||||
ref = art,
|
CoverArt(
|
||||||
name = artist?.name.orEmpty(),
|
ref = art,
|
||||||
thumbnailRef = artThumbnail,
|
name = artist?.name.orEmpty(),
|
||||||
showPlaceholder = art == null,
|
thumbnailRef = artThumbnail,
|
||||||
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
showPlaceholder = art == null,
|
||||||
)
|
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
||||||
if (nameScrimAlpha > 0f) {
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.align(Alignment.BottomStart)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(ART_HEIGHT * 0.4f)
|
|
||||||
.graphicsLayer { alpha = nameScrimAlpha }
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
listOf(Color.Transparent, Color.Black.copy(alpha = 0.6f)),
|
|
||||||
),
|
|
||||||
).testTag(ARTIST_NAME_SCRIM_TAG),
|
|
||||||
)
|
)
|
||||||
|
if (nameScrimAlpha > 0f) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(ART_HEIGHT * 0.4f)
|
||||||
|
.graphicsLayer { alpha = nameScrimAlpha }
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
listOf(Color.Transparent, Color.Black.copy(alpha = 0.6f)),
|
||||||
|
),
|
||||||
|
).testTag(ARTIST_NAME_SCRIM_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(Modifier.align(Alignment.BottomStart).fillMaxWidth()) {
|
||||||
|
Text(
|
||||||
|
text = artist?.name.orEmpty(),
|
||||||
|
style = imageNameStyle,
|
||||||
|
color = Color.White,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(TITLE_INSET),
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = onShuffle,
|
||||||
|
modifier = Modifier.align(Alignment.BottomEnd).padding(end = 8.dp, bottom = 8.dp),
|
||||||
|
) {
|
||||||
|
ShadowedIcon(Icons.Rounded.Shuffle, "Shuffle artist", size = 28.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${artist?.albumCount ?: 0} ${if (artist?.albumCount == 1L) "album" else "albums"}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
IconButton(onClick = { onToggleStar?.invoke() }, enabled = onToggleStar != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (starred) Icons.Rounded.Star else Icons.Rounded.StarBorder,
|
||||||
|
contentDescription = if (starred) "Unstar" else "Star",
|
||||||
|
tint =
|
||||||
|
if (starred) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(onClick = onMore) {
|
||||||
|
Icon(Icons.Rounded.MoreHoriz, contentDescription = "More options")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = artist?.name.orEmpty(),
|
text = "Artist biography coming soon.",
|
||||||
style = imageNameStyle,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = Color.White,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.align(Alignment.BottomStart).padding(TITLE_INSET),
|
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,11 +392,6 @@ fun ArtistDetailScreen(
|
|||||||
Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
|
||||||
IconButton(onClick = onMore, modifier = Modifier.graphicsLayer { alpha = barFraction }) {
|
|
||||||
Icon(Icons.Rounded.MoreHoriz, contentDescription = "More options")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -355,3 +406,25 @@ fun ArtistDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShadowedIcon(
|
||||||
|
imageVector: ImageVector,
|
||||||
|
contentDescription: String,
|
||||||
|
size: Dp = 24.dp,
|
||||||
|
) {
|
||||||
|
Box(Modifier.size(size)) {
|
||||||
|
Icon(
|
||||||
|
imageVector = imageVector,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.Black.copy(alpha = 0.5f),
|
||||||
|
modifier = Modifier.matchParentSize().offset(x = 0.5.dp, y = 1.5.dp),
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = imageVector,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.subtracks.ui.library
|
package com.subtracks.ui.library
|
||||||
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -15,7 +13,6 @@ import androidx.compose.material3.ListItem
|
|||||||
import androidx.compose.material3.ListItemDefaults
|
import androidx.compose.material3.ListItemDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -33,7 +30,6 @@ import com.subtracks.ui.components.FilteredEmptyState
|
|||||||
import com.subtracks.ui.components.LoadingState
|
import com.subtracks.ui.components.LoadingState
|
||||||
import com.subtracks.ui.components.MenuTarget
|
import com.subtracks.ui.components.MenuTarget
|
||||||
import com.subtracks.ui.components.ResetScrollOnChange
|
import com.subtracks.ui.components.ResetScrollOnChange
|
||||||
import com.subtracks.ui.components.StarredBadge
|
|
||||||
import com.subtracks.ui.components.rememberViewportFill
|
import com.subtracks.ui.components.rememberViewportFill
|
||||||
import com.subtracks.ui.theme.PrefetchArtworkSeeds
|
import com.subtracks.ui.theme.PrefetchArtworkSeeds
|
||||||
|
|
||||||
@@ -79,18 +75,11 @@ fun ArtistsContent(
|
|||||||
PrefetchArtworkSeeds(coverArt(artist.coverArt, true))
|
PrefetchArtworkSeeds(coverArt(artist.coverArt, true))
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Row(
|
Text(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
text = artist.name,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
maxLines = 1,
|
||||||
) {
|
overflow = TextOverflow.Ellipsis,
|
||||||
Text(
|
)
|
||||||
text = artist.name,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
|
||||||
)
|
|
||||||
StarredBadge(artist.starred != null)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
Reference in New Issue
Block a user