mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
ui: keep the artist gradient anchored to the hero image
Use symmetric page margins for the artist bio, let lines below the action buttons use the full width, and stop the expanding bio from shifting the hero gradient.
This commit is contained in:
@@ -134,6 +134,7 @@ private val SHUFFLE_RESERVE = 86.dp
|
||||
private val SHUFFLE_HANG = SHUFFLE_HEIGHT / 2
|
||||
private val BIO_ACTION_RESERVE = 104.dp
|
||||
private val BIO_ACTION_HEIGHT = 48.dp
|
||||
private val BIO_ACTION_OFFSET = 8.dp
|
||||
private val BIO_TOP_GAP = 8.dp
|
||||
|
||||
internal fun estimateScrollPx(
|
||||
@@ -363,7 +364,9 @@ fun ArtistDetailScreen(
|
||||
BoxWithConstraints(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
||||
val screenHeightPx = with(density) { maxHeight.toPx() }
|
||||
val barHeightPx = with(density) { barHeight.toPx() }
|
||||
val gradientScrollPx = scrollPx - imageHeightPx
|
||||
// The gradient is anchored to the hero image, not the whole header, whose bio can expand.
|
||||
val heroHeightPx = with(density) { ART_HEIGHT.toPx() }
|
||||
val gradientScrollPx = scrollPx - heroHeightPx
|
||||
val barColor = heroBarColor(effectiveArtwork, gradientScrollPx, barHeightPx, screenHeightPx)
|
||||
HeroGradient(
|
||||
colors = effectiveArtwork,
|
||||
@@ -441,7 +444,7 @@ fun ArtistDetailScreen(
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = TITLE_INSET),
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = TITLE_INSET),
|
||||
) {
|
||||
Text(
|
||||
text = metaText,
|
||||
@@ -579,12 +582,15 @@ private fun ArtistBio(
|
||||
remember(style) {
|
||||
measurer.measure(AnnotatedString("X"), style, maxLines = 1).size.height
|
||||
}.coerceAtLeast(1)
|
||||
val reserveLines = ceil(with(density) { BIO_ACTION_HEIGHT.toPx() } / lineHeightPx).toInt().coerceAtLeast(1)
|
||||
val reserveLines =
|
||||
ceil(with(density) { (BIO_ACTION_HEIGHT - BIO_ACTION_OFFSET).toPx() } / lineHeightPx)
|
||||
.toInt()
|
||||
.coerceAtLeast(1)
|
||||
val bioLines =
|
||||
remember(text, fullWidth, reserve, reserveLines, collapsed, expanded, moreText, lessText) {
|
||||
when {
|
||||
collapsed -> {
|
||||
splitBioCollapsed(measurer, text, style, fullWidth, reserve, moreText, maxLines = 3)
|
||||
splitBioCollapsed(measurer, text, style, fullWidth, reserve, reserveLines, moreText, maxLines = 3)
|
||||
}
|
||||
|
||||
expanded -> {
|
||||
@@ -620,7 +626,7 @@ private fun ArtistBio(
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(Modifier.align(Alignment.TopEnd).offset(y = (-8).dp)) {
|
||||
Row(Modifier.align(Alignment.TopEnd).offset(y = -BIO_ACTION_OFFSET)) {
|
||||
ArtistActions(artist, starred, onToggleStar, onMore)
|
||||
}
|
||||
}
|
||||
@@ -742,6 +748,7 @@ internal fun splitBioCollapsed(
|
||||
style: TextStyle,
|
||||
fullWidth: Int,
|
||||
reserve: Int,
|
||||
reserveLines: Int,
|
||||
moreText: String,
|
||||
maxLines: Int = 2,
|
||||
): BioLines {
|
||||
@@ -752,13 +759,13 @@ internal fun splitBioCollapsed(
|
||||
.split(' ')
|
||||
.filter { it.isNotEmpty() }
|
||||
if (words.isEmpty()) return BioLines(listOf(AnnotatedString("")), more = false)
|
||||
val available = (fullWidth - reserve).coerceAtLeast(1)
|
||||
val lines = mutableListOf<AnnotatedString>()
|
||||
var index = 0
|
||||
while (index < words.size && lines.size < maxLines - 1) {
|
||||
var current = ""
|
||||
while (index < words.size) {
|
||||
val candidate = if (current.isEmpty()) words[index] else "$current ${words[index]}"
|
||||
val available = bioLineWidth(lines.size, fullWidth, reserve, reserveLines)
|
||||
if (measurer.measure(AnnotatedString(candidate), style, maxLines = 1).size.width <= available) {
|
||||
current = candidate
|
||||
index++
|
||||
@@ -776,8 +783,9 @@ internal fun splitBioCollapsed(
|
||||
return BioLines(lines, more = false)
|
||||
}
|
||||
|
||||
val remainingAvailable = bioLineWidth(lines.size, fullWidth, reserve, reserveLines)
|
||||
val remaining = words.drop(index).joinToString(" ")
|
||||
if (measurer.measure(AnnotatedString(remaining), style, maxLines = 1).size.width <= available) {
|
||||
if (measurer.measure(AnnotatedString(remaining), style, maxLines = 1).size.width <= remainingAvailable) {
|
||||
lines += AnnotatedString(remaining)
|
||||
return BioLines(lines, more = false)
|
||||
}
|
||||
@@ -790,7 +798,7 @@ internal fun splitBioCollapsed(
|
||||
val moreWidth =
|
||||
measurer.measure(more, style, maxLines = 1).size.width +
|
||||
measurer.measure(AnnotatedString(" "), style, maxLines = 1).size.width
|
||||
val lastAvailable = (available - moreWidth).coerceAtLeast(1)
|
||||
val lastAvailable = (bioLineWidth(lines.size, fullWidth, reserve, reserveLines) - moreWidth).coerceAtLeast(1)
|
||||
var truncated = ""
|
||||
while (index < words.size) {
|
||||
val candidate = if (truncated.isEmpty()) words[index] else "$truncated ${words[index]}"
|
||||
|
||||
@@ -238,7 +238,7 @@ class ArtistDetailScreenTest {
|
||||
var result: BioLines? = null
|
||||
composeRule.setContent {
|
||||
val measurer = rememberTextMeasurer()
|
||||
result = splitBioCollapsed(measurer, bio, TextStyle(fontSize = 14.sp), 400, 0, "More")
|
||||
result = splitBioCollapsed(measurer, bio, TextStyle(fontSize = 14.sp), 400, 0, 0, "More")
|
||||
}
|
||||
composeRule.waitForIdle()
|
||||
|
||||
@@ -269,6 +269,7 @@ class ArtistDetailScreenTest {
|
||||
TextStyle(fontSize = 14.sp),
|
||||
400,
|
||||
0,
|
||||
0,
|
||||
"More",
|
||||
maxLines = 3,
|
||||
)
|
||||
@@ -298,6 +299,7 @@ class ArtistDetailScreenTest {
|
||||
TextStyle(fontSize = 14.sp),
|
||||
20,
|
||||
0,
|
||||
0,
|
||||
"More",
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user