feat(chat): 改为文本选择模式,全选仅作用于当前气泡
用 SelectionContainer 包住气泡/旁白/思维链/工具卡的正文,长按可拖选;移除长按整段复制的实现与其工具文件。
This commit is contained in:
1 parent
33770d87a6
commit
91117986e4
3 files changed
+29
-53
No files matched your search
@@ -1,32 +0,0 @@
|
||||
package top.yeij.cyrene.ui.chat
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
/**
|
||||
* 长按复制文本到系统剪贴板,并给一条 Toast 反馈。
|
||||
* 用框架的 ClipboardManager 而不是 Compose 的 LocalClipboardManager(后者在新版本已弃用)。
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.copyOnLongPress(text: String): Modifier {
|
||||
if (text.isBlank()) return this
|
||||
val context = LocalContext.current
|
||||
return this.pointerInput(text) {
|
||||
detectTapGestures(
|
||||
onLongPress = { copyToClipboard(context, text) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToClipboard(context: Context, text: String) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("Cyrene", text))
|
||||
Toast.makeText(context, "已复制", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -34,6 +35,9 @@ private const val CURSOR = "▍"
|
||||
* - 其它 mode 原样整条渲染,不解析标签;
|
||||
* - `thinking` 非空时(来自 REST `/messages`,owner 可见)在气泡上方显示思维链卡片;
|
||||
* - `image` 非空时在气泡上方显示图片。
|
||||
*
|
||||
* 文本可自由选择:每个气泡/旁白各自包一层 [SelectionContainer],
|
||||
* 因此选择菜单的「全选」只作用于当前那一块,而不是整个页面。
|
||||
*/
|
||||
@Composable
|
||||
fun MessageBubble(message: HistoryMessageDto) {
|
||||
@@ -129,13 +133,14 @@ private fun Bubble(
|
||||
private fun AnnotationLine(parts: List<ContentPart>) {
|
||||
val text = parts.joinToString(" ") { it.text }.trim()
|
||||
if (text.isEmpty()) return
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.footnote1,
|
||||
fontStyle = FontStyle.Italic,
|
||||
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||
)
|
||||
SelectionContainer(modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.footnote1,
|
||||
fontStyle = FontStyle.Italic,
|
||||
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -143,7 +148,6 @@ private fun BubbleBody(text: String, isUser: Boolean) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 300.dp)
|
||||
.copyOnLongPress(text)
|
||||
.background(
|
||||
color = if (isUser) {
|
||||
MiuixTheme.colorScheme.primary
|
||||
@@ -154,11 +158,13 @@ private fun BubbleBody(text: String, isUser: Boolean) {
|
||||
)
|
||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.main,
|
||||
color = if (isUser) MiuixTheme.colorScheme.onPrimary else MiuixTheme.colorScheme.onSurfaceContainer,
|
||||
)
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.main,
|
||||
color = if (isUser) MiuixTheme.colorScheme.onPrimary else MiuixTheme.colorScheme.onSurfaceContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -24,8 +25,8 @@ import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
|
||||
/**
|
||||
* 思维链卡片。默认展开(方便观察她的思考过程),可折叠。
|
||||
* 流式期间标题显示「思考中…」。
|
||||
* 思维链卡片。流式期间展开(方便观察),流式结束自动折叠;历史思维链默认折叠。
|
||||
* 正文包在 [SelectionContainer] 里,长按可自由选择、菜单「全选」只作用于本卡片。
|
||||
*/
|
||||
@Composable
|
||||
fun ThinkingCard(
|
||||
@@ -34,7 +35,6 @@ fun ThinkingCard(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (text.isBlank() && !active) return
|
||||
// 流式期间展开(方便观察),流式结束自动折叠;历史思维链默认折叠。
|
||||
var expanded by remember { mutableStateOf(active) }
|
||||
LaunchedEffect(active) {
|
||||
if (!active) expanded = false
|
||||
@@ -126,9 +126,11 @@ private fun FoldCardLabel(text: String) {
|
||||
|
||||
@Composable
|
||||
private fun FoldCardBody(text: String) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.footnote1,
|
||||
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = text,
|
||||
style = MiuixTheme.textStyles.footnote1,
|
||||
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user