feat(group-chat): support dynamic BiDi Arabic input and RTL message bubbles
- Align GroupChatComposer with ChatComposer (PR #1047): - Dynamic BiDi input detection via BidiUtils.resolveLayoutDirection - Docks cursor to the right edge and sets TextDirection.Rtl for Arabic - Preserves LTR outer surface and row to keep send/stop button pinned in place - User message bubbles: dynamic RTL layout direction and TextDirection.ContentOrRtl - Bot messages: dynamic TextDirection for bot display name, active speaker thinking row, and tool labels - Tool code/commands preserved as LTR monospace with BiDi-safe output text Co-authored-by: Luna <luna.m57@agentmail.to>
This commit is contained in:
1 parent
2d0d749cbe
commit
fd011ac78d
1 file changed
+234
-171
@@ -87,6 +87,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
@@ -274,6 +275,8 @@ fun GroupChatScreen(
|
|||||||
|
|
||||||
state.activeSpeaker?.let { speaker ->
|
state.activeSpeaker?.let { speaker ->
|
||||||
item(key = "active_speaker") {
|
item(key = "active_speaker") {
|
||||||
|
val thinkingText = stringResource(R.string.group_chat_thinking, speaker)
|
||||||
|
val isThinkingRtl = remember(thinkingText) { BidiUtils.isRtlText(thinkingText) }
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(start = 8.dp, top = 4.dp),
|
modifier = Modifier.padding(start = 8.dp, top = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -284,8 +287,16 @@ fun GroupChatScreen(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.group_chat_thinking, speaker),
|
text = thinkingText,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
textDirection =
|
||||||
|
if (isThinkingRtl) {
|
||||||
|
TextDirection.ContentOrRtl
|
||||||
|
} else {
|
||||||
|
TextDirection.ContentOrLtr
|
||||||
|
},
|
||||||
|
),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -409,124 +420,131 @@ fun GroupChatScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── COMPOSER (uses TextFieldValue + embedded Send button matching ChatComposer) ──
|
// ── COMPOSER (uses TextFieldValue + embedded Send button matching ChatComposer) ──
|
||||||
Surface(
|
val ambientLayoutDirection = LocalLayoutDirection.current
|
||||||
modifier =
|
val inputLayoutDirection =
|
||||||
Modifier
|
remember(inputFieldValue.text, ambientLayoutDirection) {
|
||||||
.fillMaxWidth()
|
BidiUtils.resolveLayoutDirection(inputFieldValue.text, fallback = ambientLayoutDirection)
|
||||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
}
|
||||||
shape = RoundedCornerShape(20.dp),
|
val isInputRtl = inputLayoutDirection == LayoutDirection.Rtl
|
||||||
border =
|
|
||||||
BorderStroke(
|
CompositionLocalProvider(LocalLayoutDirection provides inputLayoutDirection) {
|
||||||
width = if (isFocused) 2.dp else 1.dp,
|
BasicTextField(
|
||||||
color =
|
value = inputFieldValue,
|
||||||
if (isFocused) {
|
onValueChange = { inputFieldValue = it },
|
||||||
MaterialTheme.colorScheme.primary
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.surface,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 14.dp, end = 4.dp, top = 6.dp, bottom = 6.dp),
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
.onFocusChanged { isFocused = it.isFocused }
|
||||||
) {
|
.testTag("group_chat_input"),
|
||||||
val isInputRtl = remember(inputFieldValue.text) { BidiUtils.isRtlText(inputFieldValue.text) }
|
textStyle =
|
||||||
val ambientLayoutDirection = LocalLayoutDirection.current
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
val inputLayoutDirection =
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
remember(inputFieldValue.text, ambientLayoutDirection) {
|
textAlign = if (isInputRtl) TextAlign.Right else TextAlign.Left,
|
||||||
if (inputFieldValue.text.isBlank()) {
|
textDirection = if (isInputRtl) TextDirection.Rtl else TextDirection.Ltr,
|
||||||
ambientLayoutDirection
|
),
|
||||||
} else if (isInputRtl) {
|
singleLine = false,
|
||||||
LayoutDirection.Rtl
|
maxLines = 4,
|
||||||
} else {
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||||
LayoutDirection.Ltr
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
|
||||||
|
keyboardActions = KeyboardActions(onSend = { handleSend() }),
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
CompositionLocalProvider(LocalLayoutDirection provides ambientLayoutDirection) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
border =
|
||||||
|
BorderStroke(
|
||||||
|
width = if (isFocused) 2.dp else 1.dp,
|
||||||
|
color =
|
||||||
|
if (isFocused) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 14.dp, end = 4.dp, top = 6.dp, bottom = 6.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
contentAlignment =
|
||||||
|
if (isInputRtl) {
|
||||||
|
Alignment.CenterEnd
|
||||||
|
} else {
|
||||||
|
Alignment.CenterStart
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(LocalLayoutDirection provides inputLayoutDirection) {
|
||||||
|
if (inputFieldValue.text.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
stringResource(
|
||||||
|
R.string.group_chat_composer_hint,
|
||||||
|
state.groupName.ifBlank { groupName },
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color =
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant.copy(
|
||||||
|
alpha = 0.6f,
|
||||||
|
),
|
||||||
|
textAlign = if (isInputRtl) TextAlign.Right else TextAlign.Left,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
|
if (state.activeSpeaker != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { viewModel.stopGeneration() },
|
||||||
|
colors =
|
||||||
|
IconButtonDefaults.filledTonalIconButtonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.testTag("group_chat_stop_button"),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Stop,
|
||||||
|
contentDescription = stringResource(R.string.group_chat_action_stop),
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IconButton(
|
||||||
|
onClick = handleSend,
|
||||||
|
enabled = inputFieldValue.text.isNotBlank(),
|
||||||
|
colors = IconButtonDefaults.filledTonalIconButtonColors(),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.testTag("group_chat_send_button"),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.Send,
|
||||||
|
contentDescription = "Send",
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Box(
|
)
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
contentAlignment = if (isInputRtl) Alignment.CenterEnd else Alignment.CenterStart,
|
|
||||||
) {
|
|
||||||
if (inputFieldValue.text.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
text =
|
|
||||||
stringResource(
|
|
||||||
R.string.group_chat_composer_hint,
|
|
||||||
state.groupName.ifBlank { groupName },
|
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
|
||||||
textAlign = if (isInputRtl) TextAlign.Right else TextAlign.Left,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides inputLayoutDirection) {
|
|
||||||
BasicTextField(
|
|
||||||
value = inputFieldValue,
|
|
||||||
onValueChange = { inputFieldValue = it },
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(min = 24.dp, max = 120.dp)
|
|
||||||
.onFocusChanged { isFocused = it.isFocused }
|
|
||||||
.testTag("group_chat_input"),
|
|
||||||
textStyle =
|
|
||||||
MaterialTheme.typography.bodyMedium.copy(
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
textAlign = if (isInputRtl) TextAlign.Right else TextAlign.Left,
|
|
||||||
),
|
|
||||||
singleLine = false,
|
|
||||||
maxLines = 4,
|
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
|
|
||||||
keyboardActions = KeyboardActions(onSend = { handleSend() }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
|
|
||||||
if (state.activeSpeaker != null) {
|
|
||||||
IconButton(
|
|
||||||
onClick = { viewModel.stopGeneration() },
|
|
||||||
colors =
|
|
||||||
IconButtonDefaults.filledTonalIconButtonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
|
||||||
),
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.size(36.dp)
|
|
||||||
.testTag("group_chat_stop_button"),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Stop,
|
|
||||||
contentDescription = stringResource(R.string.group_chat_action_stop),
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
IconButton(
|
|
||||||
onClick = handleSend,
|
|
||||||
enabled = inputFieldValue.text.isNotBlank(),
|
|
||||||
colors = IconButtonDefaults.filledTonalIconButtonColors(),
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.size(36.dp)
|
|
||||||
.testTag("group_chat_send_button"),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.Send,
|
|
||||||
contentDescription = "Send",
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -865,6 +883,8 @@ private fun GroupMessageCard(
|
|||||||
message.text
|
message.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val isSystemRtl = remember(displayText) { BidiUtils.isRtlText(displayText) }
|
||||||
|
val systemDirection = if (isSystemRtl) LayoutDirection.Rtl else LocalLayoutDirection.current
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.fillMaxWidth().padding(vertical = 4.dp),
|
modifier = modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
@@ -874,28 +894,40 @@ private fun GroupMessageCard(
|
|||||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = if (message.isPass) 0.5f else 0.6f),
|
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = if (message.isPass) 0.5f else 0.6f),
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)),
|
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)),
|
||||||
) {
|
) {
|
||||||
Row(
|
CompositionLocalProvider(LocalLayoutDirection provides systemDirection) {
|
||||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
if (message.isPass) {
|
) {
|
||||||
Icon(
|
if (message.isPass) {
|
||||||
imageVector = Icons.Filled.FastForward,
|
Icon(
|
||||||
contentDescription = null,
|
imageVector = Icons.Filled.FastForward,
|
||||||
modifier = Modifier.size(12.dp),
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
modifier = Modifier.size(12.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = displayText,
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.labelSmall.copy(
|
||||||
|
textDirection =
|
||||||
|
if (isSystemRtl) {
|
||||||
|
TextDirection.ContentOrRtl
|
||||||
|
} else {
|
||||||
|
TextDirection.ContentOrLtr
|
||||||
|
},
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = displayText,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (message.isUser) {
|
} else if (message.isUser) {
|
||||||
|
val isUserRtl = remember(message.text) { BidiUtils.isRtlText(message.text) }
|
||||||
|
val userBubbleDirection = if (isUserRtl) LayoutDirection.Rtl else LocalLayoutDirection.current
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
@@ -905,52 +937,63 @@ private fun GroupMessageCard(
|
|||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.fillMaxWidth(0.85f),
|
modifier = Modifier.fillMaxWidth(0.85f),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(12.dp)) {
|
CompositionLocalProvider(LocalLayoutDirection provides userBubbleDirection) {
|
||||||
SelectionContainer {
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
Text(
|
SelectionContainer {
|
||||||
text = message.text,
|
Text(
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
text = message.text,
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
style =
|
||||||
)
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
}
|
textDirection =
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
if (isUserRtl) {
|
||||||
Row(
|
TextDirection.ContentOrRtl
|
||||||
modifier = Modifier.align(Alignment.End),
|
} else {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
TextDirection.ContentOrLtr
|
||||||
) {
|
},
|
||||||
IconButton(
|
),
|
||||||
onClick = {
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
scope.launch {
|
)
|
||||||
clipboard.setClipEntry(
|
}
|
||||||
ClipEntry(ClipData.newPlainText(null, message.text)),
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
)
|
Row(
|
||||||
}
|
modifier = Modifier.align(Alignment.End),
|
||||||
copied = true
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
},
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
IconButton(
|
||||||
imageVector = if (copied) Icons.Filled.Check else Icons.Filled.ContentCopy,
|
onClick = {
|
||||||
contentDescription = stringResource(R.string.content_desc_copy),
|
scope.launch {
|
||||||
modifier = Modifier.size(12.dp),
|
clipboard.setClipEntry(
|
||||||
tint = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f),
|
ClipEntry(ClipData.newPlainText(null, message.text)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
copied = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (copied) Icons.Filled.Check else Icons.Filled.ContentCopy,
|
||||||
|
contentDescription = stringResource(R.string.content_desc_copy),
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
formatTimestamp(
|
||||||
|
message.timestamp,
|
||||||
|
DateFormat.is24HourFormat(LocalContext.current),
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(2.dp))
|
|
||||||
Text(
|
|
||||||
text =
|
|
||||||
formatTimestamp(
|
|
||||||
message.timestamp,
|
|
||||||
DateFormat.is24HourFormat(LocalContext.current),
|
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val isBotNameRtl = remember(message.senderDisplayName) { BidiUtils.isRtlText(message.senderDisplayName) }
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
@@ -970,7 +1013,15 @@ private fun GroupMessageCard(
|
|||||||
Column(modifier = Modifier.padding(12.dp)) {
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = message.senderDisplayName,
|
text = message.senderDisplayName,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style =
|
||||||
|
MaterialTheme.typography.labelMedium.copy(
|
||||||
|
textDirection =
|
||||||
|
if (isBotNameRtl) {
|
||||||
|
TextDirection.ContentOrRtl
|
||||||
|
} else {
|
||||||
|
TextDirection.ContentOrLtr
|
||||||
|
},
|
||||||
|
),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
@@ -1112,9 +1163,13 @@ private fun GroupChatToolChip(
|
|||||||
} else {
|
} else {
|
||||||
tool.name
|
tool.name
|
||||||
}
|
}
|
||||||
|
val isLabelRtl = remember(label) { BidiUtils.isRtlText(label) }
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style =
|
||||||
|
MaterialTheme.typography.labelSmall.copy(
|
||||||
|
textDirection = if (isLabelRtl) TextDirection.ContentOrRtl else TextDirection.ContentOrLtr,
|
||||||
|
),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
@@ -1160,6 +1215,7 @@ private fun GroupChatToolChip(
|
|||||||
style =
|
style =
|
||||||
MaterialTheme.typography.bodySmall.copy(
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
|
textDirection = TextDirection.Ltr,
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 4.dp),
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 4.dp),
|
||||||
@@ -1175,6 +1231,7 @@ private fun GroupChatToolChip(
|
|||||||
tool.exitCode == 0 -> stringResource(R.string.group_chat_tool_no_output_success)
|
tool.exitCode == 0 -> stringResource(R.string.group_chat_tool_no_output_success)
|
||||||
else -> stringResource(R.string.group_chat_tool_no_output)
|
else -> stringResource(R.string.group_chat_tool_no_output)
|
||||||
}
|
}
|
||||||
|
val isOutputRtl = remember(outputText) { BidiUtils.isRtlText(outputText) }
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(4.dp),
|
shape = RoundedCornerShape(4.dp),
|
||||||
@@ -1187,6 +1244,12 @@ private fun GroupChatToolChip(
|
|||||||
style =
|
style =
|
||||||
MaterialTheme.typography.bodySmall.copy(
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
|
textDirection =
|
||||||
|
if (isOutputRtl) {
|
||||||
|
TextDirection.ContentOrRtl
|
||||||
|
} else {
|
||||||
|
TextDirection.Ltr
|
||||||
|
},
|
||||||
),
|
),
|
||||||
color =
|
color =
|
||||||
if (tool.isError) {
|
if (tool.isError) {
|
||||||
|
|||||||
Reference in new issue
Block a user