mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
ui: add a Less control and preserve bio paragraphs
Keep source line breaks, treat the triple-space paragraph separator some servers return as a blank line, justify wrapped bio lines, and append a bold Less control to the expanded bio.
This commit is contained in:
@@ -556,6 +556,12 @@ const val REDISCOVER_WINDOW_MS = 60L * 24 * 60 * 60 * 1000
|
|||||||
|
|
||||||
const val SEARCH_RESULT_LIMIT = 50
|
const val SEARCH_RESULT_LIMIT = 50
|
||||||
|
|
||||||
internal fun normalizeBiography(value: String?): String? = value?.trim()?.takeIf { it.isNotEmpty() }
|
internal fun normalizeBiography(value: String?): String? =
|
||||||
|
value
|
||||||
|
?.replace("\r\n", "\n")
|
||||||
|
?.replace('\r', '\n')
|
||||||
|
?.replace(Regex(" {3,}"), "\n\n")
|
||||||
|
?.trim()
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
fun rediscoverCutoff(): Long = System.currentTimeMillis() - REDISCOVER_WINDOW_MS
|
fun rediscoverCutoff(): Long = System.currentTimeMillis() - REDISCOVER_WINDOW_MS
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ private const val THEME_TRANSITION_MS = 100
|
|||||||
private const val SCRIM_FADE_MS = 180
|
private const val SCRIM_FADE_MS = 180
|
||||||
internal const val ARTIST_NAME_SCRIM_TAG = "artistNameScrim"
|
internal const val ARTIST_NAME_SCRIM_TAG = "artistNameScrim"
|
||||||
internal const val ARTIST_BIO_MORE_TAG = "artistBioMore"
|
internal const val ARTIST_BIO_MORE_TAG = "artistBioMore"
|
||||||
|
internal const val ARTIST_BIO_LESS_TAG = "artistBioLess"
|
||||||
|
|
||||||
private data class BioSnapshot(
|
private data class BioSnapshot(
|
||||||
val known: Boolean,
|
val known: Boolean,
|
||||||
@@ -240,6 +241,7 @@ fun ArtistDetailRoute(
|
|||||||
starred = artist?.starred != null,
|
starred = artist?.starred != null,
|
||||||
onToggleStar = artist?.let { a -> { setStar(StarType.Artist, a.id, a.starred == null) } },
|
onToggleStar = artist?.let { a -> { setStar(StarType.Artist, a.id, a.starred == null) } },
|
||||||
onExpandBio = viewModel::expandBio,
|
onExpandBio = viewModel::expandBio,
|
||||||
|
onCollapseBio = viewModel::collapseBio,
|
||||||
onShuffle = viewModel::shuffle,
|
onShuffle = viewModel::shuffle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -278,6 +280,7 @@ fun ArtistDetailScreen(
|
|||||||
starred: Boolean = false,
|
starred: Boolean = false,
|
||||||
onToggleStar: (() -> Unit)? = null,
|
onToggleStar: (() -> Unit)? = null,
|
||||||
onExpandBio: () -> Unit = {},
|
onExpandBio: () -> Unit = {},
|
||||||
|
onCollapseBio: () -> Unit = {},
|
||||||
onShuffle: () -> Unit = {},
|
onShuffle: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyGridState()
|
val listState = rememberLazyGridState()
|
||||||
@@ -455,11 +458,13 @@ fun ArtistDetailScreen(
|
|||||||
MaterialTheme.colorScheme.outline
|
MaterialTheme.colorScheme.outline
|
||||||
},
|
},
|
||||||
collapsed = bioCollapsed,
|
collapsed = bioCollapsed,
|
||||||
|
expanded = hasBio && bioExpanded,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
starred = starred,
|
starred = starred,
|
||||||
onToggleStar = onToggleStar,
|
onToggleStar = onToggleStar,
|
||||||
onMore = onMore,
|
onMore = onMore,
|
||||||
onExpand = onExpandBio,
|
onExpand = onExpandBio,
|
||||||
|
onCollapse = onCollapseBio,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.padding(
|
Modifier.padding(
|
||||||
bottom = if (hasBio && !bioCollapsed) 8.dp else 0.dp,
|
bottom = if (hasBio && !bioCollapsed) 8.dp else 0.dp,
|
||||||
@@ -553,17 +558,20 @@ private fun ArtistBio(
|
|||||||
text: String,
|
text: String,
|
||||||
color: Color,
|
color: Color,
|
||||||
collapsed: Boolean,
|
collapsed: Boolean,
|
||||||
|
expanded: Boolean,
|
||||||
artist: Artist?,
|
artist: Artist?,
|
||||||
starred: Boolean,
|
starred: Boolean,
|
||||||
onToggleStar: (() -> Unit)?,
|
onToggleStar: (() -> Unit)?,
|
||||||
onMore: () -> Unit,
|
onMore: () -> Unit,
|
||||||
onExpand: () -> Unit,
|
onExpand: () -> Unit,
|
||||||
|
onCollapse: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val measurer = rememberTextMeasurer()
|
val measurer = rememberTextMeasurer()
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val style = MaterialTheme.typography.bodyMedium
|
val style = MaterialTheme.typography.bodyMedium
|
||||||
val moreText = stringResource(R.string.artist_bio_more)
|
val moreText = stringResource(R.string.artist_bio_more)
|
||||||
|
val lessText = stringResource(R.string.artist_bio_less)
|
||||||
BoxWithConstraints(modifier.fillMaxWidth().padding(top = BIO_TOP_GAP)) {
|
BoxWithConstraints(modifier.fillMaxWidth().padding(top = BIO_TOP_GAP)) {
|
||||||
val fullWidth = with(density) { maxWidth.roundToPx() }
|
val fullWidth = with(density) { maxWidth.roundToPx() }
|
||||||
val reserve = with(density) { BIO_ACTION_RESERVE.roundToPx() }
|
val reserve = with(density) { BIO_ACTION_RESERVE.roundToPx() }
|
||||||
@@ -573,10 +581,17 @@ private fun ArtistBio(
|
|||||||
}.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.toPx() } / lineHeightPx).toInt().coerceAtLeast(1)
|
||||||
val bioLines =
|
val bioLines =
|
||||||
remember(text, fullWidth, reserve, reserveLines, collapsed, moreText) {
|
remember(text, fullWidth, reserve, reserveLines, collapsed, expanded, moreText, lessText) {
|
||||||
if (collapsed) {
|
when {
|
||||||
|
collapsed -> {
|
||||||
splitBioCollapsed(measurer, text, style, fullWidth, reserve, moreText)
|
splitBioCollapsed(measurer, text, style, fullWidth, reserve, moreText)
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
expanded -> {
|
||||||
|
splitBioExpanded(measurer, text, style, fullWidth, reserve, reserveLines, lessText)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
BioLines(
|
BioLines(
|
||||||
splitBio(measurer, text, style, fullWidth, reserve, reserveLines).map {
|
splitBio(measurer, text, style, fullWidth, reserve, reserveLines).map {
|
||||||
AnnotatedString(it.ifEmpty { " " })
|
AnnotatedString(it.ifEmpty { " " })
|
||||||
@@ -585,21 +600,30 @@ private fun ArtistBio(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Box {
|
Box {
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
bioLines.lines.forEachIndexed { index, line ->
|
bioLines.lines.forEachIndexed { index, line ->
|
||||||
val more = bioLines.more && index == bioLines.lines.lastIndex
|
val isControl = index == bioLines.lines.lastIndex && (bioLines.more || bioLines.less)
|
||||||
|
val nextBlank = index == bioLines.lines.lastIndex || bioLines.lines[index + 1].text.isBlank()
|
||||||
|
val available = bioLineWidth(index, fullWidth, reserve, reserveLines)
|
||||||
|
val displayLine =
|
||||||
|
if (!nextBlank && line.text.isNotBlank() && line.spanStyles.isEmpty()) {
|
||||||
|
justifyBioLine(line, measurer, style, available)
|
||||||
|
} else {
|
||||||
|
line
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = line,
|
text = displayLine,
|
||||||
style = style,
|
style = style,
|
||||||
color = color,
|
color = color,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier =
|
modifier =
|
||||||
if (more) {
|
when {
|
||||||
Modifier.testTag(ARTIST_BIO_MORE_TAG).clickable(onClick = onExpand)
|
isControl && bioLines.more -> Modifier.testTag(ARTIST_BIO_MORE_TAG).clickable(onClick = onExpand)
|
||||||
} else {
|
isControl && bioLines.less -> Modifier.testTag(ARTIST_BIO_LESS_TAG).clickable(onClick = onCollapse)
|
||||||
Modifier
|
else -> Modifier
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -611,7 +635,14 @@ private fun ArtistBio(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Splits text into lines, narrowing the lines that would overlap the actions. */
|
private fun bioLineWidth(
|
||||||
|
index: Int,
|
||||||
|
fullWidth: Int,
|
||||||
|
reserve: Int,
|
||||||
|
reserveLines: Int,
|
||||||
|
): Int = if (index < reserveLines) fullWidth - reserve else fullWidth
|
||||||
|
|
||||||
|
/** Splits text at every source line break, then wraps it to the width left by the actions. */
|
||||||
internal fun splitBio(
|
internal fun splitBio(
|
||||||
measurer: TextMeasurer,
|
measurer: TextMeasurer,
|
||||||
text: String,
|
text: String,
|
||||||
@@ -620,35 +651,125 @@ internal fun splitBio(
|
|||||||
reserve: Int,
|
reserve: Int,
|
||||||
reserveLines: Int,
|
reserveLines: Int,
|
||||||
): List<String> {
|
): List<String> {
|
||||||
if (text.isBlank()) return listOf("")
|
|
||||||
val lines = mutableListOf<String>()
|
val lines = mutableListOf<String>()
|
||||||
text.split("\n\n").forEachIndexed { paragraphIndex, paragraph ->
|
text.split('\n').forEach { sourceLine ->
|
||||||
if (paragraphIndex > 0) lines += ""
|
val words = sourceLine.trim().split(' ').filter { it.isNotEmpty() }
|
||||||
val words = paragraph.replace('\n', ' ').split(' ').filter { it.isNotEmpty() }
|
if (words.isEmpty()) {
|
||||||
|
lines += ""
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
var current = ""
|
var current = ""
|
||||||
var index = 0
|
for (word in words) {
|
||||||
while (index < words.size) {
|
val candidate = if (current.isEmpty()) word else "$current $word"
|
||||||
val candidate = if (current.isEmpty()) words[index] else "$current ${words[index]}"
|
val available = bioLineWidth(lines.size, fullWidth, reserve, reserveLines)
|
||||||
val available = if (lines.size < reserveLines) fullWidth - reserve else fullWidth
|
|
||||||
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++
|
|
||||||
} else if (current.isEmpty()) {
|
} else if (current.isEmpty()) {
|
||||||
lines += candidate
|
lines += candidate
|
||||||
index++
|
|
||||||
} else {
|
} else {
|
||||||
lines += current
|
lines += current
|
||||||
current = ""
|
current = word
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (current.isNotEmpty()) lines += current
|
if (current.isNotEmpty()) lines += current
|
||||||
}
|
}
|
||||||
return lines
|
while (lines.isNotEmpty() && lines.last().isBlank()) lines.removeAt(lines.lastIndex)
|
||||||
|
return lines.ifEmpty { listOf("") }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun splitBioExpanded(
|
||||||
|
measurer: TextMeasurer,
|
||||||
|
text: String,
|
||||||
|
style: TextStyle,
|
||||||
|
fullWidth: Int,
|
||||||
|
reserve: Int,
|
||||||
|
reserveLines: Int,
|
||||||
|
lessText: String,
|
||||||
|
): BioLines {
|
||||||
|
val lines = splitBio(measurer, text, style, fullWidth, reserve, reserveLines).mapTo(mutableListOf()) { AnnotatedString(it) }
|
||||||
|
appendBioControl(lines, lessText, measurer, style, fullWidth, reserve, reserveLines)
|
||||||
|
return BioLines(lines, more = false, less = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendBioControl(
|
||||||
|
lines: MutableList<AnnotatedString>,
|
||||||
|
label: String,
|
||||||
|
measurer: TextMeasurer,
|
||||||
|
style: TextStyle,
|
||||||
|
fullWidth: Int,
|
||||||
|
reserve: Int,
|
||||||
|
reserveLines: Int,
|
||||||
|
) {
|
||||||
|
while (lines.isNotEmpty() && lines.last().text.isBlank()) lines.removeAt(lines.lastIndex)
|
||||||
|
val control =
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(" ")
|
||||||
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append(label) }
|
||||||
|
}
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
lines += control
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val lastIndex = lines.lastIndex
|
||||||
|
val available = bioLineWidth(lastIndex, fullWidth, reserve, reserveLines)
|
||||||
|
val combined =
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(lines[lastIndex])
|
||||||
|
append(control)
|
||||||
|
}
|
||||||
|
if (measurer.measure(combined, style, maxLines = 1).size.width <= available) {
|
||||||
|
lines[lastIndex] = combined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val words = lines[lastIndex].text.split(' ').filter { it.isNotEmpty() }
|
||||||
|
if (words.size > 1) {
|
||||||
|
val tailLine =
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(words.last())
|
||||||
|
append(control)
|
||||||
|
}
|
||||||
|
if (measurer.measure(tailLine, style, maxLines = 1).size.width <= available) {
|
||||||
|
lines[lastIndex] = AnnotatedString(words.dropLast(1).joinToString(" "))
|
||||||
|
lines += tailLine
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines += control
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun justifyBioLine(
|
||||||
|
line: AnnotatedString,
|
||||||
|
measurer: TextMeasurer,
|
||||||
|
style: TextStyle,
|
||||||
|
available: Int,
|
||||||
|
): AnnotatedString {
|
||||||
|
if (line.spanStyles.isNotEmpty()) return line
|
||||||
|
val parts = line.text.split(' ')
|
||||||
|
if (parts.size < 2) return line
|
||||||
|
val spaceWidth = measurer.measure(AnnotatedString(" "), style, maxLines = 1).size.width
|
||||||
|
if (spaceWidth <= 0) return line
|
||||||
|
val extra = available - measurer.measure(line, style, maxLines = 1).size.width
|
||||||
|
val spaces = extra / spaceWidth
|
||||||
|
if (spaces <= 0) return line
|
||||||
|
val gaps = parts.size - 1
|
||||||
|
val perGap = spaces / gaps
|
||||||
|
val remainder = spaces % gaps
|
||||||
|
return AnnotatedString(
|
||||||
|
buildString {
|
||||||
|
parts.forEachIndexed { index, part ->
|
||||||
|
append(part)
|
||||||
|
if (index < gaps) {
|
||||||
|
repeat(1 + perGap + if (index < remainder) 1 else 0) { append(' ') }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class BioLines(
|
internal data class BioLines(
|
||||||
val lines: List<AnnotatedString>,
|
val lines: List<AnnotatedString>,
|
||||||
val more: Boolean,
|
val more: Boolean,
|
||||||
|
val less: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Collapses text to two lines, appending a bold More only when it does not fit. */
|
/** Collapses text to two lines, appending a bold More only when it does not fit. */
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ class ArtistDetailViewModel(
|
|||||||
_bioExpanded.value = true
|
_bioExpanded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collapseBio() {
|
||||||
|
_bioExpanded.value = false
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun downloadedBytes(
|
suspend fun downloadedBytes(
|
||||||
list: DownloadList,
|
list: DownloadList,
|
||||||
refId: String,
|
refId: String,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<string name="artist_bio_loading">Loading artist bio…</string>
|
<string name="artist_bio_loading">Loading artist bio…</string>
|
||||||
<string name="artist_bio_unavailable">Artist bio unavailable.</string>
|
<string name="artist_bio_unavailable">Artist bio unavailable.</string>
|
||||||
<string name="artist_bio_more">More</string>
|
<string name="artist_bio_more">More</string>
|
||||||
|
<string name="artist_bio_less">Less</string>
|
||||||
<string name="artist_meta">%1$s · %2$s</string>
|
<string name="artist_meta">%1$s · %2$s</string>
|
||||||
<string name="resources_filter_genre">Genre</string>
|
<string name="resources_filter_genre">Genre</string>
|
||||||
<string name="resources_filter_starred">Starred</string>
|
<string name="resources_filter_starred">Starred</string>
|
||||||
|
|||||||
@@ -329,6 +329,15 @@ class LibraryRepositoryTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun biographiesKeepLineAndParagraphBreaks() {
|
||||||
|
assertEquals("Line one\nLine two", normalizeBiography("Line one\r\nLine two"))
|
||||||
|
assertEquals("Line one\nLine two", normalizeBiography("Line one\nLine two"))
|
||||||
|
assertEquals("One\n\nTwo", normalizeBiography("One Two"))
|
||||||
|
assertEquals("One\n\nTwo", normalizeBiography("One\n\nTwo"))
|
||||||
|
assertNull(normalizeBiography(" "))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun aFailedRefreshKeepsTheStoredBio() =
|
fun aFailedRefreshKeepsTheStoredBio() =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.compose.ui.test.onNodeWithText
|
|||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performScrollTo
|
import androidx.compose.ui.test.performScrollTo
|
||||||
import androidx.compose.ui.test.performScrollToNode
|
import androidx.compose.ui.test.performScrollToNode
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -25,6 +26,7 @@ import com.subtracks.data.model.Album
|
|||||||
import com.subtracks.data.model.Artist
|
import com.subtracks.data.model.Artist
|
||||||
import com.subtracks.data.model.ArtistAlbums
|
import com.subtracks.data.model.ArtistAlbums
|
||||||
import com.subtracks.ui.theme.SubtracksTheme
|
import com.subtracks.ui.theme.SubtracksTheme
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -65,6 +67,7 @@ class ArtistDetailScreenTest {
|
|||||||
onBack = {},
|
onBack = {},
|
||||||
onAlbumClick = {},
|
onAlbumClick = {},
|
||||||
onExpandBio = { expanded = true },
|
onExpandBio = { expanded = true },
|
||||||
|
onCollapseBio = { expanded = false },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,6 +222,12 @@ class ArtistDetailScreenTest {
|
|||||||
composeRule.onNodeWithTag(ARTIST_BIO_MORE_TAG).performClick()
|
composeRule.onNodeWithTag(ARTIST_BIO_MORE_TAG).performClick()
|
||||||
|
|
||||||
composeRule.onNodeWithTag(ARTIST_BIO_MORE_TAG).assertDoesNotExist()
|
composeRule.onNodeWithTag(ARTIST_BIO_MORE_TAG).assertDoesNotExist()
|
||||||
|
composeRule.onNodeWithTag(ARTIST_BIO_LESS_TAG).performScrollTo().assertExists()
|
||||||
|
|
||||||
|
composeRule.onNodeWithTag(ARTIST_BIO_LESS_TAG).performClick()
|
||||||
|
|
||||||
|
composeRule.onNodeWithTag(ARTIST_BIO_LESS_TAG).assertDoesNotExist()
|
||||||
|
composeRule.onNodeWithTag(ARTIST_BIO_MORE_TAG).performScrollTo().assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -275,6 +284,43 @@ class ArtistDetailScreenTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun sourceLineAndParagraphBreaksArePreserved() {
|
||||||
|
var lineBreak: List<String>? = null
|
||||||
|
var paragraphBreak: List<String>? = null
|
||||||
|
composeRule.setContent {
|
||||||
|
val measurer = rememberTextMeasurer()
|
||||||
|
val style = TextStyle(fontSize = 14.sp)
|
||||||
|
lineBreak = splitBio(measurer, "First line.\nSecond line.", style, 400, 0, 2)
|
||||||
|
paragraphBreak = splitBio(measurer, "First paragraph.\n\nSecond paragraph.", style, 400, 0, 2)
|
||||||
|
}
|
||||||
|
composeRule.waitForIdle()
|
||||||
|
|
||||||
|
assertEquals(listOf("First line.", "Second line."), lineBreak)
|
||||||
|
assertEquals(listOf("First paragraph.", "", "Second paragraph."), paragraphBreak)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun aJustifiedLineFillsTheAvailableWidth() {
|
||||||
|
val style = TextStyle(fontSize = 14.sp)
|
||||||
|
var plainWidth = 0
|
||||||
|
var justifiedWidth = 0
|
||||||
|
var justified = ""
|
||||||
|
composeRule.setContent {
|
||||||
|
val measurer = rememberTextMeasurer()
|
||||||
|
val line = AnnotatedString("one two three four five")
|
||||||
|
plainWidth = measurer.measure(line, style, maxLines = 1).size.width
|
||||||
|
val result = justifyBioLine(line, measurer, style, 400)
|
||||||
|
justifiedWidth = measurer.measure(result, style, maxLines = 1).size.width
|
||||||
|
justified = result.text
|
||||||
|
}
|
||||||
|
composeRule.waitForIdle()
|
||||||
|
|
||||||
|
assertTrue(justifiedWidth > plainWidth)
|
||||||
|
assertTrue(justifiedWidth <= 400)
|
||||||
|
assertTrue(justified.contains(" "))
|
||||||
|
}
|
||||||
|
|
||||||
private fun album(
|
private fun album(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user