4 Commits
Author SHA1 Message Date
OneWay 586852073e Release 1.6.0
- The app starts faster
- Smooth screen transition animations
- Articles render DSL line breaks, indentation and named colors correctly
- Tabs and extra spaces inside articles no longer break the layout
- The article title can be copied, tapping the article clears the selection
- Source URLs accept host:port addresses
- Dictionary downloads retry automatically when the network switches
- Download progress notifications are more reliable
- Search results clear instantly when the query is erased
- Leftover files from cancelled downloads are now removed automatically
- Improved app stability
- Faster dictionary indexing
- Updated build tooling and documentation
- Long link-heavy articles no longer freeze while scrolling
- Large search result lists render faster
- Dark article colors are readable in the dark theme
2026-09-23 22:21:15 +08:00
OneWay 10a7ff06c1 fix(ui): clear article title selection on any tap
The title SelectionContainer covered only the text, so a tap on the empty bar area or the article body never cleared the highlight. A tap anywhere on the article screen now recreates the container and drops the selection.
2026-09-23 22:10:16 +08:00
OneWay ec28d63656 fix(ui): remap dark article colors in dark theme
Colors set by dictionaries such as navy, dark red and brown are nearly invisible on the dark background. Swap the dark named colors for lighter tones when rendering in the dark theme; the light theme keeps the original colors.
2026-09-23 18:27:00 +08:00
OneWay c9b4a958bf fix(ui): merge article blocks to fix scroll jank
Link-heavy articles split every line into its own list item, so a
fast fling composed a dozen items in one frame and froze. Merge
small blocks into multi-paragraph items: indent via em space and
paragraph spacing via baseline shift.
2026-09-23 09:30:00 +08:00
2 changed files with 53 additions and 25 deletions

No files matched your search

@@ -14,6 +14,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -21,9 +22,12 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.IntSize
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.example.research.R
@@ -56,8 +60,17 @@ fun ArticleRoute(
private const val MERGED_BLOCK_MAX_CHARS = 1500
private const val MERGED_BLOCK_MAX_ITEMS = 12
private fun mergeDslBlocks(blocks: List<DslBlock>): List<DslBlock> {
private fun mergeDslBlocks(
blocks: List<DslBlock>,
density: Density,
baseStyle: TextStyle,
): List<DslBlock> {
if (blocks.size <= MERGED_BLOCK_MAX_ITEMS) return blocks
val gapShift = with(density) {
val fontPx =
if (baseStyle.fontSize.isUnspecified) 0f else baseStyle.fontSize.toPx()
if (fontPx > 0f) 8.dp.toPx() / (0.534f * fontPx) else 0.94f
}
val merged = ArrayList<DslBlock>(blocks.size / 4)
var i = 0
while (i < blocks.size) {
@@ -80,6 +93,15 @@ private fun mergeDslBlocks(blocks: List<DslBlock>): List<DslBlock> {
val more = j + 1 < blocks.size &&
(j + 1 - i) < MERGED_BLOCK_MAX_ITEMS &&
builder.length < MERGED_BLOCK_MAX_CHARS
if (more && !blocks[j + 1].continuesPrevious) {
val shiftPos = builder.length
builder.append('​')
builder.addStyle(
SpanStyle(baselineShift = BaselineShift(-gapShift)),
shiftPos,
shiftPos + 1
)
}
if (more) {
builder.append('\n')
}
@@ -116,11 +138,7 @@ fun ArticleContent(
is ArticleState.Success -> {
val articleSaveKey = "${articleState.dictionaryPath}:${articleState.title}"
val baseTextStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSurface,
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Proportional,
trim = LineHeightStyle.Trim.Both,
),
color = MaterialTheme.colorScheme.onSurface
)
val indentStep = 16.dp
@@ -180,14 +198,16 @@ fun ArticleContent(
val isHeavyArticle =
articleState.dslContent.size > HEAVY_ARTICLE_BLOCK_COUNT
val density = LocalDensity.current
val darkTheme = MaterialTheme.colorScheme.background.luminance() < 0.5f
val displayBlocks = remember(
articleState.dslContent,
density,
baseTextStyle,
darkTheme
) {
val merged =
mergeDslBlocks(articleState.dslContent)
mergeDslBlocks(articleState.dslContent, density, baseTextStyle)
if (darkTheme) {
merged.map {
it.copy(text = DslAnnotatedParser.remapForDarkTheme(it.text))
@@ -213,24 +233,32 @@ fun ArticleContent(
},
contentType = { _, _ -> "article_block" }
) { index, block ->
Box(
modifier = Modifier
.padding(start = Spacing.contentStart + indentStep * block.indent)
) {
ArticleLinkText(
block = block,
linksEnabled = true,
preferDirectHitTesting = isHeavyArticle,
style = baseTextStyle,
onRefClick = refClickHandler,
onTextLayout = {},
val followedByContinuation =
displayBlocks.getOrNull(index + 1)?.continuesPrevious == true
val textContent = @Composable {
Box(
modifier = Modifier
.testTag("article_block_$index")
.semantics {
testTagsAsResourceId = true
}
)
.padding(
start = Spacing.contentStart + indentStep * block.indent
)
.padding(bottom = if (followedByContinuation) 0.dp else 8.dp)
) {
ArticleLinkText(
block = block,
linksEnabled = true,
preferDirectHitTesting = isHeavyArticle,
style = baseTextStyle,
onRefClick = refClickHandler,
onTextLayout = {},
modifier = Modifier
.testTag("article_block_$index")
.semantics {
testTagsAsResourceId = true
}
)
}
}
textContent()
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ aboutlibraries = "15.2.0"
activity_compose = "1.13.0"
agp = "9.4.1"
compose_bom = "2026.09.00"
core = "1.19.1"
core = "1.19.0"
datastore_preferences = "1.2.1"
documentfile = "1.1.0"
kotlin = "2.4.20"