diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index b5bef7c..3493110 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -1408,7 +1408,10 @@ fun ChatDetailScreen( keyboardController?.hide() focusManager.clearFocus() showMediaPicker = true - } + }, + myPublicKey = viewModel.myPublicKey ?: "", + opponentPublicKey = user.publicKey, + myPrivateKey = currentUserPrivateKey ) } } 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 0d8061f..d98eed1 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 @@ -625,7 +625,33 @@ fun ReplyBubble( // Загружаем полноценную картинку вместо blur preview var imageBitmap by remember { mutableStateOf(null) } + // Blurhash preview для fallback + var blurPreviewBitmap by remember { mutableStateOf(null) } + // Сначала загружаем blurhash preview + LaunchedEffect(imageAttachment?.preview) { + if (imageAttachment != null && imageAttachment.preview.isNotEmpty()) { + withContext(Dispatchers.IO) { + try { + // Получаем blurhash из preview (может быть в формате "tag::blurhash") + val blurhash = if (imageAttachment.preview.contains("::")) { + imageAttachment.preview.substringAfter("::") + } else if (!imageAttachment.preview.startsWith("http") && imageAttachment.preview.length < 50) { + imageAttachment.preview + } else { + "" + } + if (blurhash.isNotEmpty()) { + blurPreviewBitmap = BlurHash.decode(blurhash, 36, 36) + } + } catch (e: Exception) { + // Ignore blurhash decode errors + } + } + } + } + + // Потом пробуем загрузить полноценную картинку LaunchedEffect(imageAttachment?.id) { if (imageAttachment != null) { withContext(Dispatchers.IO) { @@ -742,8 +768,16 @@ fun ReplyBubble( modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) + } else if (blurPreviewBitmap != null) { + // Blurhash preview если картинка не загружена + Image( + bitmap = blurPreviewBitmap!!.asImageBitmap(), + contentDescription = "Photo preview", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) } else { - // Placeholder с иконкой + // Placeholder с иконкой только если нет blurhash Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -920,10 +954,15 @@ private fun KebabMenuItem( @Composable fun ReplyImagePreview( attachment: MessageAttachment, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + senderPublicKey: String = "", + recipientPrivateKey: String = "" ) { + val context = androidx.compose.ui.platform.LocalContext.current var previewBitmap by remember { mutableStateOf(null) } + var fullImageBitmap by remember { mutableStateOf(null) } + // Сначала загружаем blurhash preview LaunchedEffect(attachment.preview) { if (attachment.preview.isNotEmpty()) { withContext(Dispatchers.IO) { @@ -944,12 +983,74 @@ fun ReplyImagePreview( } } + // Потом пробуем загрузить полноценную картинку + LaunchedEffect(attachment.id) { + if (senderPublicKey.isNotEmpty() && recipientPrivateKey.isNotEmpty()) { + withContext(Dispatchers.IO) { + try { + // Пробуем сначала из blob + if (attachment.blob.isNotEmpty()) { + val decoded = try { + val cleanBase64 = if (attachment.blob.contains(",")) { + attachment.blob.substringAfter(",") + } else { + attachment.blob + } + val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT) + BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) + } catch (e: Exception) { + null + } + if (decoded != null) { + fullImageBitmap = decoded + return@withContext + } + } + + // Если blob нет - загружаем из локального файла + val localBlob = AttachmentFileManager.readAttachment( + context, + attachment.id, + senderPublicKey, + 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 + } + fullImageBitmap = decoded + } + } catch (e: Exception) { + Log.e("ReplyImagePreview", "Failed to load full image: ${e.message}") + } + } + } + } + Box( modifier = modifier .clip(RoundedCornerShape(4.dp)) .background(Color.Gray.copy(alpha = 0.3f)) ) { - if (previewBitmap != null) { + if (fullImageBitmap != null) { + // Полноценная картинка если загружена + Image( + bitmap = fullImageBitmap!!.asImageBitmap(), + contentDescription = "Photo preview", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } else if (previewBitmap != null) { + // Blurhash preview если полная не загружена Image( bitmap = previewBitmap!!.asImageBitmap(), contentDescription = "Photo preview", @@ -957,7 +1058,7 @@ fun ReplyImagePreview( contentScale = ContentScale.Crop ) } else { - // Placeholder с иконкой + // Placeholder с иконкой только если нет ничего Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt index 74ba8ad..11a7e01 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt @@ -216,18 +216,6 @@ fun ImageViewerScreen( fontSize = 13.sp ) } - - // More options - IconButton( - onClick = { /* TODO: Share, save, etc */ }, - modifier = Modifier.align(Alignment.CenterEnd) - ) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "More", - tint = Color.White - ) - } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt index 229e286..0a40446 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt @@ -75,7 +75,10 @@ fun MessageInputBar( coordinator: KeyboardTransitionCoordinator, displayReplyMessages: List = emptyList(), onReplyClick: (String) -> Unit = {}, - onAttachClick: () -> Unit = {} + onAttachClick: () -> Unit = {}, + myPublicKey: String = "", + opponentPublicKey: String = "", + myPrivateKey: String = "" ) { val hasReply = replyMessages.isNotEmpty() val keyboardController = LocalSoftwareKeyboardController.current @@ -350,7 +353,9 @@ fun MessageInputBar( Spacer(modifier = Modifier.width(8.dp)) ReplyImagePreview( attachment = imageAttachment, - modifier = Modifier.size(36.dp) + modifier = Modifier.size(36.dp), + senderPublicKey = if (msg.isOutgoing) myPublicKey else opponentPublicKey, + recipientPrivateKey = myPrivateKey ) Spacer(modifier = Modifier.width(4.dp)) }