feat: Add timestamp and message status to MessageBubble and ImageAttachment components
This commit is contained in:
@@ -3,13 +3,14 @@ package com.rosetta.messenger.ui.chats.components
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -23,18 +24,23 @@ 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.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import compose.icons.TablerIcons
|
||||||
|
import compose.icons.tablericons.*
|
||||||
import com.rosetta.messenger.crypto.CryptoManager
|
import com.rosetta.messenger.crypto.CryptoManager
|
||||||
import com.rosetta.messenger.crypto.MessageCrypto
|
import com.rosetta.messenger.crypto.MessageCrypto
|
||||||
import com.rosetta.messenger.network.AttachmentType
|
import com.rosetta.messenger.network.AttachmentType
|
||||||
import com.rosetta.messenger.network.MessageAttachment
|
import com.rosetta.messenger.network.MessageAttachment
|
||||||
import com.rosetta.messenger.network.TransportManager
|
import com.rosetta.messenger.network.TransportManager
|
||||||
import com.rosetta.messenger.repository.AvatarRepository
|
import com.rosetta.messenger.repository.AvatarRepository
|
||||||
|
import com.rosetta.messenger.ui.chats.models.MessageStatus
|
||||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
import com.rosetta.messenger.utils.AvatarFileManager
|
import com.rosetta.messenger.utils.AvatarFileManager
|
||||||
import com.vanniktech.blurhash.BlurHash
|
import com.vanniktech.blurhash.BlurHash
|
||||||
@@ -67,6 +73,8 @@ fun MessageAttachments(
|
|||||||
isOutgoing: Boolean,
|
isOutgoing: Boolean,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
senderPublicKey: String,
|
senderPublicKey: String,
|
||||||
|
timestamp: java.util.Date,
|
||||||
|
messageStatus: MessageStatus = MessageStatus.READ,
|
||||||
avatarRepository: AvatarRepository? = null,
|
avatarRepository: AvatarRepository? = null,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@@ -84,7 +92,9 @@ fun MessageAttachments(
|
|||||||
chachaKey = chachaKey,
|
chachaKey = chachaKey,
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
isOutgoing = isOutgoing,
|
isOutgoing = isOutgoing,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme,
|
||||||
|
timestamp = timestamp,
|
||||||
|
messageStatus = messageStatus
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AttachmentType.FILE -> {
|
AttachmentType.FILE -> {
|
||||||
@@ -124,7 +134,9 @@ fun ImageAttachment(
|
|||||||
chachaKey: String,
|
chachaKey: String,
|
||||||
privateKey: String,
|
privateKey: String,
|
||||||
isOutgoing: Boolean,
|
isOutgoing: Boolean,
|
||||||
isDarkTheme: Boolean
|
isDarkTheme: Boolean,
|
||||||
|
timestamp: java.util.Date,
|
||||||
|
messageStatus: MessageStatus = MessageStatus.READ
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -224,12 +236,20 @@ fun ImageAttachment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Telegram-style image с blurhash placeholder
|
// Telegram-style image с blurhash placeholder и тонким бордером
|
||||||
|
val timeFormat = remember { java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()) }
|
||||||
|
val borderColor = if (isOutgoing) {
|
||||||
|
Color.White.copy(alpha = 0.15f)
|
||||||
|
} else {
|
||||||
|
if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f)
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(min = 180.dp, max = 260.dp)
|
.widthIn(min = 180.dp, max = 260.dp)
|
||||||
.heightIn(min = 140.dp, max = 300.dp)
|
.heightIn(min = 140.dp, max = 300.dp)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(Color.Transparent)
|
||||||
.clickable {
|
.clickable {
|
||||||
when (downloadStatus) {
|
when (downloadStatus) {
|
||||||
DownloadStatus.NOT_DOWNLOADED -> download()
|
DownloadStatus.NOT_DOWNLOADED -> download()
|
||||||
@@ -272,6 +292,81 @@ fun ImageAttachment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Тонкий бордер как в Telegram (просто через border modifier)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = borderColor,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Время в правом нижнем углу (только если изображение загружено)
|
||||||
|
if (downloadStatus == DownloadStatus.DOWNLOADED) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(8.dp)
|
||||||
|
.background(
|
||||||
|
Color.Black.copy(alpha = 0.5f),
|
||||||
|
shape = RoundedCornerShape(10.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 6.dp, vertical = 3.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = timeFormat.format(timestamp),
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
if (isOutgoing) {
|
||||||
|
// Статус доставки для исходящих
|
||||||
|
when (messageStatus) {
|
||||||
|
MessageStatus.SENDING -> {
|
||||||
|
Icon(
|
||||||
|
compose.icons.TablerIcons.Clock,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.size(14.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MessageStatus.SENT -> {
|
||||||
|
Icon(
|
||||||
|
compose.icons.TablerIcons.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.size(14.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MessageStatus.DELIVERED -> {
|
||||||
|
Icon(
|
||||||
|
compose.icons.TablerIcons.Checks,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.size(14.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MessageStatus.READ -> {
|
||||||
|
Icon(
|
||||||
|
compose.icons.TablerIcons.Checks,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color(0xFF4FC3F7),
|
||||||
|
modifier = Modifier.size(14.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Оверлей для статуса скачивания
|
// Оверлей для статуса скачивания
|
||||||
if (downloadStatus != DownloadStatus.DOWNLOADED) {
|
if (downloadStatus != DownloadStatus.DOWNLOADED) {
|
||||||
Box(
|
Box(
|
||||||
|
|||||||
@@ -345,7 +345,9 @@ fun MessageBubble(
|
|||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
isOutgoing = message.isOutgoing,
|
isOutgoing = message.isOutgoing,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
senderPublicKey = senderPublicKey
|
senderPublicKey = senderPublicKey,
|
||||||
|
timestamp = message.timestamp,
|
||||||
|
messageStatus = message.status
|
||||||
)
|
)
|
||||||
if (message.text.isNotEmpty()) {
|
if (message.text.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|||||||
Reference in New Issue
Block a user