Починил optimistic и сохранение групповых фото при отправке
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user