merge: W54D —— 分享确认面板正文预览改成三行窗口 + 内部滚动(发送前能读到整段正文)
This commit is contained in:
commit
f3c1121887
2 files changed
+102
-16
No files matched your search
@@ -15,11 +15,15 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import top.yeij.cyrene.domain.chat.DocumentChips
|
||||
@@ -247,15 +251,15 @@ const val SHARE_CONFIRM_CAPTION_PLACEHOLDER = "加一句描述…"
|
||||
* 所以预览区自己带一个高度上限 + 内部滚动:内容少时它按内容高(上限不生效),内容多时它滚动、
|
||||
* 输入框与按钮原地不动。
|
||||
*
|
||||
* ## 正文为什么只给三行
|
||||
* ## 正文给三行,但三行之外是「滚」不是「省略号」
|
||||
* 预览只是「分享进来的是什么」的一眼,而用户要的是按一下发送:把一段几万字的正文整个铺开,这个面板
|
||||
* 就成了一个阅读器。三行之外用省略号收口,不假装那是全文。
|
||||
* 就成了一个阅读器。所以正文那一窗**恒为三行高**([SHARE_PREVIEW_TEXT_LINES]),面板不随正文长短
|
||||
* 变化。
|
||||
*
|
||||
* ⚠️ W53X3 之后**面板里再没有第二个地方放着这段正文**:输入框已经改成「用户附加的描述」(初值恒为
|
||||
* 空),不再是正文的可编辑副本。也就是说超过三行的分享正文在面板上只看得到开头——**这不影响发出去的
|
||||
* 内容**([shareConfirmBody] 用的是完整的那一段,一字不丢),但它确实少了一处「发送前读全文」的地方。
|
||||
* 这是 W53X 那一版的老行为,本批只把它如实写下来、**没有顺手改**:正文块要不要能展开属于观感取舍,
|
||||
* 得由主人拍板。
|
||||
* 但三行之外**不再收成省略号**(W54D 主人拍板改掉了 W53X 的老行为):W53X3 之后**面板里再没有第二
|
||||
* 个地方放着这段正文**——输入框是「用户附加的描述」(初值恒为空),不再是正文的可编辑副本。于是截断
|
||||
* 成省略号就等于「按发送之前读不到要发的那段正文」,而用户点这颗「发送」要确认的恰恰是它。所以这一窗
|
||||
* 内部可滚([ShareConfirmBody]):**面板的高度一分不涨,正文滚着读完**。
|
||||
*/
|
||||
@Composable
|
||||
private fun ShareConfirmPreviews(preview: ShareConfirmPreview) {
|
||||
@@ -303,22 +307,70 @@ private fun ShareConfirmPreviews(preview: ShareConfirmPreview) {
|
||||
style = MiuixTheme.textStyles.footnote2,
|
||||
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
Text(
|
||||
text = preview.text,
|
||||
style = MiuixTheme.textStyles.footnote1,
|
||||
maxLines = SHARE_PREVIEW_TEXT_LINES,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
ShareConfirmBody(preview.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 正文那一窗:**高度恒为三行,再长就在这一窗里滚**(W54D 主人拍板,见 [ShareConfirmPreviews])。
|
||||
*
|
||||
* ## 为什么量行高,而不是写一个 dp 常量
|
||||
* 这个上限必须**正好三行**:矮了会在字身上切一刀(滚动停在一个被切开的字形上),高了就等于把「三行」
|
||||
* 这个上限悄悄放宽。而三行有多高没法提前写出来——Miuix 的 `footnote1` 只给了 13sp 的字号,
|
||||
* `lineHeight` 是 `Unspecified`,行高由系统字体的 ascent/descent 决定、随「大字体」设置一起缩放。
|
||||
* 一个 dp 常量在默认字号档上勉强对得上,到了大字号档就会矮一大截(行数不足三行、末行被切)。
|
||||
* 所以这里改为**量**:先按没有上限排一次,从 `TextLayoutResult` 上取第三行的底边当上限。
|
||||
*
|
||||
* 上限只写一次(`cap` 还是 [Dp.Infinity] 时才改),所以不会来回跳:拿到上限后重新排版,正文的宽度
|
||||
* 与样式都没变,行位置与首帧一致,量出来的值也一样。
|
||||
*
|
||||
* ⚠️ 代价是**首帧先按完整正文排一次**、下一帧才收到三行:正文很长时,这一窗理论上会有一帧高出一截、
|
||||
* 然后回落。另一条路——写死一个 dp 上限——没有这一帧,但在非默认字号档上必然切字,两害相权取了
|
||||
* 「量」。这一帧在实际渲染里看不看得见(弹层这时正在进场,可能被动画盖住)**没有在真机上验过**,
|
||||
* 留给主人真机验收。
|
||||
*
|
||||
* ## 两层滚动怎么分工
|
||||
* 这一窗自己滚(正文读完),外层 [ShareConfirmPreviews] 也滚(图片 + 文件芯片 + 这一块合起来的高度
|
||||
* 上限)。手指停在正文上时先滚正文、滚到底再把余量交给外层——`verticalScroll` 本来就走嵌套滚动,
|
||||
* 不用额外接线。
|
||||
*/
|
||||
@Composable
|
||||
private fun ShareConfirmBody(text: String) {
|
||||
val density = LocalDensity.current
|
||||
var cap by remember(text) { mutableStateOf(Dp.Infinity) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = cap)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.footnote1,
|
||||
onTextLayout = { layout ->
|
||||
if (cap == Dp.Infinity && layout.lineCount > 0) {
|
||||
// 正文不到三行时上限就取全文的高度(`heightIn` 是个上限,短了本来也不生效)。
|
||||
val lastVisible = layout.lineCount.coerceAtMost(SHARE_PREVIEW_TEXT_LINES) - 1
|
||||
cap = with(density) { layout.getLineBottom(lastVisible).toDp() }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 预览区的高度上限(见 [ShareConfirmPreviews]:超出的部分靠内部滚动看)。 */
|
||||
private val SHARE_PREVIEW_MAX_HEIGHT = 150.dp
|
||||
|
||||
/** 预览里正文给几行(超出的部分收成省略号,见 [ShareConfirmPreviews] 的 KDoc)。 */
|
||||
private const val SHARE_PREVIEW_TEXT_LINES = 3
|
||||
/**
|
||||
* 预览里正文那一窗给几行(W54D 起**不再截断**,超出的部分在窗内滚动,见 [ShareConfirmBody])。
|
||||
*
|
||||
* `internal` 是为了让 `ShareConfirmTest` 能把这个数钉成字面量:面板的滚动行为单测够不着(本模块没有
|
||||
* Compose UI 测试的依赖),能钉住的只有「这一窗是三行」这条判据本身。
|
||||
*/
|
||||
internal const val SHARE_PREVIEW_TEXT_LINES = 3
|
||||
|
||||
/**
|
||||
* 分享进来的一份文件 ⇒ 待发附件。
|
||||
|
||||
@@ -32,6 +32,13 @@ import top.yeij.cyrene.domain.share.SharedDocument
|
||||
* ⚠️ 「输入框初值为空」本身是 Compose 状态(`ChatScreen` 里那格 `remember`),单测碰不到——它由
|
||||
* 主人真机验收(输入框空着、预览里有正文)。这份测试能钉住的是它的**后果**:描述为空时,拼出来的
|
||||
* 正文就是分享正文本身、而且只出现**一次**(见 `the caption is not a copy of the shared text`)。
|
||||
*
|
||||
* ## W54D 又改掉了什么
|
||||
* 正文那一窗从「三行 + 省略号」改成「三行窗口 + 内部滚动」(主人拍板):面板里再没有第二处放着这段
|
||||
* 正文,收成省略号就等于「按发送之前读不到要发的那段正文」。滚动是渲染层的事、单测够不着(本模块
|
||||
* 没有 Compose UI 测试的依赖),所以这里钉的是它的两个前提——**正文完整地进到预览**(一个字都不少)
|
||||
* 与**这一窗仍然是三行**(面板不随正文变高),见 `the whole body reaches the preview …` 与
|
||||
* `the preview window stays three lines tall`。
|
||||
*/
|
||||
class ShareConfirmTest {
|
||||
|
||||
@@ -54,6 +61,33 @@ class ShareConfirmTest {
|
||||
assertFalse(preview.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the whole body reaches the preview even when it is longer than the window`() {
|
||||
val long = (1..40).joinToString("\n") { "第 $it 行" }
|
||||
|
||||
// W54D 主人拍板:预览的正文那一窗恒为三行高,但**里面能滚**——三行之外不再收成省略号。这条钉
|
||||
// 的是那一窗的输入:纯函数层把**完整**的正文交给渲染层(这一段一个字都不许在这里少),少了就
|
||||
// 一定读不到——面板里再没有第二处放着这段正文(输入框只剩「用户附加的描述」)。
|
||||
val preview = shareConfirmPreview(SharedContent(text = long))
|
||||
assertEquals(long, preview.text)
|
||||
assertEquals(40, preview.text.lines().size)
|
||||
assertTrue(
|
||||
"这一档必须真的超过三行,否则上面那条断言什么也证明不了",
|
||||
preview.text.lines().size > SHARE_PREVIEW_TEXT_LINES,
|
||||
)
|
||||
|
||||
// 发出去的那一段同样一个字不少(两边同源,见 [shareConfirmBody])。
|
||||
assertEquals(long, shareConfirmBody(SharedContent(text = long), ""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the preview window stays three lines tall`() {
|
||||
// 3 这个字面量就是这条测试的全部内容:滚动是渲染层的事、单测够不着(本模块没有 Compose UI
|
||||
// 测试的依赖),能钉住的只有「这一窗是三行」这条判据本身。改大 ⇒ 面板会随长正文变高(主人
|
||||
// 拍板的高度上限没了);改小 ⇒ 正文的可见窗口白缩一行。
|
||||
assertEquals(3, SHARE_PREVIEW_TEXT_LINES)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blank pieces count as nothing to show`() {
|
||||
val preview = shareConfirmPreview(
|
||||
|
||||
Reference in new issue
Block a user