From 99a1156e9687d1822fb3d4b6ce65fe44765d87a5 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 26 Jan 2026 13:07:01 +0500 Subject: [PATCH] feat: Enhance FileAttachment component with timestamp and message status display --- .../chats/components/AttachmentComponents.kt | 243 +++++++++++------- 1 file changed, 155 insertions(+), 88 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/AttachmentComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/AttachmentComponents.kt index 8dde65d..05be4f1 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/AttachmentComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/AttachmentComponents.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -40,6 +41,7 @@ import com.rosetta.messenger.crypto.MessageCrypto import com.rosetta.messenger.network.AttachmentType import com.rosetta.messenger.network.MessageAttachment import com.rosetta.messenger.network.TransportManager +import com.rosetta.messenger.R import com.rosetta.messenger.repository.AvatarRepository import com.rosetta.messenger.ui.chats.models.MessageStatus import com.rosetta.messenger.ui.onboarding.PrimaryBlue @@ -104,7 +106,9 @@ fun MessageAttachments( chachaKey = chachaKey, privateKey = privateKey, isOutgoing = isOutgoing, - isDarkTheme = isDarkTheme + isDarkTheme = isDarkTheme, + timestamp = timestamp, + messageStatus = messageStatus ) } AttachmentType.AVATAR -> { @@ -490,7 +494,9 @@ fun FileAttachment( chachaKey: String, privateKey: String, isOutgoing: Boolean, - isDarkTheme: Boolean + isDarkTheme: Boolean, + timestamp: java.util.Date, + messageStatus: MessageStatus = MessageStatus.READ ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -556,98 +562,101 @@ fun FileAttachment( } // Telegram-style файл - как в desktop: без внутреннего фона, просто иконка + текст - Row( - modifier = Modifier - .fillMaxWidth() - .clickable(enabled = downloadStatus == DownloadStatus.NOT_DOWNLOADED || downloadStatus == DownloadStatus.ERROR) { - download() - } - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically + Box( + modifier = Modifier.fillMaxWidth() ) { - // File icon с индикатором прогресса - круглая иконка как в desktop - Box( - modifier = Modifier.size(40.dp), - contentAlignment = Alignment.Center + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(enabled = downloadStatus == DownloadStatus.NOT_DOWNLOADED || downloadStatus == DownloadStatus.ERROR) { + download() + } + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically ) { - // Круглый фон иконки + // File icon с индикатором прогресса - круглая иконка как в desktop Box( - modifier = Modifier - .fillMaxSize() - .clip(CircleShape) - .background( - if (downloadStatus == DownloadStatus.ERROR) Color(0xFFE53935) - else PrimaryBlue - ), + modifier = Modifier.size(40.dp), contentAlignment = Alignment.Center ) { - when (downloadStatus) { - DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - color = Color.White, - strokeWidth = 2.dp - ) - } - DownloadStatus.NOT_DOWNLOADED -> { - Icon( - Icons.Default.ArrowDownward, - contentDescription = "Download", - tint = Color.White, - modifier = Modifier.size(20.dp) - ) - } - DownloadStatus.DOWNLOADED -> { - Icon( - Icons.Default.InsertDriveFile, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(20.dp) - ) - } - DownloadStatus.ERROR -> { - Icon( - Icons.Default.Close, - contentDescription = "Error", - tint = Color.White, - modifier = Modifier.size(20.dp) - ) - } - else -> { - Icon( - Icons.Default.InsertDriveFile, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(20.dp) - ) + // Круглый фон иконки + Box( + modifier = Modifier + .fillMaxSize() + .clip(CircleShape) + .background( + if (downloadStatus == DownloadStatus.ERROR) Color(0xFFE53935) + else PrimaryBlue + ), + contentAlignment = Alignment.Center + ) { + when (downloadStatus) { + DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = Color.White, + strokeWidth = 2.dp + ) + } + DownloadStatus.NOT_DOWNLOADED -> { + Icon( + Icons.Default.ArrowDownward, + contentDescription = "Download", + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } + DownloadStatus.DOWNLOADED -> { + Icon( + Icons.Default.InsertDriveFile, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } + DownloadStatus.ERROR -> { + Icon( + Icons.Default.Close, + contentDescription = "Error", + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } + else -> { + Icon( + Icons.Default.InsertDriveFile, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } } } } - } - - Spacer(modifier = Modifier.width(10.dp)) - - // File info - Column(modifier = Modifier.weight(1f)) { - Text( - text = fileName, - fontSize = 14.sp, - fontWeight = FontWeight.Normal, - color = if (isOutgoing) Color.White else (if (isDarkTheme) Color.White else Color.Black), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(2.dp)) - // Размер файла и тип - val fileExtension = fileName.substringAfterLast('.', "").uppercase() - Text( - text = when (downloadStatus) { - DownloadStatus.DOWNLOADING -> "Downloading..." - DownloadStatus.DECRYPTING -> "Decrypting..." - DownloadStatus.ERROR -> "File expired" - else -> "${formatFileSize(fileSize)} $fileExtension" - }, + Spacer(modifier = Modifier.width(10.dp)) + + // File info + Column(modifier = Modifier.weight(1f)) { + Text( + text = fileName, + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + color = if (isOutgoing) Color.White else (if (isDarkTheme) Color.White else Color.Black), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(2.dp)) + + // Размер файла и тип + val fileExtension = fileName.substringAfterLast('.', "").uppercase() + Text( + text = when (downloadStatus) { + DownloadStatus.DOWNLOADING -> "Downloading..." + DownloadStatus.DECRYPTING -> "Decrypting..." + DownloadStatus.ERROR -> "File expired" + else -> "${formatFileSize(fileSize)} $fileExtension" + }, fontSize = 12.sp, color = if (downloadStatus == DownloadStatus.ERROR) { Color(0xFFE53935) @@ -659,11 +668,69 @@ fun FileAttachment( ) } } + + // Time and checkmarks (bottom-right overlay) for outgoing files + if (isOutgoing) { + Row( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(bottom = 6.dp, end = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + // Time + Text( + text = android.text.format.DateFormat.format("HH:mm", timestamp).toString(), + fontSize = 11.sp, + color = Color.White.copy(alpha = 0.7f) + ) + + Spacer(modifier = Modifier.width(4.dp)) + + // Checkmarks + 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, MessageStatus.READ -> { + Icon( + compose.icons.TablerIcons.Checks, + contentDescription = null, + tint = if (messageStatus == MessageStatus.READ) { + Color(0xFF4FC3F7) + } else { + Color.White.copy(alpha = 0.7f) + }, + modifier = Modifier.size(14.dp) + ) + } + MessageStatus.ERROR -> { + Icon( + Icons.Default.Error, + contentDescription = null, + tint = Color(0xFFE53935), + modifier = Modifier.size(14.dp) + ) + } + } + } + } + } } -/** - * Avatar attachment - Telegram style - */ @Composable fun AvatarAttachment( attachment: MessageAttachment,