From 77190a2af1b1e2be1bd37451d8154db697d2c103 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Mon, 19 Oct 2026 13:55:16 +0000 Subject: [PATCH] ui: drop bio justification and normalize cached biographies Read normalization heals biographies cached before line breaks were preserved, without waiting for a refresh. --- .../subtracks/data/repo/LibraryRepository.kt | 7 +++- .../ui/library/ArtistDetailScreen.kt | 39 +------------------ .../data/repo/LibraryRepositoryTest.kt | 10 +++++ .../ui/library/ArtistDetailScreenTest.kt | 22 ----------- 4 files changed, 17 insertions(+), 61 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt index 63058fa1..6ba3d6b1 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -360,7 +361,11 @@ class LibraryRepository( fun artistBio( sourceId: Long, artistId: String, - ): Flow = db.libraryDao().artistBio(sourceId, artistId) + ): Flow = + db + .libraryDao() + .artistBio(sourceId, artistId) + .map { bio -> bio?.copy(biography = normalizeBiography(bio.biography)) } suspend fun refreshArtistBio( sourceId: Long, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt index ace37164..2906082f 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt @@ -605,16 +605,8 @@ private fun ArtistBio( Column(Modifier.fillMaxWidth()) { bioLines.lines.forEachIndexed { index, line -> 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 = displayLine, + text = line, style = style, color = color, maxLines = 1, @@ -737,35 +729,6 @@ private fun appendBioControl( 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( val lines: List, val more: Boolean, diff --git a/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt index e825e02a..9312c99c 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt @@ -338,6 +338,16 @@ class LibraryRepositoryTest { assertNull(normalizeBiography(" ")) } + @Test + fun aPreviouslyStoredBioIsNormalizedWhenRead() = + runBlocking { + withSource { + val sourceId = sourceRepository.activeSourceIdOnce()!! + db.libraryDao().upsertArtistBio(ArtistBio(sourceId, "ar1", "One Two")) + assertEquals("One\n\nTwo", repository.artistBio(sourceId, "ar1").first()?.biography) + } + } + @Test fun aFailedRefreshKeepsTheStoredBio() = runBlocking { diff --git a/app/src/test/kotlin/com/subtracks/ui/library/ArtistDetailScreenTest.kt b/app/src/test/kotlin/com/subtracks/ui/library/ArtistDetailScreenTest.kt index a7e2cb2e..de951710 100644 --- a/app/src/test/kotlin/com/subtracks/ui/library/ArtistDetailScreenTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/library/ArtistDetailScreenTest.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.performScrollToNode -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.sp @@ -330,27 +329,6 @@ class ArtistDetailScreenTest { 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( id: String, name: String,