Починил optimistic и сохранение групповых фото при отправке

This commit is contained in:
2026-03-19 22:34:00 +05:00
parent 1ba173be54
commit f34e520d03

View File

@@ -3500,15 +3500,101 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
return return
} }
val recipient = opponentKey
val sender = myPublicKey
val privateKey = myPrivateKey
val context = getApplication<Application>() val context = getApplication<Application>()
val groupDebugId = UUID.randomUUID().toString().replace("-", "").take(8) val groupDebugId = UUID.randomUUID().toString().replace("-", "").take(8)
if (recipient == null || sender == null || privateKey == null) {
ProtocolManager.addLog(
"❌ IMG-GROUP $groupDebugId | aborted: missing keys (recipient=${recipient != null}, sender=${sender != null}, private=${privateKey != null})"
)
return
}
if (isSending) {
ProtocolManager.addLog("⚠️ IMG-GROUP $groupDebugId | skipped: another send in progress")
return
}
isSending = true
val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
val timestamp = System.currentTimeMillis()
val text = caption.trim()
val attachmentIds = imageUris.indices.map { index -> "img_${timestamp}_$index" }
val optimisticAttachments =
imageUris.mapIndexed { index, uri ->
MessageAttachment(
id = attachmentIds[index],
blob = "",
type = AttachmentType.IMAGE,
preview = "",
width = 0,
height = 0,
localUri = uri.toString()
)
}
addMessageSafely(
ChatMessage(
id = messageId,
text = text,
isOutgoing = true,
timestamp = Date(timestamp),
status = MessageStatus.SENDING,
attachments = optimisticAttachments
)
)
_inputText.value = ""
ProtocolManager.addLog( ProtocolManager.addLog(
"📸 IMG-GROUP $groupDebugId | prepare start: count=${imageUris.size}, captionLen=${caption.trim().length}" "📸 IMG-GROUP $groupDebugId | prepare start: count=${imageUris.size}, captionLen=${caption.trim().length}"
) )
backgroundUploadScope.launch { backgroundUploadScope.launch {
try {
val optimisticAttachmentsJson =
JSONArray().apply {
imageUris.forEachIndexed { index, uri ->
put(
JSONObject().apply {
put("id", attachmentIds[index])
put("type", AttachmentType.IMAGE.value)
put("preview", "")
put("blob", "")
put("width", 0)
put("height", 0)
put("localUri", uri.toString())
}
)
}
}.toString()
saveMessageToDatabase(
messageId = messageId,
text = text,
encryptedContent = "",
encryptedKey = "",
timestamp = timestamp,
isFromMe = true,
delivered = 0,
attachmentsJson = optimisticAttachmentsJson,
opponentPublicKey = recipient
)
saveDialog(
lastMessage = if (text.isNotEmpty()) text else "📷 ${imageUris.size} photos",
timestamp = timestamp,
opponentPublicKey = recipient
)
} catch (_: Exception) {
ProtocolManager.addLog("⚠️ IMG-GROUP $groupDebugId | optimistic DB save skipped (non-fatal)")
}
val preparedImages = val preparedImages =
imageUris.mapIndexedNotNull { index, uri -> imageUris.mapIndexed { index, uri ->
val (width, height) = val (width, height) =
com.rosetta.messenger.utils.MediaUtils.getImageDimensions( com.rosetta.messenger.utils.MediaUtils.getImageDimensions(
context, context,
@@ -3523,7 +3609,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
ProtocolManager.addLog( ProtocolManager.addLog(
"❌ IMG-GROUP $groupDebugId | item#$index base64 conversion failed" "❌ IMG-GROUP $groupDebugId | item#$index base64 conversion failed"
) )
return@mapIndexedNotNull null throw IllegalStateException(
"group item#$index base64 conversion failed"
)
} }
val blurhash = val blurhash =
com.rosetta.messenger.utils.MediaUtils.generateBlurhash( com.rosetta.messenger.utils.MediaUtils.generateBlurhash(
@@ -3533,26 +3621,156 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
ProtocolManager.addLog( ProtocolManager.addLog(
"📸 IMG-GROUP $groupDebugId | item#$index prepared: ${width}x$height, base64Len=${imageBase64.length}, blurhashLen=${blurhash.length}" "📸 IMG-GROUP $groupDebugId | item#$index prepared: ${width}x$height, base64Len=${imageBase64.length}, blurhashLen=${blurhash.length}"
) )
ImageData( index to
base64 = imageBase64, ImageData(
blurhash = blurhash, base64 = imageBase64,
width = width, blurhash = blurhash,
height = height width = width,
) height = height
)
} }
if (preparedImages.isEmpty()) { if (preparedImages.isEmpty()) {
ProtocolManager.addLog( ProtocolManager.addLog(
"❌ IMG-GROUP $groupDebugId | no prepared images, send canceled" "❌ IMG-GROUP $groupDebugId | no prepared images, send canceled"
) )
updateMessageStatusInDb(messageId, DeliveryStatus.ERROR.value)
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.ERROR) }
isSending = false
return@launch return@launch
} }
ProtocolManager.addLog( ProtocolManager.addLog(
"📸 IMG-GROUP $groupDebugId | prepare done: ready=${preparedImages.size}" "📸 IMG-GROUP $groupDebugId | prepare done: ready=${preparedImages.size}"
) )
withContext(Dispatchers.Main) { try {
sendImageGroup(preparedImages, caption) val groupStartedAt = System.currentTimeMillis()
val encryptionContext =
buildEncryptionContext(
plaintext = text,
recipient = recipient,
privateKey = privateKey
) ?: throw IllegalStateException("Cannot resolve chat encryption context")
val encryptedContent = encryptionContext.encryptedContent
val encryptedKey = encryptionContext.encryptedKey
val aesChachaKey = encryptionContext.aesChachaKey
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
val isSavedMessages = sender == recipient
val networkAttachments = mutableListOf<MessageAttachment>()
val finalDbAttachments = JSONArray()
val finalAttachmentsById = mutableMapOf<String, MessageAttachment>()
for ((originalIndex, imageData) in preparedImages) {
val attachmentId = attachmentIds[originalIndex]
val encryptedImageBlob =
encryptAttachmentPayload(imageData.base64, encryptionContext)
val uploadTag =
if (!isSavedMessages) {
TransportManager.uploadFile(attachmentId, encryptedImageBlob)
} else {
""
}
val previewWithTag =
if (uploadTag.isNotEmpty()) "$uploadTag::${imageData.blurhash}"
else imageData.blurhash
AttachmentFileManager.saveAttachment(
context = context,
blob = imageData.base64,
attachmentId = attachmentId,
publicKey = sender,
privateKey = privateKey
)
val finalAttachment =
MessageAttachment(
id = attachmentId,
blob = if (uploadTag.isNotEmpty()) "" else encryptedImageBlob,
type = AttachmentType.IMAGE,
preview = previewWithTag,
width = imageData.width,
height = imageData.height,
localUri = ""
)
networkAttachments.add(finalAttachment)
finalAttachmentsById[attachmentId] = finalAttachment
finalDbAttachments.put(
JSONObject().apply {
put("id", attachmentId)
put("type", AttachmentType.IMAGE.value)
put("preview", previewWithTag)
put("blob", "")
put("width", imageData.width)
put("height", imageData.height)
}
)
}
val packet =
PacketMessage().apply {
fromPublicKey = sender
toPublicKey = recipient
content = encryptedContent
chachaKey = encryptedKey
this.aesChachaKey = aesChachaKey
this.timestamp = timestamp
this.privateKey = privateKeyHash
this.messageId = messageId
attachments = networkAttachments
}
if (!isSavedMessages) {
ProtocolManager.send(packet)
}
updateMessageStatusAndAttachmentsInDb(
messageId = messageId,
delivered = 1,
attachmentsJson = finalDbAttachments.toString()
)
withContext(Dispatchers.Main) {
_messages.value =
_messages.value.map { msg ->
if (msg.id != messageId) return@map msg
msg.copy(
status = MessageStatus.SENT,
attachments =
msg.attachments.map { current ->
val final = finalAttachmentsById[current.id]
if (final != null) {
current.copy(
preview = final.preview,
width = final.width,
height = final.height,
localUri = ""
)
} else {
current.copy(localUri = "")
}
}
)
}
updateCacheFromCurrentMessages()
}
saveDialog(
lastMessage = if (text.isNotEmpty()) text else "📷 ${imageUris.size} photos",
timestamp = timestamp,
opponentPublicKey = recipient
)
logPhotoPipeline(
messageId,
"group-from-uri completed; totalElapsed=${System.currentTimeMillis() - groupStartedAt}ms"
)
} catch (e: Exception) {
logPhotoPipelineError(messageId, "group-from-uri", e)
updateMessageStatusInDb(messageId, DeliveryStatus.ERROR.value)
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.ERROR) }
} finally {
isSending = false
} }
} }
} }