Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.github.zly2006.zhihu

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.SystemClock
import android.util.Log
import androidx.compose.foundation.ComposeFoundationFlags
Expand All @@ -30,13 +31,16 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.platform.TextToolbar
Expand Down Expand Up @@ -99,6 +103,8 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
Expand Down Expand Up @@ -564,6 +570,64 @@ class ArticleScreenInstrumentedTest {
composeRule.onNodeWithText("“$FORMATTED_HIGHLIGHT”").assertIsDisplayed()
}

@Test
fun highlightedTextDrawsDashesAcrossEveryWrappedLine() {
composeRule.setScreenContent {
MaterialTheme(
colorScheme = lightColorScheme(
outlineVariant = Color.Magenta,
),
) {
RenderMarkdown(
html = WRAPPED_HIGHLIGHT_PARAGRAPH_HTML,
modifier = androidx.compose.ui.Modifier
.width(220.dp)
.testTag("wrapped-highlight-article"),
enableScroll = false,
)
}
}

val paragraph = composeRule.onNodeWithText(WRAPPED_HIGHLIGHT_PARAGRAPH)
val layouts = mutableListOf<TextLayoutResult>()
paragraph.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { getTextLayoutResult ->
assertTrue(getTextLayoutResult(layouts))
}
val layout = layouts.single()
val highlightStart = WRAPPED_HIGHLIGHT_PREFIX.length
val highlightEnd = highlightStart + WRAPPED_HIGHLIGHT.length
val startLine = layout.getLineForOffset(highlightStart)
val endLine = layout.getLineForOffset(highlightEnd - 1)
assertTrue("Fixture must wrap the highlighted text onto at least three lines", endLine - startLine >= 2)

val image = composeRule
.onNodeWithTag("wrapped-highlight-article")
.captureToImage()
val output = File(
requireNotNull(InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(null)),
"segment-highlight-wrapped.png",
)
FileOutputStream(output).use { stream ->
image.asAndroidBitmap().compress(Bitmap.CompressFormat.PNG, 100, stream)
}

val pixels = image.toPixelMap()
for (line in startLine..endLine) {
val top = (layout.getLineBottom(line) - 6f).toInt().coerceAtLeast(0)
val bottom = (layout.getLineBottom(line) + 2f).toInt().coerceAtMost(pixels.height - 1)
Comment on lines +616 to +617

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

test_file="$(fd -a '^ArticleScreenInstrumentedTest\.kt$' | head -n 1)"
sed -n '573,630p' "$test_file"

# 检查项目的受管设备或 CI 配置是否指定了高密度 AVD。
rg -n -i -C 2 'hw\.lcd\.density|densityDpi|managedDevices|avd|emulator' \
  -g '*.gradle' -g '*.gradle.kts' -g '*.properties' -g '*.yml' -g '*.yaml' .

Repository: Chloemlla/zhihu-plus-plus

Length of output: 2709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Candidate files:"
fd -a '.*Manifest.*|gradle.properties|app|androidTest|build\.gradle.*' . | sed -n '1,120p'

echo
echo "Manifest density/dpi/emulator refs:"
fd -a --glob '*Manifest*' . | xargs -r rg -n -i 'screen|density|dpi|emulator|managedDevices|hw\.lcd\.density' || true

echo
echo "Gradle/CI density/emulator/device refs:"
rg -n -i -C 2 'hw\.lcd\.density|densityDpi|managedDevices|avd|emulator|abiFilter|targetAbi|emulators|testOptions|virtual' \
  -g '*.gradle' -g '*.gradle.kts' -g '*.properties' -g '*.yml' -g '*.yaml' -g 'build*.xml' -g 'instrumentation*.xml' . || true

echo
echo "RenderMarkdown/virtualize/dashes production refs:"
rg -n -i 'dash|dashed|虚线|renderMarkdown|RenderMarkdown|Segment|highlight|outlineVariant|magenta|`#f`|SegmentHighlight|segment' app/src/main app -g '*.kt' -g '*.kts' | sed -n '1,240p'

Repository: Chloemlla/zhihu-plus-plus

Length of output: 26613


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find RenderMarkdown definitions/usages:"
fd -a 'RenderMarkdown.*\.kt|.*Markdown.*\.kt' app/src/main app/src -E '.gradle' | sort | sed -n '1,120p'

echo
rg -n -i 'class RenderMarkdown|object RenderMarkdown|fun RenderMarkdown|RenderMarkdown\(|dashed|Dash|DashPathEffect|offset|lineBottom|outlineVariant|highlight' \
  app/src/main app/src -g '*.kt' | sed -n '1,260p'

echo
echo "Relevant article test imports:"
sed -n '1,90p' app/src/androidTest/java/com/github/zly2006/zhihu/ArticleScreenInstrumentedTest.kt

Repository: Chloemlla/zhihu-plus-plus

Length of output: 27298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

find app -name '*.kt' -path '*/main/*' -exec rg -l 'RenderMarkdown|class RenderMarkdown|object RenderMarkdown|drawDashes|dashed|Dash' {} \;

echo
find app/src/main -name '*.kt' | xargs rg -n 'RenderMarkdown|class RenderMarkdown|object RenderMarkdown|drawDashes|dashed|Dash|DashPathEffect|outlineVariant|magenta|SegmentMatch' || true

echo
test_file="app/src/androidTest/java/com/github/zly2006/zhihu/ArticleScreenInstrumentedTest.kt"
sed -n '610,630p' "$test_file" | cat -n

Repository: Chloemlla/zhihu-plus-plus

Length of output: 1219


🌐 Web query:

Project Chloemlla zhihu-plus-plus RenderMarkdown outlineVariant 2.dp

💡 Result:

The query "Project Chloemlla zhihu-plus-plus RenderMarkdown outlineVariant 2.dp" appears to combine several disparate technical terms, projects, and identifiers that do not belong to a single unified software project or API. Based on current technical documentation and repository data: 1. Chloemlla: This is a GitHub user account (GitHub: Chloemlla) that maintains various open-source projects, including site-status [1], microsoft-tts [2], and mqlens-mongodb [3]. There is no "Project Chloemlla" that encompasses the other terms in your query. 2. zhihu-plus-plus: This is an open-source, third-party, ad-free Android client for the platform Zhihu, maintained by user zly2006 [4][5]. It supports features like offline AI inference (Full version) and custom navigation [4][5]. It is entirely unrelated to "Project Chloemlla" [1][4]. 3. RenderMarkdown / outlineVariant: These appear to be generic technical terms frequently found in Markdown rendering libraries and documentation [6][7][8]. For example, renderMarkdown is a common function name in many libraries (e.g., in VS Code's codebase [7] or various Markdown parsing plugins [6]), while outlineVariant is a common configuration parameter used in UI components to manage Table of Contents (TOC) or document outlines [6][8]. 4. 2.dp: This is likely a specific, non-standard unit of measurement or a version fragment. In Android development, "dp" (density-independent pixels) is a standard unit [4], but "2.dp" is not a recognized standalone library, API, or project identifier. In summary, these terms are unrelated fragments from different software ecosystems (Android, Markdown rendering, and individual GitHub repositories). There is no single project that integrates these items.

Citations:


🌐 Web query:

site-status repo Chloemlla RenderMarkdown 2.dp outlineVariant

💡 Result:

The repository "site-status" associated with the GitHub user "Chloemlla" is a status monitoring panel based on the UptimeRobot API [1]. The technical terms you mentioned—"RenderMarkdown," "2.dp," and "outlineVariant"—are common identifiers used in UI development with Jetpack Compose (Android) and Flutter, rather than specific configuration parameters for that repository [2][3][4][5]. 1. RenderMarkdown: This is a frequently used function name in Kotlin/Jetpack Compose projects (such as zhihu-plus-plus or custom Markdown rendering implementations) to handle the conversion of Markdown content into UI components [6][7]. 2. 2.dp: This is a standard unit of measure in Jetpack Compose, representing 2 density-independent pixels, often used for padding or spacing in UI layouts [2]. 3. outlineVariant: This is a standard color token in Material Design 3 (M3) systems for both Android (Compose Multiplatform) and Flutter, used to define the color of borders or decorative outlines that are secondary to the primary outline [4][5]. There is no direct functional link between these specific UI implementation details and the "Chloemlla/site-status" repository [1]. The repository is a web-based status panel rather than a specialized Markdown rendering engine or a mobile UI component library [1].

Citations:


🌐 Web query:

"Site_Status_DesktopApp" RenderMarkdown outlineVariant 2.dp

💡 Result:

The terms provided do not form a single recognized technical command or standard property. Instead, they appear to be a mix of distinct concepts from different software contexts: 1. Site_Status_DesktopApp: This refers to the Status Desktop client, a privacy-centric, decentralized messenger built by the Status team using Nim and Qt/QML [1][2]. It is not a standard function or property name, but rather a reference to the application itself [1]. 2. RenderMarkdown: This is a common function name found in various Markdown rendering libraries, particularly within Kotlin-based projects (such as Compose Multiplatform) [3][4]. 3. outlineVariant: This is a standard property within Material Design 3 (Material 3) color systems [5][6]. It represents a color used for decorative elements or boundaries (such as dividers) where high-contrast color is not strictly required [5]. 4. 2.dp: This is a unit of measurement used in UI frameworks like Jetpack Compose and Compose Multiplatform to specify dimensions (density-independent pixels) [3][5]. These terms are likely being viewed together in a codebase or technical documentation context where a UI component is being styled. For instance, a developer might be configuring the appearance of a rendered Markdown element within a Material 3-themed application by applying an outline variant color with a thickness of 2 density-independent pixels [3][6].

Citations:


🌐 Web query:

"outlineVariant = MaterialTheme" "2.dp.toPx()" "RenderMarkdown"

💡 Result:

In Jetpack Compose, the snippet provided combines two distinct concepts: a Material Design 3 color property and a density-dependent unit conversion. outlineVariant outlineVariant is a property of the ColorScheme class in the Material Design 3 (Material3) library [1][2]. It is a utility color intended for the boundaries of decorative elements where strong contrast (provided by the standard outline color) is not required [2]. It is accessed through the current theme: val color = MaterialTheme.colorScheme.outlineVariant 2.dp.toPx The.toPx function is an extension method on the Dp class that converts a density-independent pixel (dp) value into physical pixels [3][4]. Because the conversion depends on the device's screen density, this method requires access to a Density instance [3][5]. To use.toPx outside of a scope that already provides density (such as DrawScope inside a Canvas) [6][7], you must use the LocalDensity composition local [5][6]: import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp // Within a Composable function: val density = LocalDensity.current val pxValue = with(density) { 2.dp.toPx } If you attempted to use the expression literally as written in your query, it would likely result in a compilation error because.toPx cannot be called directly on an integer or Dp value without providing the density context [5][6].

Citations:


🌐 Web query:

zly2006/zhihu-plus-plus "RenderMarkdown" outlineVariant

💡 Result:

In the zly2006/zhihu-plus-plus repository, RenderMarkdown is a Jetpack Compose composable function used to render Markdown-formatted content, such as Zhihu question and answer text, within the application [1][2][3]. Regarding the term "outlineVariant", it is not a specific component or function within the zhihu-plus-plus repository. Instead, it refers to a standard property of the ColorScheme class in Google's Material Design library (often used in conjunction with Jetpack Compose) [4][5]. This property provides a color value for decorative elements (like dividers) that do not require high contrast [4][5]. The project zhihu-plus-plus utilizes Material 3 and, as of recent updates, has introduced support for miuix (Xiaomi's UI style) in addition to Material 3, allowing for theme-based UI styling [3]. You may encounter references to Material Design properties like outlineVariant when exploring the codebase's UI theme configurations or custom composable styling [3][4][5].

Citations:


🌐 Web query:

"site-status" repo outlineVariant 2.dp.toPx() TextLayoutResult

💡 Result:

The query refers to concepts within Jetpack Compose development and potentially a specific repository component. 1. Density.toPx: In Jetpack Compose, toPx is an extension function on the Dp type that converts density-independent pixels to raw pixels [1][2]. Because it depends on the screen density, it requires a Density receiver (such as LocalDensity.current) to be in scope [3][4][5]. It is commonly used within DrawScope (e.g., inside a Canvas) [2][5] or by accessing the current density: with(LocalDensity.current) { 2.dp.toPx } [3][4]. 2. TextLayoutResult: This is a data class that holds the result of a text layout calculation [6]. It is often obtained via the onTextLayout callback on a Text composable [7] or by using a TextMeasurer to measure text manually [8][9]. It provides access to layout information such as the text size, baseline positions, and bounding boxes for individual lines [6][10][7]. 3. Repo Context: While "site-status" refers to a web component (rh-site-status) often used in design systems (such as Red Hat's) [11][12], it is not a standard Jetpack Compose library component. It is likely a coincidence in naming or a reference to a custom project-specific repository. There is no standard combined API that links a "site-status repo" directly to a specific usage of 2.dp.toPx and TextLayoutResult. These are standard, distinct Jetpack Compose building blocks often used together when building custom UI components that require text measurement and pixel-based drawing [13][10][9].

Citations:


🌐 Web query:

github.com/chloemlla/sentence_embeddings outlineVariant 2.dp.toPx

💡 Result:

The query combines two distinct topics: software repositories for natural language processing and Android Jetpack Compose development. Regarding the GitHub repository link: The URL github.com/chloemlla/sentence_embeddings appears to be a hypothetical or unreachable repository. Several other public repositories on GitHub use similar naming conventions, such as choran/sentence_embeddings [1], contemn1/sentence_embeddings [2], and GiovanniStephens/sentence-embeddings [3], but none are associated with the user chloemlla. Regarding the code snippet outlineVariant 2.dp.toPx: This is a reference to Android Jetpack Compose development [4][5]. 1. outlineVariant: This is a color role defined in the Material Design 3 color system [6][7], typically used for borders and dividers. 2. 2.dp.toPx: This is a standard Jetpack Compose expression used to convert density-independent pixels (dp) to raw pixels (px) [4][8][5]. Because conversion depends on the screen density, it requires a Density context (often provided by LocalDensity.current) [8][9]. A common implementation pattern is: val pxValue = with(LocalDensity.current) { 2.dp.toPx } [4][8][9] This specific snippet likely appears in code examples demonstrating custom drawing, animations, or UI layouts where pixel-precise sizing is required [6][5][10].

Citations:


按测试密度计算虚线扫描区域。

当前固定扫描范围在高密度设备像素下可能错过 lineBottom - 2.dp 位置的实际虚线。使用 composeRule.density 将虚线偏移和扫描半径换算为测试捕获图像的像素范围。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/androidTest/java/com/github/zly2006/zhihu/ArticleScreenInstrumentedTest.kt`
around lines 616 - 617, 更新虚线扫描区域的 top 和 bottom 计算,使用 composeRule.density 将
lineBottom 的 dp 偏移及扫描半径转换为测试截图像素,避免固定的 6f 和 2f 在高密度设备上遗漏目标位置;保留现有的像素边界限制。

val magentaPixels = (top..bottom).sumOf { y ->
(0 until pixels.width).count { x ->
val color = pixels[x, y]
color.red > 0.8f && color.green < 0.2f && color.blue > 0.8f
}
}
assertTrue(
"Highlighted visual line $line must contain visible dash pixels; found $magentaPixels. Screenshot: ${output.absolutePath}",
magentaPixels >= 4,
)
}
}

@Test
fun highlightedParagraphTapOpensActionsInsideAnswerScreen() {
val viewModel = seededAnswerViewModel(ANSWER)
Expand Down Expand Up @@ -1387,6 +1451,12 @@ class ArticleScreenInstrumentedTest {
const val FORMATTED_HIGHLIGHT_PREFIX = "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
const val FORMATTED_HIGHLIGHT = "划线命中"
const val FORMATTED_HIGHLIGHT_PARAGRAPH = "$FORMATTED_HIGHLIGHT_PREFIX$FORMATTED_HIGHLIGHT 后缀"
const val WRAPPED_HIGHLIGHT_PREFIX = "普通前缀 "
const val WRAPPED_HIGHLIGHT =
"这是位于段落中间并且需要跨越多个视觉行的划线内容,用于验证每一行都能完整绘制虚线。"
const val WRAPPED_HIGHLIGHT_SUFFIX = " 普通后缀"
const val WRAPPED_HIGHLIGHT_PARAGRAPH =
"$WRAPPED_HIGHLIGHT_PREFIX$WRAPPED_HIGHLIGHT$WRAPPED_HIGHLIGHT_SUFFIX"
val HIGHLIGHTED_PARAGRAPH_HTML =
"""
<p data-pid="WGd4cbq-"><span class="highlight-wrap other has-comments"
Expand All @@ -1411,6 +1481,15 @@ class ArticleScreenInstrumentedTest {
data-highlight-content-id="777"
data-highlight-content-type="answer">$FORMATTED_HIGHLIGHT</span> 后缀</p>
""".trimIndent()
val WRAPPED_HIGHLIGHT_PARAGRAPH_HTML =
"""
<p>$WRAPPED_HIGHLIGHT_PREFIX<span class="highlight-wrap other has-comments"
data-highlight-id="wrapped-highlight"
data-highlight-like-count="1"
data-highlight-comment-count="1"
data-highlight-content-id="778"
data-highlight-content-type="answer">$WRAPPED_HIGHLIGHT</span>$WRAPPED_HIGHLIGHT_SUFFIX</p>
""".trimIndent()

val ARTICLE = Article(
type = ArticleType.Article,
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ ksp.incremental=true
# Enable parallel execution
org.gradle.parallel=true

app.versionName=0.26.1
app.versionCode=730
app.versionName=0.27
app.versionCode=732

# Enabled parallel sync for Gradle 9.4+
org.gradle.tooling.parallel=true
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.Comment
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.MarkChatRead
import androidx.compose.material.icons.filled.PersonAddAlt1
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.ContactPage
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material.icons.outlined.StarOutline
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.CenterAlignedTopAppBar
Expand Down Expand Up @@ -299,20 +299,6 @@ private fun NotificationInvitationRow(
)
Spacer(Modifier.height(3.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
invitation.avatarUrls.take(2).forEachIndexed { index, avatar ->
AsyncImage(
model = avatar.url,
contentDescription = null,
modifier = Modifier
.padding(start = if (index == 0) 0.dp else 2.dp)
.size(22.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surfaceVariant),
)
}
if (invitation.avatarUrls.isNotEmpty()) {
Spacer(Modifier.width(6.dp))
}
Text(
text = invitation.textPrefix + invitation.text,
style = MaterialTheme.typography.bodyMedium,
Expand Down Expand Up @@ -549,9 +535,9 @@ fun NotificationItemView(

private fun MobileNotificationCategory.homeIcon(): ImageVector = when (this) {
MobileNotificationCategory.Comment -> Icons.AutoMirrored.Outlined.Comment
MobileNotificationCategory.Like -> Icons.Outlined.FavoriteBorder
MobileNotificationCategory.Favorite -> Icons.Outlined.StarOutline
MobileNotificationCategory.Follow -> Icons.Outlined.Info
MobileNotificationCategory.Like -> Icons.Filled.Favorite
MobileNotificationCategory.Favorite -> Icons.Filled.Bookmark
MobileNotificationCategory.Follow -> Icons.Filled.PersonAddAlt1
}

internal fun MobileNotificationTimelineItem.displayTitle(): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,16 @@ private fun highlightedLineRects(
return (startLine..endLine).map { line ->
val lineStart = maxOf(safeStart, layout.getLineStart(line))
val lineEnd = minOf(safeEnd, layout.getLineEnd(line, visibleEnd = true))
val left = if (lineStart < lineEnd) layout.getHorizontalPosition(lineStart, usePrimaryDirection = true) else 0f
val right = if (lineStart < lineEnd) layout.getHorizontalPosition(lineEnd, usePrimaryDirection = true) else left
val left = when {
lineStart >= lineEnd -> 0f
line == startLine -> layout.getHorizontalPosition(lineStart, usePrimaryDirection = true)
else -> layout.getLineLeft(line)
}
val right = when {
lineStart >= lineEnd -> left
line == endLine -> layout.getHorizontalPosition(lineEnd, usePrimaryDirection = true)
else -> layout.getLineRight(line)
}
androidx.compose.ui.geometry.Rect(
left = minOf(left, right),
top = layout.getLineTop(line),
Expand Down
Loading