Улучшена обработка звонков и вложений: нормализация входящих аттачментов, обновление UI карточек звонков

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 20:45:25 +05:00
parent 434ccef30c
commit ff854e919e
8 changed files with 429 additions and 50 deletions

View File

@@ -844,17 +844,20 @@ class MessageRepository private constructor(private val context: Context) {
}
}
val normalizedIncomingAttachments =
normalizeIncomingAttachments(packet.attachments, plainText)
// 📝 LOG: Расшифровка успешна
MessageLogger.logDecryptionSuccess(
messageId = messageId,
plainTextLength = plainText.length,
attachmentsCount = packet.attachments.size
attachmentsCount = normalizedIncomingAttachments.size
)
// Сериализуем attachments в JSON с расшифровкой MESSAGES blob
val attachmentsJson =
serializeAttachmentsWithDecryption(
packet.attachments,
normalizedIncomingAttachments,
packet.chachaKey,
privateKey,
plainKeyAndNonce,
@@ -863,7 +866,7 @@ class MessageRepository private constructor(private val context: Context) {
// 🖼️ Обрабатываем IMAGE attachments - сохраняем в файл (как в desktop)
processImageAttachments(
packet.attachments,
normalizedIncomingAttachments,
packet.chachaKey,
privateKey,
plainKeyAndNonce,
@@ -876,7 +879,7 @@ class MessageRepository private constructor(private val context: Context) {
val avatarOwnerKey =
if (isGroupMessage) toGroupDialogPublicKey(packet.toPublicKey) else packet.fromPublicKey
processAvatarAttachments(
packet.attachments,
normalizedIncomingAttachments,
avatarOwnerKey,
packet.chachaKey,
privateKey,
@@ -917,7 +920,7 @@ class MessageRepository private constructor(private val context: Context) {
plainMessage = encryptedPlainMessage, // 🔒 Зашифрованный текст
attachments = attachmentsJson,
primaryAttachmentType =
resolvePrimaryAttachmentType(packet.attachments),
resolvePrimaryAttachmentType(normalizedIncomingAttachments),
dialogKey = dialogKey
)
@@ -1703,6 +1706,50 @@ class MessageRepository private constructor(private val context: Context) {
return attachments.first().type.value
}
/**
* Desktop иногда присылает attachment звонка с некорректным type при поврежденном/пограничном
* пакете (в UI это превращается в пустой пузырь). Для attachment-only сообщения мягко
* нормализуем такой кейс к CALL.
*/
private fun normalizeIncomingAttachments(
attachments: List<MessageAttachment>,
plainText: String
): List<MessageAttachment> {
if (attachments.isEmpty() || plainText.isNotBlank() || attachments.size != 1) {
return attachments
}
val first = attachments.first()
if (!isLikelyCallAttachment(first, plainText)) {
return attachments
}
return when (first.type) {
AttachmentType.CALL -> attachments
else -> {
MessageLogger.debug(
"📥 ATTACHMENT FIXUP: coerced ${first.type} -> CALL for ${first.id.take(8)}..."
)
listOf(first.copy(type = AttachmentType.CALL))
}
}
}
private fun isLikelyCallAttachment(attachment: MessageAttachment, plainText: String): Boolean {
if (plainText.isNotBlank()) return false
if (attachment.blob.isNotBlank()) return false
if (attachment.width > 0 || attachment.height > 0) return false
val preview = attachment.preview.trim()
if (preview.isEmpty()) return true
val tail = preview.substringAfterLast("::", preview).trim()
if (tail.toIntOrNull() != null) return true
return Regex("duration(?:Sec|Seconds)?\\s*[:=]\\s*\\d+", RegexOption.IGNORE_CASE)
.containsMatchIn(preview)
}
private suspend fun upsertSearchIndex(account: String, entity: MessageEntity, plainText: String) {
val opponentKey =
if (entity.fromMe == 1) entity.toPublicKey.trim() else entity.fromPublicKey.trim()