Fix: Ensure sender's name truncates correctly in incoming messages

This commit resolves an issue where the sender's name in incoming message bubbles would not truncate properly, potentially breaking the layout. The fix ensures the name text correctly adapts to the width of the message bubble.

Additionally, this change introduces `ImmutableList` for message attachments to improve performance and refactors where the conversion to `ImmutableList` happens, moving it into the `MessageMapper`.

Key changes:
- The `MessageBubble` now reports its width, allowing the sender's name `Text` to be constrained correctly.
- Sender's name now uses `labelMedium` typography.
- Enabled showing the sender's name by default in `MessagesHistoryViewModelImpl`.
- Changed `UiItem.Message.attachments` from `List` to `ImmutableList` for better Compose performance.
- Moved the `toImmutableList()` conversion for attachments into the `MessageMapper`.
This commit is contained in:
2025-12-15 22:58:50 +03:00
parent 478639e427
commit 8839015249
6 changed files with 32 additions and 11 deletions
@@ -1231,7 +1231,7 @@ class MessagesHistoryViewModelImpl(
val newUiMessages = messages.mapIndexed { index, message ->
message.asPresentation(
resourceProvider = resourceProvider,
showName = false,
showName = true,
prevMessage = messages.getOrNull(index + 1),
nextMessage = messages.getOrNull(index - 1),
showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages,
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.text.AnnotatedString
import dev.meloda.fast.common.model.UiImage
import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.ui.util.ImmutableList
sealed class UiItem(
open val id: Long,
@@ -31,7 +32,7 @@ sealed class UiItem(
val isSelected: Boolean,
val isPinned: Boolean,
val isImportant: Boolean,
val attachments: List<VkAttachment>?,
val attachments: ImmutableList<VkAttachment>?,
val replyCmId: Long?,
val replyTitle: String?,
val replySummary: String?
@@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
@@ -58,7 +59,8 @@ fun MessageBubble(
replySummary: String? = null,
onClick: (VkAttachment) -> Unit = {},
onLongClick: (VkAttachment) -> Unit = {},
onReplyClick: () -> Unit = {}
onReplyClick: () -> Unit = {},
onBubbleWidthChange: (Int) -> Unit = {},
) {
val density = LocalDensity.current
@@ -149,6 +151,9 @@ fun MessageBubble(
.onGloballyPositioned {
bubbleContainerWidth = it.size.width
}
.onSizeChanged {
onBubbleWidthChange(it.width)
}
.widthIn(min = if (shouldFill) attachmentsContainerWidth.dp else 56.dp)
.clip(
RoundedCornerShape(
@@ -20,12 +20,16 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntOffset
@@ -36,7 +40,6 @@ import com.conena.nanokt.android.content.dpInPx
import dev.meloda.fast.messageshistory.model.UiItem
import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
import kotlin.math.roundToInt
@Composable
@@ -49,10 +52,16 @@ fun IncomingMessageBubble(
onLongClick: (VkAttachment) -> Unit = {},
onReplyClick: () -> Unit = {}
) {
val density = LocalDensity.current
val currentOnClick by rememberUpdatedState(onClick)
val currentOnLongClick by rememberUpdatedState(onLongClick)
val currentOnReplyClick by rememberUpdatedState(onReplyClick)
var bubbleContainerWidth by remember {
mutableStateOf(0.dp)
}
Row(
modifier = modifier
.fillMaxWidth()
@@ -103,9 +112,12 @@ fun IncomingMessageBubble(
Text(
modifier = Modifier
.padding(start = 12.dp)
.widthIn(max = 140.dp),
.widthIn(
max = (bubbleContainerWidth.takeIf { it > 0.dp }
?: 140.dp) - 24.dp
),
text = message.name,
style = MaterialTheme.typography.bodySmall,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@@ -123,14 +135,16 @@ fun IncomingMessageBubble(
isPinned = message.isPinned,
isImportant = message.isImportant,
isSelected = message.isSelected,
attachments = message.attachments?.toImmutableList(),
attachments = message.attachments,
replyTitle = message.replyTitle,
replySummary = message.replySummary,
onClick = currentOnClick,
onLongClick = currentOnLongClick,
onReplyClick = currentOnReplyClick
onReplyClick = currentOnReplyClick,
onBubbleWidthChange = {
bubbleContainerWidth = with(density) { it.toDp() }
}
)
}
}
Spacer(modifier = Modifier.fillMaxWidth(0.25f))
@@ -80,7 +80,7 @@ fun OutgoingMessageBubble(
isPinned = message.isPinned,
isImportant = message.isImportant,
isSelected = message.isSelected,
attachments = message.attachments?.toImmutableList(),
attachments = message.attachments,
replyTitle = message.replyTitle,
replySummary = message.replySummary,
onClick = currentOnClick,
@@ -24,6 +24,7 @@ import dev.meloda.fast.model.api.domain.FormatDataType
import dev.meloda.fast.model.api.domain.VkConversation
import dev.meloda.fast.model.api.domain.VkMessage
import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
import java.text.SimpleDateFormat
import java.util.Locale
@@ -156,7 +157,7 @@ fun VkMessage.asPresentation(
isSelected = isSelected,
isPinned = isPinned,
isImportant = isImportant,
attachments = attachments?.ifEmpty { null },
attachments = attachments?.ifEmpty { null }?.toImmutableList(),
replyCmId = replyMessage?.cmId,
replyTitle = extractReplyTitle(),
replySummary = extractReplySummary()