feat: Enhance message display with inline timestamps and status for outgoing messages in chat

This commit is contained in:
k1ngsterr1
2026-01-13 19:19:44 +05:00
parent b60738ce55
commit a7576865ef
2 changed files with 90 additions and 69 deletions

View File

@@ -1591,10 +1591,38 @@ private fun MessageBubble(
Spacer(modifier = Modifier.height(6.dp))
}
AppleEmojiText(text = message.text, color = textColor, fontSize = 16.sp)
Spacer(modifier = Modifier.height(2.dp))
// 🔥 Telegram-style: текст + время inline
if (!message.isOutgoing) {
// Входящие сообщения - время справа inline с текстом
Box {
AppleEmojiText(
text = message.text + " ", // Пробелы для места под время
color = textColor,
fontSize = 16.sp
)
// Время в правом нижнем углу
Text(
text = timeFormat.format(message.timestamp),
color = timeColor,
fontSize = 11.sp,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(top = 2.dp)
)
}
} else {
// Исходящие сообщения - время + статус справа внизу
Box {
AppleEmojiText(
text = message.text + " ", // Пробелы для места под время и статус
color = textColor,
fontSize = 16.sp
)
// Время и статус в правом нижнем углу
Row(
modifier = Modifier.align(Alignment.End),
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(top = 2.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
@@ -1602,7 +1630,6 @@ private fun MessageBubble(
color = timeColor,
fontSize = 11.sp
)
if (message.isOutgoing) {
Spacer(modifier = Modifier.width(3.dp))
AnimatedMessageStatus(
status = message.status,
@@ -1616,6 +1643,7 @@ private fun MessageBubble(
}
}
}
}
} // End of swipe Box wrapper
}
@@ -2273,30 +2301,20 @@ fun MessageSkeletonList(
isDarkTheme: Boolean,
modifier: Modifier = Modifier
) {
val shimmerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
val shimmerHighlight = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFF5F5F5)
// Цвета пузырьков как у настоящих сообщений
val outgoingBubbleColor = if (isDarkTheme) Color(0xFF3B82F6).copy(alpha = 0.3f) else Color(0xFF3B82F6).copy(alpha = 0.2f)
val incomingBubbleColor = if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE5E5EA)
// Анимация shimmer
// Shimmer анимация
val infiniteTransition = rememberInfiniteTransition(label = "shimmer")
val shimmerProgress by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
val shimmerAlpha by infiniteTransition.animateFloat(
initialValue = 0.4f,
targetValue = 0.8f,
animationSpec = infiniteRepeatable(
animation = tween(1200, easing = LinearEasing),
repeatMode = RepeatMode.Restart
animation = tween(800, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "shimmer"
)
// Градиент для shimmer эффекта
val shimmerBrush = Brush.horizontalGradient(
colors = listOf(
shimmerColor,
shimmerHighlight,
shimmerColor
),
startX = shimmerProgress * 1000f - 500f,
endX = shimmerProgress * 1000f + 500f
label = "shimmerAlpha"
)
// 🔥 Box с выравниванием внизу - как настоящий чат
@@ -2306,28 +2324,28 @@ fun MessageSkeletonList(
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
// Паттерн сообщений снизу вверх (как в реальном чате) - короткие пузырьки
SkeletonBubble(isOutgoing = true, widthFraction = 0.25f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
SkeletonBubble(isOutgoing = false, widthFraction = 0.35f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
SkeletonBubble(isOutgoing = true, widthFraction = 0.30f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
SkeletonBubble(isOutgoing = false, widthFraction = 0.28f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
SkeletonBubble(isOutgoing = true, widthFraction = 0.40f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
SkeletonBubble(isOutgoing = false, widthFraction = 0.32f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
// Паттерн сообщений снизу вверх (как в реальном чате)
SkeletonBubble(isOutgoing = true, widthFraction = 0.45f, bubbleColor = outgoingBubbleColor, alpha = shimmerAlpha)
SkeletonBubble(isOutgoing = false, widthFraction = 0.55f, bubbleColor = incomingBubbleColor, alpha = shimmerAlpha)
SkeletonBubble(isOutgoing = true, widthFraction = 0.35f, bubbleColor = outgoingBubbleColor, alpha = shimmerAlpha)
SkeletonBubble(isOutgoing = false, widthFraction = 0.50f, bubbleColor = incomingBubbleColor, alpha = shimmerAlpha)
SkeletonBubble(isOutgoing = true, widthFraction = 0.60f, bubbleColor = outgoingBubbleColor, alpha = shimmerAlpha)
SkeletonBubble(isOutgoing = false, widthFraction = 0.40f, bubbleColor = incomingBubbleColor, alpha = shimmerAlpha)
}
}
}
/**
* Пузырёк-скелетон сообщения (как настоящий bubble)
* Пузырёк-скелетон сообщения (толстый как настоящий с текстом)
*/
@Composable
private fun SkeletonBubble(
isOutgoing: Boolean,
widthFraction: Float,
brush: Brush,
isDarkTheme: Boolean
bubbleColor: Color,
alpha: Float
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -2336,14 +2354,15 @@ private fun SkeletonBubble(
Box(
modifier = Modifier
.fillMaxWidth(widthFraction)
.height(34.dp) // Фиксированная высота как у реального пузырька
.defaultMinSize(minHeight = 44.dp) // Минимум как пузырёк с текстом
.clip(RoundedCornerShape(
topStart = 18.dp,
topEnd = 18.dp,
bottomStart = if (isOutgoing) 18.dp else 4.dp,
bottomEnd = if (isOutgoing) 4.dp else 18.dp
bottomStart = if (isOutgoing) 18.dp else 6.dp,
bottomEnd = if (isOutgoing) 6.dp else 18.dp
))
.background(brush)
.background(bubbleColor.copy(alpha = alpha))
.padding(horizontal = 14.dp, vertical = 10.dp)
)
}
}

View File

@@ -815,10 +815,12 @@ fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, onClick: () -> Unit)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Avatar container with online indicator
Box(modifier = Modifier.size(56.dp)) {
// Avatar
Box(
modifier =
Modifier.size(56.dp)
Modifier.fillMaxSize()
.clip(CircleShape)
.background(avatarColors.backgroundColor),
contentAlignment = Alignment.Center
@@ -829,14 +831,14 @@ fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, onClick: () -> Unit)
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp
)
}
// Online indicator
if (dialog.isOnline == 1) {
Box(
modifier =
Modifier.size(14.dp)
Modifier.size(16.dp)
.align(Alignment.BottomEnd)
.offset(x = (-2).dp, y = (-2).dp)
.clip(CircleShape)
.background(
if (isDarkTheme) Color(0xFF1A1A1A)