feat: Enhance message display with inline timestamps and status for outgoing messages in chat
This commit is contained in:
@@ -1591,10 +1591,38 @@ private fun MessageBubble(
|
|||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
AppleEmojiText(text = message.text, color = textColor, fontSize = 16.sp)
|
// 🔥 Telegram-style: текст + время inline
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
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(
|
Row(
|
||||||
modifier = Modifier.align(Alignment.End),
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(top = 2.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -1602,7 +1630,6 @@ private fun MessageBubble(
|
|||||||
color = timeColor,
|
color = timeColor,
|
||||||
fontSize = 11.sp
|
fontSize = 11.sp
|
||||||
)
|
)
|
||||||
if (message.isOutgoing) {
|
|
||||||
Spacer(modifier = Modifier.width(3.dp))
|
Spacer(modifier = Modifier.width(3.dp))
|
||||||
AnimatedMessageStatus(
|
AnimatedMessageStatus(
|
||||||
status = message.status,
|
status = message.status,
|
||||||
@@ -1616,6 +1643,7 @@ private fun MessageBubble(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // End of swipe Box wrapper
|
} // End of swipe Box wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2273,30 +2301,20 @@ fun MessageSkeletonList(
|
|||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
modifier: Modifier = Modifier
|
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 infiniteTransition = rememberInfiniteTransition(label = "shimmer")
|
||||||
val shimmerProgress by infiniteTransition.animateFloat(
|
val shimmerAlpha by infiniteTransition.animateFloat(
|
||||||
initialValue = 0f,
|
initialValue = 0.4f,
|
||||||
targetValue = 1f,
|
targetValue = 0.8f,
|
||||||
animationSpec = infiniteRepeatable(
|
animationSpec = infiniteRepeatable(
|
||||||
animation = tween(1200, easing = LinearEasing),
|
animation = tween(800, easing = FastOutSlowInEasing),
|
||||||
repeatMode = RepeatMode.Restart
|
repeatMode = RepeatMode.Reverse
|
||||||
),
|
),
|
||||||
label = "shimmer"
|
label = "shimmerAlpha"
|
||||||
)
|
|
||||||
|
|
||||||
// Градиент для shimmer эффекта
|
|
||||||
val shimmerBrush = Brush.horizontalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
shimmerColor,
|
|
||||||
shimmerHighlight,
|
|
||||||
shimmerColor
|
|
||||||
),
|
|
||||||
startX = shimmerProgress * 1000f - 500f,
|
|
||||||
endX = shimmerProgress * 1000f + 500f
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 🔥 Box с выравниванием внизу - как настоящий чат
|
// 🔥 Box с выравниванием внизу - как настоящий чат
|
||||||
@@ -2306,28 +2324,28 @@ fun MessageSkeletonList(
|
|||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
.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 = true, widthFraction = 0.45f, bubbleColor = outgoingBubbleColor, alpha = shimmerAlpha)
|
||||||
SkeletonBubble(isOutgoing = false, widthFraction = 0.35f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
|
SkeletonBubble(isOutgoing = false, widthFraction = 0.55f, bubbleColor = incomingBubbleColor, alpha = shimmerAlpha)
|
||||||
SkeletonBubble(isOutgoing = true, widthFraction = 0.30f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
|
SkeletonBubble(isOutgoing = true, widthFraction = 0.35f, bubbleColor = outgoingBubbleColor, alpha = shimmerAlpha)
|
||||||
SkeletonBubble(isOutgoing = false, widthFraction = 0.28f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
|
SkeletonBubble(isOutgoing = false, widthFraction = 0.50f, bubbleColor = incomingBubbleColor, alpha = shimmerAlpha)
|
||||||
SkeletonBubble(isOutgoing = true, widthFraction = 0.40f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
|
SkeletonBubble(isOutgoing = true, widthFraction = 0.60f, bubbleColor = outgoingBubbleColor, alpha = shimmerAlpha)
|
||||||
SkeletonBubble(isOutgoing = false, widthFraction = 0.32f, brush = shimmerBrush, isDarkTheme = isDarkTheme)
|
SkeletonBubble(isOutgoing = false, widthFraction = 0.40f, bubbleColor = incomingBubbleColor, alpha = shimmerAlpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Пузырёк-скелетон сообщения (как настоящий bubble)
|
* Пузырёк-скелетон сообщения (толстый как настоящий с текстом)
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun SkeletonBubble(
|
private fun SkeletonBubble(
|
||||||
isOutgoing: Boolean,
|
isOutgoing: Boolean,
|
||||||
widthFraction: Float,
|
widthFraction: Float,
|
||||||
brush: Brush,
|
bubbleColor: Color,
|
||||||
isDarkTheme: Boolean
|
alpha: Float
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -2336,14 +2354,15 @@ private fun SkeletonBubble(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(widthFraction)
|
.fillMaxWidth(widthFraction)
|
||||||
.height(34.dp) // Фиксированная высота как у реального пузырька
|
.defaultMinSize(minHeight = 44.dp) // Минимум как пузырёк с текстом
|
||||||
.clip(RoundedCornerShape(
|
.clip(RoundedCornerShape(
|
||||||
topStart = 18.dp,
|
topStart = 18.dp,
|
||||||
topEnd = 18.dp,
|
topEnd = 18.dp,
|
||||||
bottomStart = if (isOutgoing) 18.dp else 4.dp,
|
bottomStart = if (isOutgoing) 18.dp else 6.dp,
|
||||||
bottomEnd = if (isOutgoing) 4.dp else 18.dp
|
bottomEnd = if (isOutgoing) 6.dp else 18.dp
|
||||||
))
|
))
|
||||||
.background(brush)
|
.background(bubbleColor.copy(alpha = alpha))
|
||||||
|
.padding(horizontal = 14.dp, vertical = 10.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -815,10 +815,12 @@ fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, onClick: () -> Unit)
|
|||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
// Avatar container with online indicator
|
||||||
|
Box(modifier = Modifier.size(56.dp)) {
|
||||||
// Avatar
|
// Avatar
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.size(56.dp)
|
Modifier.fillMaxSize()
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(avatarColors.backgroundColor),
|
.background(avatarColors.backgroundColor),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -829,14 +831,14 @@ fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, onClick: () -> Unit)
|
|||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Online indicator
|
// Online indicator
|
||||||
if (dialog.isOnline == 1) {
|
if (dialog.isOnline == 1) {
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.size(14.dp)
|
Modifier.size(16.dp)
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.offset(x = (-2).dp, y = (-2).dp)
|
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(
|
.background(
|
||||||
if (isDarkTheme) Color(0xFF1A1A1A)
|
if (isDarkTheme) Color(0xFF1A1A1A)
|
||||||
|
|||||||
Reference in New Issue
Block a user