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 -> val newUiMessages = messages.mapIndexed { index, message ->
message.asPresentation( message.asPresentation(
resourceProvider = resourceProvider, resourceProvider = resourceProvider,
showName = false, showName = true,
prevMessage = messages.getOrNull(index + 1), prevMessage = messages.getOrNull(index + 1),
nextMessage = messages.getOrNull(index - 1), nextMessage = messages.getOrNull(index - 1),
showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages, showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages,
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.common.model.UiImage
import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.ui.util.ImmutableList
sealed class UiItem( sealed class UiItem(
open val id: Long, open val id: Long,
@@ -31,7 +32,7 @@ sealed class UiItem(
val isSelected: Boolean, val isSelected: Boolean,
val isPinned: Boolean, val isPinned: Boolean,
val isImportant: Boolean, val isImportant: Boolean,
val attachments: List<VkAttachment>?, val attachments: ImmutableList<VkAttachment>?,
val replyCmId: Long?, val replyCmId: Long?,
val replyTitle: String?, val replyTitle: String?,
val replySummary: String? val replySummary: String?
@@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -58,7 +59,8 @@ fun MessageBubble(
replySummary: String? = null, replySummary: String? = null,
onClick: (VkAttachment) -> Unit = {}, onClick: (VkAttachment) -> Unit = {},
onLongClick: (VkAttachment) -> Unit = {}, onLongClick: (VkAttachment) -> Unit = {},
onReplyClick: () -> Unit = {} onReplyClick: () -> Unit = {},
onBubbleWidthChange: (Int) -> Unit = {},
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
@@ -149,6 +151,9 @@ fun MessageBubble(
.onGloballyPositioned { .onGloballyPositioned {
bubbleContainerWidth = it.size.width bubbleContainerWidth = it.size.width
} }
.onSizeChanged {
onBubbleWidthChange(it.width)
}
.widthIn(min = if (shouldFill) attachmentsContainerWidth.dp else 56.dp) .widthIn(min = if (shouldFill) attachmentsContainerWidth.dp else 56.dp)
.clip( .clip(
RoundedCornerShape( RoundedCornerShape(
@@ -20,12 +20,16 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntOffset 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.messageshistory.model.UiItem
import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.ui.R import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Composable @Composable
@@ -49,10 +52,16 @@ fun IncomingMessageBubble(
onLongClick: (VkAttachment) -> Unit = {}, onLongClick: (VkAttachment) -> Unit = {},
onReplyClick: () -> Unit = {} onReplyClick: () -> Unit = {}
) { ) {
val density = LocalDensity.current
val currentOnClick by rememberUpdatedState(onClick) val currentOnClick by rememberUpdatedState(onClick)
val currentOnLongClick by rememberUpdatedState(onLongClick) val currentOnLongClick by rememberUpdatedState(onLongClick)
val currentOnReplyClick by rememberUpdatedState(onReplyClick) val currentOnReplyClick by rememberUpdatedState(onReplyClick)
var bubbleContainerWidth by remember {
mutableStateOf(0.dp)
}
Row( Row(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@@ -103,9 +112,12 @@ fun IncomingMessageBubble(
Text( Text(
modifier = Modifier modifier = Modifier
.padding(start = 12.dp) .padding(start = 12.dp)
.widthIn(max = 140.dp), .widthIn(
max = (bubbleContainerWidth.takeIf { it > 0.dp }
?: 140.dp) - 24.dp
),
text = message.name, text = message.name,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@@ -123,14 +135,16 @@ fun IncomingMessageBubble(
isPinned = message.isPinned, isPinned = message.isPinned,
isImportant = message.isImportant, isImportant = message.isImportant,
isSelected = message.isSelected, isSelected = message.isSelected,
attachments = message.attachments?.toImmutableList(), attachments = message.attachments,
replyTitle = message.replyTitle, replyTitle = message.replyTitle,
replySummary = message.replySummary, replySummary = message.replySummary,
onClick = currentOnClick, onClick = currentOnClick,
onLongClick = currentOnLongClick, onLongClick = currentOnLongClick,
onReplyClick = currentOnReplyClick onReplyClick = currentOnReplyClick,
onBubbleWidthChange = {
bubbleContainerWidth = with(density) { it.toDp() }
}
) )
} }
} }
Spacer(modifier = Modifier.fillMaxWidth(0.25f)) Spacer(modifier = Modifier.fillMaxWidth(0.25f))
@@ -80,7 +80,7 @@ fun OutgoingMessageBubble(
isPinned = message.isPinned, isPinned = message.isPinned,
isImportant = message.isImportant, isImportant = message.isImportant,
isSelected = message.isSelected, isSelected = message.isSelected,
attachments = message.attachments?.toImmutableList(), attachments = message.attachments,
replyTitle = message.replyTitle, replyTitle = message.replyTitle,
replySummary = message.replySummary, replySummary = message.replySummary,
onClick = currentOnClick, 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.VkConversation
import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkMessage
import dev.meloda.fast.ui.R import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@@ -156,7 +157,7 @@ fun VkMessage.asPresentation(
isSelected = isSelected, isSelected = isSelected,
isPinned = isPinned, isPinned = isPinned,
isImportant = isImportant, isImportant = isImportant,
attachments = attachments?.ifEmpty { null }, attachments = attachments?.ifEmpty { null }?.toImmutableList(),
replyCmId = replyMessage?.cmId, replyCmId = replyMessage?.cmId,
replyTitle = extractReplyTitle(), replyTitle = extractReplyTitle(),
replySummary = extractReplySummary() replySummary = extractReplySummary()