mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +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 SHUFFLE_HANG = SHUFFLE_HEIGHT / 2
|
||||||
private val BIO_ACTION_RESERVE = 104.dp
|
private val BIO_ACTION_RESERVE = 104.dp
|
||||||
private val BIO_ACTION_HEIGHT = 48.dp
|
private val BIO_ACTION_HEIGHT = 48.dp
|
||||||
|
private val BIO_ACTION_OFFSET = 8.dp
|
||||||
private val BIO_TOP_GAP = 8.dp
|
private val BIO_TOP_GAP = 8.dp
|
||||||
|
|
||||||
internal fun estimateScrollPx(
|
internal fun estimateScrollPx(
|
||||||
@@ -363,7 +364,9 @@ fun ArtistDetailScreen(
|
|||||||
BoxWithConstraints(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
BoxWithConstraints(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
||||||
val screenHeightPx = with(density) { maxHeight.toPx() }
|
val screenHeightPx = with(density) { maxHeight.toPx() }
|
||||||
val barHeightPx = with(density) { barHeight.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)
|
val barColor = heroBarColor(effectiveArtwork, gradientScrollPx, barHeightPx, screenHeightPx)
|
||||||
HeroGradient(
|
HeroGradient(
|
||||||
colors = effectiveArtwork,
|
colors = effectiveArtwork,
|
||||||
@@ -441,7 +444,7 @@ fun ArtistDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(
|
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(
|
||||||
text = metaText,
|
text = metaText,
|
||||||
@@ -579,12 +582,15 @@ private fun ArtistBio(
|
|||||||
remember(style) {
|
remember(style) {
|
||||||
measurer.measure(AnnotatedString("X"), style, maxLines = 1).size.height
|
measurer.measure(AnnotatedString("X"), style, maxLines = 1).size.height
|
||||||
}.coerceAtLeast(1)
|
}.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 =
|
val bioLines =
|
||||||
remember(text, fullWidth, reserve, reserveLines, collapsed, expanded, moreText, lessText) {
|
remember(text, fullWidth, reserve, reserveLines, collapsed, expanded, moreText, lessText) {
|
||||||
when {
|
when {
|
||||||
collapsed -> {
|
collapsed -> {
|
||||||
splitBioCollapsed(measurer, text, style, fullWidth, reserve, moreText, maxLines = 3)
|
splitBioCollapsed(measurer, text, style, fullWidth, reserve, reserveLines, moreText, maxLines = 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
expanded -> {
|
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)
|
ArtistActions(artist, starred, onToggleStar, onMore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -742,6 +748,7 @@ internal fun splitBioCollapsed(
|
|||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
fullWidth: Int,
|
fullWidth: Int,
|
||||||
reserve: Int,
|
reserve: Int,
|
||||||
|
reserveLines: Int,
|
||||||
moreText: String,
|
moreText: String,
|
||||||
maxLines: Int = 2,
|
maxLines: Int = 2,
|
||||||
): BioLines {
|
): BioLines {
|
||||||
@@ -752,13 +759,13 @@ internal fun splitBioCollapsed(
|
|||||||
.split(' ')
|
.split(' ')
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
if (words.isEmpty()) return BioLines(listOf(AnnotatedString("")), more = false)
|
if (words.isEmpty()) return BioLines(listOf(AnnotatedString("")), more = false)
|
||||||
val available = (fullWidth - reserve).coerceAtLeast(1)
|
|
||||||
val lines = mutableListOf<AnnotatedString>()
|
val lines = mutableListOf<AnnotatedString>()
|
||||||
var index = 0
|
var index = 0
|
||||||
while (index < words.size && lines.size < maxLines - 1) {
|
while (index < words.size && lines.size < maxLines - 1) {
|
||||||
var current = ""
|
var current = ""
|
||||||
while (index < words.size) {
|
while (index < words.size) {
|
||||||
val candidate = if (current.isEmpty()) words[index] else "$current ${words[index]}"
|
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) {
|
if (measurer.measure(AnnotatedString(candidate), style, maxLines = 1).size.width <= available) {
|
||||||
current = candidate
|
current = candidate
|
||||||
index++
|
index++
|
||||||
@@ -776,8 +783,9 @@ internal fun splitBioCollapsed(
|
|||||||
return BioLines(lines, more = false)
|
return BioLines(lines, more = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val remainingAvailable = bioLineWidth(lines.size, fullWidth, reserve, reserveLines)
|
||||||
val remaining = words.drop(index).joinToString(" ")
|
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)
|
lines += AnnotatedString(remaining)
|
||||||
return BioLines(lines, more = false)
|
return BioLines(lines, more = false)
|
||||||
}
|
}
|
||||||
@@ -790,7 +798,7 @@ internal fun splitBioCollapsed(
|
|||||||
val moreWidth =
|
val moreWidth =
|
||||||
measurer.measure(more, style, maxLines = 1).size.width +
|
measurer.measure(more, style, maxLines = 1).size.width +
|
||||||
measurer.measure(AnnotatedString(" "), 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 = ""
|
var truncated = ""
|
||||||
while (index < words.size) {
|
while (index < words.size) {
|
||||||
val candidate = if (truncated.isEmpty()) words[index] else "$truncated ${words[index]}"
|
val candidate = if (truncated.isEmpty()) words[index] else "$truncated ${words[index]}"
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ class ArtistDetailScreenTest {
|
|||||||
var result: BioLines? = null
|
var result: BioLines? = null
|
||||||
composeRule.setContent {
|
composeRule.setContent {
|
||||||
val measurer = rememberTextMeasurer()
|
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()
|
composeRule.waitForIdle()
|
||||||
|
|
||||||
@@ -269,6 +269,7 @@ class ArtistDetailScreenTest {
|
|||||||
TextStyle(fontSize = 14.sp),
|
TextStyle(fontSize = 14.sp),
|
||||||
400,
|
400,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
"More",
|
"More",
|
||||||
maxLines = 3,
|
maxLines = 3,
|
||||||
)
|
)
|
||||||
@@ -298,6 +299,7 @@ class ArtistDetailScreenTest {
|
|||||||
TextStyle(fontSize = 14.sp),
|
TextStyle(fontSize = 14.sp),
|
||||||
20,
|
20,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
"More",
|
"More",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user