fix(chat): 附件芯片改换行排布 —— 多附件不再被裁到屏幕外

主人 2026-10-04 在主会话里发现「用户消息显示有问题」:带 2~3 个附件的用户消息里,
第一枚芯片就吃掉了整排——服务端存储名是 32 位十六进制 + 扩展名(如 `36a4aba0…5524808.txt`),
一枚约 267dp,而芯片排是「横向可滚的单行 + `widthIn(max = 320.dp)`」,于是第二枚只剩一条边
露在屏幕外。看的人不知道还有别的附件,也看不出那一行可以滚。

改为 `FlowRow` 换行排布(用户消息整排贴右 `alignEnd`),N 枚芯片全部可见;代价是 5 枚会占
2~3 行——比「藏起来」值。去掉随之失效的 `CHIP_ROW_MAX_WIDTH` 与两个 import,
并同步 `ChatScreen` 里待发芯片排的注释(同一处行为)。

实机复核:主会话那条三附件消息现在渲染为三行,三枚芯片与各自的
「TXT · 4 B · 4 字 · 已读入」全部可见(截图 `app/build/e2e-shots/chips-after.png`)。
This commit is contained in:
AskaEth committed 2026-10-04 19:08:26 +08:00
1 parent 2369dd2115
commit ca5596a732
2 files changed
+19 -15

No files matched your search

@@ -363,7 +363,7 @@ private fun InputBar(
}
}
/** 待发附件的芯片排:横向可滚(5 枚芯片不挤成两行,也不会把输入框顶上去)。 */
/** 待发附件的芯片排:换行排布(5 枚最多占 3 行;宁可占点高度,也不把附件藏进横向滚动里)。 */
@Composable
private fun DraftDocumentChips(documents: List<DraftDocument>, onRemove: (Int) -> Unit) {
DocumentChipRow(
@@ -1,16 +1,15 @@
package top.yeij.cyrene.ui.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.runtime.Composable
@@ -41,9 +40,6 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme
/** 流式进行中的光标。 */
private const val CURSOR = "▍"
/** 附件芯片排的最大宽度:与气泡同级,避免 5 枚芯片把整行铺满屏幕。 */
private val CHIP_ROW_MAX_WIDTH = 320.dp
/**
* 一条落库消息渲染成一个或多个气泡:
* - `mode` 为 `chat`(或缺失——后端当前不落库 mode,一律回落 chat)时按 `<p>` 切分,
@@ -70,7 +66,7 @@ fun MessageBubble(message: HistoryMessageDto) {
MessageGroup(isUser = isUser, footer = messageSourceLabel(message, LocalEnumCatalog.current)) {
message.thinking?.takeIf { it.isNotBlank() }?.let { ThinkingCard(it) }
DocumentChipRow(chips = message.documents.map(DocumentChips::of))
DocumentChipRow(chips = message.documents.map(DocumentChips::of), alignEnd = isUser)
message.image?.takeIf { it.isNotBlank() }?.let { ChatImage(it) }
bubbles.forEachIndexed { index, bubble ->
Bubble(bubble, isUser, isLast = index == bubbles.lastIndex)
@@ -88,16 +84,21 @@ fun MessageBubble(message: HistoryMessageDto) {
@Composable
fun PendingBubble(item: PendingMessage) {
MessageGroup(isUser = true, footer = "发送中…") {
DocumentChipRow(chips = item.documents.map(DocumentChips::of))
DocumentChipRow(chips = item.documents.map(DocumentChips::of), alignEnd = true)
item.imageUri?.takeIf { it.isNotBlank() }?.let { ChatImage(it) }
if (item.text.isNotEmpty()) Bubble(plainBubble(item.text), isUser = true, isLast = true)
}
}
/**
* 附件芯片排:横向可滚(最多 5 枚也不挤成两行),一枚一枚展示
* 「名字(中略,保住扩展名)/ 类型 · 体积 · 字数 · 状态」。
* 附件芯片排:**换行排布**——一枚放不下就落到下一行,N 枚都看得见。
*
* 它曾经是「横向可滚的单行 + `widthIn(max = 320.dp)`」,**实机一看就是坏的**(2026-10-04
* 主人在主会话里发现):服务端存储名是 **32 位十六进制 + 扩展名**(`36a4aba0…5524808.txt`),
* 一枚芯片就能吃掉 ~267dp,于是 320dp 的视口里第二枚只剩一条边露在屏幕外——看的人根本
* 不知道还有第二个附件,也没人看得出那一行可以滚。换行之后 5 枚会占 2~3 行,比「藏起来」值。
*
* [alignEnd] = 用户消息(气泡在右侧):整排贴右;否则一枚短芯片会贴在该排左端、看着像错位。
* [onRemove] 非空即带移除按钮——待发位(输入栏上方)要能反悔,历史气泡里则不给。
*
* 附件可能是空列表(绝大多数消息都是),此时**不占位、不留空白**。
@@ -106,14 +107,17 @@ fun PendingBubble(item: PendingMessage) {
fun DocumentChipRow(
chips: List<DocumentChipModel>,
modifier: Modifier = Modifier,
alignEnd: Boolean = false,
onRemove: ((Int) -> Unit)? = null,
) {
if (chips.isEmpty()) return
Row(
modifier = modifier
.widthIn(max = CHIP_ROW_MAX_WIDTH)
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(6.dp),
FlowRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(
space = 6.dp,
alignment = if (alignEnd) Alignment.End else Alignment.Start,
),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
chips.forEachIndexed { index, chip ->
DocumentChip(model = chip, onRemove = onRemove?.let { remove -> { remove(index) } })