mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Refactor message input to use BasicTextField2 (#2231)
This commit is contained in:
parent
a253464b8a
commit
833861eb20
3 changed files with 189 additions and 186 deletions
|
|
@ -22,6 +22,8 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.recalculateWindowInsets
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
|
|
@ -196,7 +198,7 @@ fun MainScreen(
|
|||
val currentDestination = navController.currentBackStackEntryAsState().value?.destination
|
||||
val topLevelDestination = TopLevelDestination.fromNavDestination(currentDestination)
|
||||
NavigationSuiteScaffold(
|
||||
modifier = Modifier.safeDrawingPadding(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
navigationSuiteItems = {
|
||||
TopLevelDestination.entries.forEach { destination ->
|
||||
val isSelected = destination == topLevelDestination
|
||||
|
|
@ -292,6 +294,11 @@ fun MainScreen(
|
|||
},
|
||||
)
|
||||
NavGraph(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.recalculateWindowInsets()
|
||||
.safeDrawingPadding()
|
||||
.imePadding(),
|
||||
uIViewModel = uIViewModel,
|
||||
bluetoothViewModel = bluetoothViewModel,
|
||||
navController = navController,
|
||||
|
|
|
|||
|
|
@ -20,18 +20,23 @@ package com.geeksville.mesh.ui.message
|
|||
import android.content.ClipData
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Reply
|
||||
|
|
@ -53,7 +58,6 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -64,23 +68,13 @@ 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.focus.onFocusEvent
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.isAltPressed
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
|
|
@ -116,6 +110,9 @@ internal fun MessageScreen(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
val clipboardManager = LocalClipboard.current
|
||||
|
||||
val ourNode by viewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val isConnected by viewModel.isConnected.collectAsStateWithLifecycle(false)
|
||||
|
||||
val channelIndex = contactKey[0].digitToIntOrNull()
|
||||
val nodeId = contactKey.substring(1)
|
||||
val channels by viewModel.channels.collectAsStateWithLifecycle()
|
||||
|
|
@ -140,16 +137,11 @@ internal fun MessageScreen(
|
|||
val selectedIds = rememberSaveable { mutableStateOf(emptySet<Long>()) }
|
||||
val inSelectionMode by remember { derivedStateOf { selectedIds.value.isNotEmpty() } }
|
||||
|
||||
val connState by viewModel.connectionState.collectAsStateWithLifecycle()
|
||||
val quickChat by viewModel.quickChatActions.collectAsStateWithLifecycle()
|
||||
val messages by viewModel.getMessagesFrom(contactKey).collectAsStateWithLifecycle(listOf())
|
||||
|
||||
val messageInput = rememberSaveable(
|
||||
key = contactKey, // Use contactKey as key so state resets when switching conversations
|
||||
stateSaver = TextFieldValue.Saver
|
||||
) {
|
||||
mutableStateOf(TextFieldValue(message))
|
||||
}
|
||||
val messageInput = rememberTextFieldState(message)
|
||||
|
||||
var replyingTo by remember { mutableStateOf<Message?>(null) }
|
||||
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
|
|
@ -164,8 +156,16 @@ internal fun MessageScreen(
|
|||
onDismiss = { showDeleteDialog = false }
|
||||
)
|
||||
}
|
||||
var sharedContact: Node? by remember { mutableStateOf(null) }
|
||||
if (sharedContact != null) {
|
||||
SharedContactDialog(
|
||||
contact = sharedContact,
|
||||
onDismiss = { sharedContact = null }
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
if (inSelectionMode) {
|
||||
ActionModeTopBar(selectedIds.value) { action ->
|
||||
|
|
@ -198,96 +198,20 @@ internal fun MessageScreen(
|
|||
MessageTopBar(title, channelIndex, mismatchKey, onNavigateBack)
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
val isConnected = connState.isConnected()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, end = 8.dp, bottom = 4.dp),
|
||||
) {
|
||||
QuickChatRow(isConnected, quickChat) { action ->
|
||||
if (action.mode == QuickChatAction.Mode.Append) {
|
||||
val originalText = messageInput.value.text
|
||||
if (!originalText.contains(action.message)) {
|
||||
val needsSpace =
|
||||
!originalText.endsWith(' ') && originalText.isNotEmpty()
|
||||
val newText = buildString {
|
||||
append(originalText)
|
||||
if (needsSpace) append(' ')
|
||||
append(action.message)
|
||||
}.take(MESSAGE_CHARACTER_LIMIT)
|
||||
messageInput.value = TextFieldValue(newText, TextRange(newText.length))
|
||||
}
|
||||
} else {
|
||||
viewModel.sendMessage(action.message, contactKey)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = replyingTo != null) {
|
||||
val fromLocal = replyingTo?.node?.user?.id == DataPacket.ID_LOCAL
|
||||
|
||||
val replyingToNode = if (fromLocal) {
|
||||
viewModel.ourNodeInfo.value
|
||||
} else {
|
||||
replyingTo?.node
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Default.Reply,
|
||||
contentDescription = stringResource(R.string.reply)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Replying to ${replyingToNode?.user?.shortName ?: stringResource(R.string.unknown)}",
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
Text(
|
||||
replyingTo?.text?.take(SNIPPET_CHARACTER_LIMIT)
|
||||
?.let { if (it.length == SNIPPET_CHARACTER_LIMIT) "$it…" else it } ?: "", // Snippet
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
replyingTo = null
|
||||
}) { // ViewModel function to set replyingToMessageState to null
|
||||
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
}
|
||||
TextInput(isConnected, messageInput) { message ->
|
||||
replyingTo?.let {
|
||||
viewModel.sendMessage(message, contactKey, it.packetId)
|
||||
replyingTo = null
|
||||
} ?: viewModel.sendMessage(message, contactKey)
|
||||
// Clear the text input after sending the message and updating all state
|
||||
messageInput.value = TextFieldValue("")
|
||||
}
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
if (messages.isNotEmpty()) {
|
||||
var sharedContact: Node? by remember { mutableStateOf(null) }
|
||||
if (sharedContact != null) {
|
||||
SharedContactDialog(
|
||||
contact = sharedContact,
|
||||
onDismiss = { sharedContact = null }
|
||||
)
|
||||
}
|
||||
|
||||
Column(Modifier.padding(padding)) {
|
||||
MessageList(
|
||||
modifier = Modifier.padding(padding),
|
||||
modifier = Modifier.weight(1f, fill = true),
|
||||
messages = messages,
|
||||
selectedIds = selectedIds,
|
||||
onUnreadChanged = { viewModel.clearUnreadCount(contactKey, it) },
|
||||
onSendReaction = { emoji, id -> viewModel.sendReaction(emoji, id, contactKey) },
|
||||
onSendReaction = { emoji, id ->
|
||||
viewModel.sendReaction(
|
||||
emoji,
|
||||
id,
|
||||
contactKey
|
||||
)
|
||||
},
|
||||
viewModel = viewModel,
|
||||
contactKey = contactKey,
|
||||
onReply = { replyingTo = it },
|
||||
|
|
@ -307,10 +231,108 @@ internal fun MessageScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
QuickChatRow(
|
||||
enabled = isConnected,
|
||||
actions = quickChat,
|
||||
onClick = { action ->
|
||||
handleQuickChatAction(action, messageInput, viewModel, contactKey)
|
||||
}
|
||||
)
|
||||
ReplySnippet(replyingTo, { replyingTo = null }, ourNode)
|
||||
TextInput(isConnected, messageInput) {
|
||||
val message = messageInput.text.toString().trim()
|
||||
if (message.isNotEmpty()) {
|
||||
replyingTo?.let {
|
||||
viewModel.sendMessage(message, contactKey, it.packetId)
|
||||
replyingTo = null
|
||||
} ?: viewModel.sendMessage(message, contactKey)
|
||||
// Clear the text input after sending the message and updating all state
|
||||
messageInput.clearText()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReplySnippet(
|
||||
originalMessage: Message?,
|
||||
clearReply: () -> Unit = {},
|
||||
ourNode: Node?
|
||||
) {
|
||||
AnimatedVisibility(visible = originalMessage != null) {
|
||||
val fromLocal = originalMessage?.node?.user?.id == DataPacket.ID_LOCAL
|
||||
|
||||
val replyingToNode = if (fromLocal) {
|
||||
ourNode
|
||||
} else {
|
||||
originalMessage?.node
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
IconButton(
|
||||
enabled = false,
|
||||
onClick = {}
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Default.Reply,
|
||||
contentDescription = stringResource(R.string.reply)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
"Replying to ${replyingToNode?.user?.shortName ?: stringResource(R.string.unknown)}",
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f, fill = true),
|
||||
text = originalMessage?.text?.take(SNIPPET_CHARACTER_LIMIT)
|
||||
?.let { if (it.length == SNIPPET_CHARACTER_LIMIT) "$it…" else it }
|
||||
?: "", // Snippet
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
IconButton(
|
||||
onClick = clearReply
|
||||
) { // ViewModel function to set replyingToMessageState to null
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.cancel)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleQuickChatAction(
|
||||
action: QuickChatAction,
|
||||
messageInput: TextFieldState,
|
||||
viewModel: UIViewModel,
|
||||
contactKey: String
|
||||
) {
|
||||
if (action.mode == QuickChatAction.Mode.Append) {
|
||||
val originalText = messageInput.text
|
||||
if (!originalText.contains(action.message)) {
|
||||
val needsSpace =
|
||||
!originalText.endsWith(' ') && originalText.isNotEmpty()
|
||||
val newText = buildString {
|
||||
append(originalText)
|
||||
if (needsSpace) append(' ')
|
||||
append(action.message)
|
||||
}.take(MESSAGE_CHARACTER_LIMIT)
|
||||
messageInput.setTextAndPlaceCursorAtEnd(newText)
|
||||
}
|
||||
} else {
|
||||
viewModel.sendMessage(action.message, contactKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeleteMessageDialog(
|
||||
size: Int,
|
||||
|
|
@ -412,9 +434,9 @@ private fun MessageTopBar(
|
|||
|
||||
@Composable
|
||||
private fun QuickChatRow(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean,
|
||||
actions: List<QuickChatAction>,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (QuickChatAction) -> Unit
|
||||
) {
|
||||
val alertAction = QuickChatAction(
|
||||
|
|
@ -441,68 +463,58 @@ private fun QuickChatRow(
|
|||
}
|
||||
}
|
||||
|
||||
private const val ROUNDED_CORNER_PERCENT = 100
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun TextInput(
|
||||
enabled: Boolean,
|
||||
message: MutableState<TextFieldValue>,
|
||||
message: TextFieldState,
|
||||
modifier: Modifier = Modifier,
|
||||
maxSize: Int = MESSAGE_CHARACTER_LIMIT,
|
||||
onClick: (String) -> Unit = {}
|
||||
) = Column(modifier) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
onSendMessage: () -> Unit = {}
|
||||
) {
|
||||
val isOverLimit = message.text.length > maxSize
|
||||
val isValid = !isOverLimit && message.text.isNotEmpty()
|
||||
OutlinedTextField(
|
||||
value = message.value,
|
||||
onValueChange = {
|
||||
if (it.text.toByteArray().size <= maxSize) {
|
||||
message.value = it
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusEvent { isFocused = it.isFocused }
|
||||
.onKeyEvent { event ->
|
||||
if (event.type == KeyEventType.KeyDown &&
|
||||
event.key == Key.Enter &&
|
||||
event.isAltPressed
|
||||
) {
|
||||
val str = message.value.text.trim()
|
||||
if (str.isNotEmpty()) {
|
||||
onClick(str)
|
||||
}
|
||||
true // Consume the event
|
||||
} else {
|
||||
false // Do not consume other key events
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
state = message,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
label = { Text(stringResource(R.string.send_text)) },
|
||||
enabled = enabled,
|
||||
placeholder = { Text(stringResource(id = R.string.send_text)) },
|
||||
shape = RoundedCornerShape(ROUNDED_CORNER_PERCENT),
|
||||
isError = isOverLimit,
|
||||
placeholder = { Text(stringResource(R.string.send_text)) },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onSend = {
|
||||
val str = message.value.text.trim()
|
||||
if (str.isNotEmpty()) {
|
||||
onClick(str)
|
||||
}
|
||||
onKeyboardAction = {
|
||||
if (isValid) {
|
||||
onSendMessage()
|
||||
}
|
||||
),
|
||||
maxLines = 3,
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = "${message.text.length}/$maxSize",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (isOverLimit) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
},
|
||||
inputTransformation = {
|
||||
if (this.length > maxSize) {
|
||||
this.replace(maxSize, this.length, "")
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
val str = message.value.text.trim()
|
||||
if (str.isNotEmpty()) {
|
||||
onClick(str)
|
||||
focusManager.clearFocus()
|
||||
if (isValid) {
|
||||
onSendMessage()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.size(48.dp),
|
||||
enabled = enabled,
|
||||
enabled = enabled && isValid,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.Send,
|
||||
|
|
@ -511,15 +523,6 @@ private fun TextInput(
|
|||
}
|
||||
}
|
||||
)
|
||||
if (isFocused) {
|
||||
Text(
|
||||
text = "${message.value.text.toByteArray().size}/$maxSize",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(top = 4.dp, end = 72.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
|
|
@ -530,12 +533,12 @@ private fun TextInputPreview() {
|
|||
Column {
|
||||
TextInput(
|
||||
enabled = true,
|
||||
message = remember { mutableStateOf(TextFieldValue("")) },
|
||||
message = rememberTextFieldState("Hello"),
|
||||
)
|
||||
Spacer(Modifier.size(16.dp))
|
||||
TextInput(
|
||||
enabled = true,
|
||||
message = remember { mutableStateOf(TextFieldValue("Hello")) },
|
||||
message = rememberTextFieldState("Hello"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FormatQuote
|
||||
import androidx.compose.material3.Card
|
||||
|
|
@ -122,7 +120,7 @@ internal fun MessageItem(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
ReplyingTo(
|
||||
OriginalMessageSnippet(
|
||||
message = message,
|
||||
ourNode = ourNode,
|
||||
cardColors = cardColors,
|
||||
|
|
@ -214,7 +212,7 @@ internal fun MessageItem(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ReplyingTo(
|
||||
private fun OriginalMessageSnippet(
|
||||
message: Message,
|
||||
ourNode: Node,
|
||||
cardColors: CardColors = CardDefaults.cardColors(),
|
||||
|
|
@ -233,32 +231,27 @@ private fun ReplyingTo(
|
|||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.FormatQuote,
|
||||
contentDescription = stringResource(R.string.reply), // Add to strings.xml
|
||||
modifier = Modifier.size(14.dp), // Smaller icon
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "${originalMessageNode.user.shortName} ${
|
||||
originalMessageNode.user.longName
|
||||
?: stringResource(R.string.unknown_username)
|
||||
}",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = originalMessage.text, // Should not be null if isAReply is true
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1, // Keep snippet brief
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "${originalMessageNode.user.shortName}",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f, fill = true),
|
||||
text = originalMessage.text, // Should not be null if isAReply is true
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1, // Keep snippet brief
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue