diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 21fe2a5..2d48cf5 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -851,7 +851,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { messageId = "", senderName = opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } }, text = replyText, - isFromMe = false // Мы не знаем кто автор из fallback формата + isFromMe = false, // Мы не знаем кто автор из fallback формата + senderPublicKey = opponentKey ?: "", + recipientPrivateKey = myPrivateKey ?: "" ), mainText ) @@ -1005,7 +1007,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } }, text = replyText, isFromMe = isReplyFromMe, - attachments = originalAttachments + attachments = originalAttachments, + senderPublicKey = if (isReplyFromMe) myPublicKey ?: "" else opponentKey ?: "", + recipientPrivateKey = myPrivateKey ?: "" ) return result } else { @@ -1171,7 +1175,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { text = firstReply.text, isFromMe = firstReply.isOutgoing, isForwarded = isForward, - attachments = replyAttachments + attachments = replyAttachments, + senderPublicKey = if (firstReply.isOutgoing) myPublicKey ?: "" else opponentKey ?: "", + recipientPrivateKey = myPrivateKey ?: "" ) } else null diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt index 778b037..0d8061f 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt @@ -39,13 +39,19 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties +import android.graphics.BitmapFactory +import android.util.Base64 +import android.util.Log +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.repository.AvatarRepository import com.rosetta.messenger.ui.components.AppleEmojiText import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.chats.models.* import com.rosetta.messenger.ui.chats.utils.* +import com.rosetta.messenger.utils.AttachmentFileManager import com.vanniktech.blurhash.BlurHash import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -598,6 +604,7 @@ fun ReplyBubble( isDarkTheme: Boolean, onClick: () -> Unit = {} ) { + val context = androidx.compose.ui.platform.LocalContext.current val backgroundColor = if (isOutgoing) { Color.Black.copy(alpha = 0.15f) } else { @@ -616,24 +623,56 @@ fun ReplyBubble( val imageAttachment = replyData.attachments.firstOrNull { it.type == AttachmentType.IMAGE } val hasImage = imageAttachment != null - // Декодируем blurhash для превью - var previewBitmap by remember { mutableStateOf(null) } + // Загружаем полноценную картинку вместо blur preview + var imageBitmap by remember { mutableStateOf(null) } - LaunchedEffect(imageAttachment?.preview) { - if (imageAttachment != null && imageAttachment.preview.isNotEmpty()) { + LaunchedEffect(imageAttachment?.id) { + if (imageAttachment != null) { withContext(Dispatchers.IO) { try { - // Получаем blurhash из preview (может быть в формате "tag::blurhash") - val blurhash = if (imageAttachment.preview.contains("::")) { - imageAttachment.preview.split("::").lastOrNull() ?: "" - } else { - imageAttachment.preview + // Пробуем сначала из blob + if (imageAttachment.blob.isNotEmpty()) { + val decoded = try { + val cleanBase64 = if (imageAttachment.blob.contains(",")) { + imageAttachment.blob.substringAfter(",") + } else { + imageAttachment.blob + } + val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT) + BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) + } catch (e: Exception) { + null + } + if (decoded != null) { + imageBitmap = decoded + return@withContext + } } - if (blurhash.isNotEmpty() && !blurhash.startsWith("http")) { - previewBitmap = BlurHash.decode(blurhash, 40, 40) + + // Если blob нет - загружаем из локального файла + val localBlob = AttachmentFileManager.readAttachment( + context, + imageAttachment.id, + replyData.senderPublicKey, + replyData.recipientPrivateKey + ) + + if (localBlob != null) { + val decoded = try { + val cleanBase64 = if (localBlob.contains(",")) { + localBlob.substringAfter(",") + } else { + localBlob + } + val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT) + BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) + } catch (e: Exception) { + null + } + imageBitmap = decoded } } catch (e: Exception) { - // Ignore blurhash decode errors + Log.e("ReplyBubble", "Failed to load image: ${e.message}") } } } @@ -696,9 +735,9 @@ fun ReplyBubble( .clip(RoundedCornerShape(4.dp)) .background(Color.Gray.copy(alpha = 0.3f)) ) { - if (previewBitmap != null) { + if (imageBitmap != null) { Image( - bitmap = previewBitmap!!.asImageBitmap(), + bitmap = imageBitmap!!.asImageBitmap(), contentDescription = "Photo preview", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/models/ChatDetailModels.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/models/ChatDetailModels.kt index 4618a18..68927b6 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/models/ChatDetailModels.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/models/ChatDetailModels.kt @@ -23,7 +23,9 @@ data class ReplyData( val text: String, val isFromMe: Boolean, val isForwarded: Boolean = false, - val attachments: List = emptyList() // 🖼️ Для превью фото в reply + val attachments: List = emptyList(), // 🖼️ Для превью фото в reply + val senderPublicKey: String = "", // Для расшифровки attachments в reply + val recipientPrivateKey: String = "" // Для расшифровки attachments в reply ) /** Legacy message model (for compatibility) */