feat: Enhance ForwardManager and ChatViewModel for improved message handling and logging; adjust ForwardChatPickerBottomSheet height for better UX
This commit is contained in:
@@ -103,6 +103,22 @@ object ForwardManager {
|
||||
*/
|
||||
fun hasForwardMessagesForChat(publicKey: String): Boolean {
|
||||
val selectedKey = _selectedChatPublicKey.value
|
||||
return selectedKey == publicKey && _forwardMessages.value.isNotEmpty()
|
||||
val hasMessages = _forwardMessages.value.isNotEmpty()
|
||||
android.util.Log.d("ForwardManager", "📨 hasForwardMessagesForChat($publicKey): selectedKey=$selectedKey, hasMessages=$hasMessages")
|
||||
return selectedKey == publicKey && hasMessages
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить выбранный чат и вернуть сообщения для него
|
||||
* Комбинированный метод для атомарного получения данных
|
||||
*/
|
||||
fun getForwardMessagesForChat(publicKey: String): List<ForwardMessage> {
|
||||
val selectedKey = _selectedChatPublicKey.value
|
||||
return if (selectedKey == publicKey && _forwardMessages.value.isNotEmpty()) {
|
||||
android.util.Log.d("ForwardManager", "📨 getForwardMessagesForChat: returning ${_forwardMessages.value.size} messages")
|
||||
_forwardMessages.value
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
opponentTitle = title
|
||||
opponentUsername = username
|
||||
|
||||
// 📨 СНАЧАЛА проверяем ForwardManager - ДО сброса состояния!
|
||||
// Это важно для правильного отображения forward сообщений сразу
|
||||
val forwardMessages = ForwardManager.getForwardMessagesForChat(publicKey)
|
||||
val hasForward = forwardMessages.isNotEmpty()
|
||||
if (hasForward) {
|
||||
android.util.Log.d("ChatViewModel", "📨 Will apply ${forwardMessages.size} forward messages")
|
||||
}
|
||||
|
||||
// Сбрасываем состояние
|
||||
_messages.value = emptyList()
|
||||
_opponentOnline.value = false
|
||||
@@ -366,11 +374,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
readReceiptSentForCurrentDialog = false
|
||||
isDialogActive = true // 🔥 Диалог активен!
|
||||
|
||||
// 📨 Проверяем ForwardManager - если есть сообщения для пересылки в этот чат
|
||||
if (ForwardManager.hasForwardMessagesForChat(publicKey)) {
|
||||
val forwardMessages = ForwardManager.consumeForwardMessages()
|
||||
if (forwardMessages.isNotEmpty()) {
|
||||
android.util.Log.d("ChatViewModel", "📨 Received ${forwardMessages.size} forward messages")
|
||||
// 📨 Применяем Forward сообщения СРАЗУ после сброса
|
||||
if (hasForward) {
|
||||
android.util.Log.d("ChatViewModel", "📨 Applying ${forwardMessages.size} forward messages NOW")
|
||||
// Конвертируем ForwardMessage в ReplyMessage
|
||||
_replyMessages.value = forwardMessages.map { fm ->
|
||||
ReplyMessage(
|
||||
@@ -382,9 +388,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
}
|
||||
_isForwardMode.value = true
|
||||
android.util.Log.d("ChatViewModel", "📨 isForwardMode set to TRUE, replyMessages.size = ${_replyMessages.value.size}")
|
||||
// Очищаем ForwardManager после применения
|
||||
ForwardManager.clear()
|
||||
}
|
||||
} else {
|
||||
// Сбрасываем forward state если нет forward сообщений
|
||||
_replyMessages.value = emptyList()
|
||||
_isForwardMode.value = false
|
||||
}
|
||||
|
||||
// Подписываемся на онлайн статус
|
||||
@@ -608,6 +618,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Как в архиве: расшифровываем при каждой загрузке
|
||||
*/
|
||||
private suspend fun entityToChatMessage(entity: MessageEntity): ChatMessage {
|
||||
android.util.Log.d("ReplyDebug", "🔄 [DB LOAD] entityToChatMessage called")
|
||||
android.util.Log.d("ReplyDebug", " - messageId: ${entity.messageId}")
|
||||
android.util.Log.d("ReplyDebug", " - attachments: ${entity.attachments}")
|
||||
android.util.Log.d("ReplyDebug", " - attachments length: ${entity.attachments.length}")
|
||||
|
||||
// Расшифровываем сообщение из content + chachaKey
|
||||
var displayText = try {
|
||||
val privateKey = myPrivateKey
|
||||
@@ -645,24 +660,36 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Decrypted text: ${displayText.take(50)}")
|
||||
|
||||
// Парсим attachments для поиска MESSAGES (цитата)
|
||||
// 🔥 ВАЖНО: Передаем content и chachaKey для расшифровки reply blob если нужно
|
||||
android.util.Log.d("ReplyDebug", " - Calling parseReplyFromAttachments...")
|
||||
var replyData = parseReplyFromAttachments(
|
||||
attachmentsJson = entity.attachments,
|
||||
isFromMe = entity.fromMe == 1,
|
||||
content = entity.content,
|
||||
chachaKey = entity.chachaKey
|
||||
)
|
||||
android.util.Log.d("ReplyDebug", " - parseReplyFromAttachments returned: ${if (replyData != null) "NOT NULL" else "NULL"}")
|
||||
|
||||
// Если не нашли reply в attachments, пробуем распарсить из текста
|
||||
if (replyData == null) {
|
||||
android.util.Log.d("ReplyDebug", " - Reply is null, trying to parse from text...")
|
||||
val parseResult = parseReplyFromText(displayText)
|
||||
if (parseResult != null) {
|
||||
android.util.Log.d("ReplyDebug", " - ✅ Parsed reply from text")
|
||||
replyData = parseResult.first
|
||||
displayText = parseResult.second
|
||||
} else {
|
||||
android.util.Log.d("ReplyDebug", " - ❌ Failed to parse reply from text")
|
||||
}
|
||||
} else {
|
||||
android.util.Log.d("ReplyDebug", " - ✅ Reply found in attachments!")
|
||||
}
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Creating ChatMessage with replyData: ${if (replyData != null) "NOT NULL" else "NULL"}")
|
||||
|
||||
return ChatMessage(
|
||||
id = entity.messageId,
|
||||
text = displayText,
|
||||
@@ -714,74 +741,132 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
content: String,
|
||||
chachaKey: String
|
||||
): ReplyData? {
|
||||
if (attachmentsJson.isEmpty() || attachmentsJson == "[]") return null
|
||||
android.util.Log.d("ReplyDebug", "🔍 [DB LOAD] parseReplyFromAttachments called")
|
||||
android.util.Log.d("ReplyDebug", " - attachmentsJson.isEmpty(): ${attachmentsJson.isEmpty()}")
|
||||
android.util.Log.d("ReplyDebug", " - attachmentsJson == '[]': ${attachmentsJson == "[]"}")
|
||||
|
||||
if (attachmentsJson.isEmpty() || attachmentsJson == "[]") {
|
||||
android.util.Log.d("ReplyDebug", " - Early return: attachments empty or []")
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
android.util.Log.d("ReplyDebug", "💾 [DB] Parsing reply from attachments JSON")
|
||||
android.util.Log.d("ReplyDebug", " - Attachments JSON preview: ${attachmentsJson.take(200)}")
|
||||
android.util.Log.d("ReplyDebug", "💾 [DB LOAD] Parsing reply from attachments JSON")
|
||||
android.util.Log.d("ReplyDebug", " - Full JSON: $attachmentsJson")
|
||||
android.util.Log.d("ReplyDebug", " - JSON length: ${attachmentsJson.length}")
|
||||
val attachments = JSONArray(attachmentsJson)
|
||||
android.util.Log.d("ReplyDebug", " - Attachments count: ${attachments.length()}")
|
||||
|
||||
for (i in 0 until attachments.length()) {
|
||||
android.util.Log.d("ReplyDebug", " - Processing attachment $i...")
|
||||
val attachment = attachments.getJSONObject(i)
|
||||
val type = attachment.optInt("type", 0)
|
||||
android.util.Log.d("ReplyDebug", " - Attachment $i type: $type")
|
||||
android.util.Log.d("ReplyDebug", " - type: $type")
|
||||
|
||||
// MESSAGES = 1 (цитата)
|
||||
if (type == 1) {
|
||||
android.util.Log.d("ReplyDebug", " - ✅ Found MESSAGES type!")
|
||||
|
||||
// Данные могут быть в blob или preview
|
||||
var dataJson = attachment.optString("blob", "").ifEmpty {
|
||||
attachment.optString("preview", "")
|
||||
var dataJson = attachment.optString("blob", "")
|
||||
android.util.Log.d("ReplyDebug", " - blob from JSON: ${if (dataJson.isEmpty()) "EMPTY" else "length=${dataJson.length}"}")
|
||||
|
||||
if (dataJson.isEmpty()) {
|
||||
dataJson = attachment.optString("preview", "")
|
||||
android.util.Log.d("ReplyDebug", " - blob was empty, trying preview: ${if (dataJson.isEmpty()) "ALSO EMPTY" else "length=${dataJson.length}"}")
|
||||
}
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Final data length: ${dataJson.length}")
|
||||
android.util.Log.d("ReplyDebug", " - Data preview (first 200): ${dataJson.take(200)}")
|
||||
|
||||
if (dataJson.isEmpty()) {
|
||||
android.util.Log.e("ReplyDebug", " - ❌ Data is empty, skipping")
|
||||
continue
|
||||
}
|
||||
android.util.Log.d("ReplyDebug", " - Found MESSAGES attachment, data length: ${dataJson.length}")
|
||||
android.util.Log.d("ReplyDebug", " - Data preview: ${dataJson.take(200)}")
|
||||
if (dataJson.isEmpty()) continue
|
||||
|
||||
// 🔥 Проверяем формат blob - если содержит ":", то это зашифрованный формат "iv:ciphertext"
|
||||
// Расшифровываем с приватным ключом (как в Desktop Архиве)
|
||||
val colonCount = dataJson.count { it == ':' }
|
||||
android.util.Log.d("ReplyDebug", " - Colon count in data: $colonCount")
|
||||
|
||||
if (dataJson.contains(":") && dataJson.split(":").size == 2) {
|
||||
android.util.Log.d("ReplyDebug", " - Blob is encrypted, decrypting with private key...")
|
||||
android.util.Log.d("ReplyDebug", " - 🔒 Blob is ENCRYPTED (iv:ciphertext format)")
|
||||
android.util.Log.d("ReplyDebug", " - Attempting to decrypt with private key...")
|
||||
val privateKey = myPrivateKey
|
||||
android.util.Log.d("ReplyDebug", " - Private key available: ${privateKey != null}")
|
||||
|
||||
if (privateKey != null) {
|
||||
android.util.Log.d("ReplyDebug", " - Private key length: ${privateKey.length}")
|
||||
try {
|
||||
dataJson = CryptoManager.decryptWithPassword(dataJson, privateKey) ?: dataJson
|
||||
android.util.Log.d("ReplyDebug", " - Decrypted successfully, length: ${dataJson.length}")
|
||||
val decrypted = CryptoManager.decryptWithPassword(dataJson, privateKey)
|
||||
android.util.Log.d("ReplyDebug", " - Decryption result: ${if (decrypted != null) "SUCCESS" else "NULL"}")
|
||||
if (decrypted != null) {
|
||||
android.util.Log.d("ReplyDebug", " - Decrypted length: ${decrypted.length}")
|
||||
android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}")
|
||||
dataJson = decrypted
|
||||
} else {
|
||||
android.util.Log.e("ReplyDebug", " - ❌ Decryption returned null")
|
||||
continue
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ReplyDebug", " - Failed to decrypt blob", e)
|
||||
android.util.Log.e("ReplyDebug", " - ❌ Decryption exception: ${e.message}", e)
|
||||
android.util.Log.e("ReplyDebug", " - Stack trace:", e)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
android.util.Log.e("ReplyDebug", " - Cannot decrypt: private key is null")
|
||||
android.util.Log.e("ReplyDebug", " - ❌ Cannot decrypt: private key is NULL")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
android.util.Log.d("ReplyDebug", " - 📄 Blob is PLAINTEXT (not encrypted)")
|
||||
}
|
||||
|
||||
val messagesArray = JSONArray(dataJson)
|
||||
android.util.Log.d("ReplyDebug", " - Attempting to parse as JSONArray...")
|
||||
val messagesArray = try {
|
||||
JSONArray(dataJson)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ReplyDebug", " - ❌ Failed to parse as JSONArray: ${e.message}")
|
||||
android.util.Log.e("ReplyDebug", " - Data was: $dataJson")
|
||||
continue
|
||||
}
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Messages array length: ${messagesArray.length()}")
|
||||
|
||||
if (messagesArray.length() > 0) {
|
||||
android.util.Log.d("ReplyDebug", " - Extracting first message from array...")
|
||||
val replyMessage = messagesArray.getJSONObject(0)
|
||||
val replyPublicKey = replyMessage.optString("publicKey", "")
|
||||
val replyText = replyMessage.optString("message", "")
|
||||
val replyMessageId = replyMessage.optString("message_id", "")
|
||||
|
||||
// Определяем, кто автор цитируемого сообщения
|
||||
// Если publicKey == myPublicKey - цитата от меня
|
||||
val isReplyFromMe = replyPublicKey == myPublicKey
|
||||
android.util.Log.d("ReplyDebug", " - replyMessageId: $replyMessageId")
|
||||
android.util.Log.d("ReplyDebug", " - replyPublicKey: ${replyPublicKey.take(20)}...")
|
||||
android.util.Log.d("ReplyDebug", " - replyText: ${replyText.take(50)}")
|
||||
android.util.Log.d("ReplyDebug", " - myPublicKey: ${myPublicKey?.take(20)}...")
|
||||
|
||||
// Определяем, кто автор цитируемого сообщения
|
||||
val isReplyFromMe = replyPublicKey == myPublicKey
|
||||
android.util.Log.d("ReplyDebug", " - isReplyFromMe: $isReplyFromMe")
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Parsed from DB: id=$replyMessageId, text=${replyText.take(30)}, isFromMe=$isReplyFromMe")
|
||||
val result = ReplyData(
|
||||
messageId = replyMessageId,
|
||||
senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } },
|
||||
text = replyText,
|
||||
isFromMe = isReplyFromMe
|
||||
)
|
||||
android.util.Log.d("ReplyDebug", "✅ [DB] Reply data parsed successfully from DB")
|
||||
android.util.Log.d("ReplyDebug", " - ✅ Created ReplyData: senderName=${result.senderName}, text=${result.text.take(30)}")
|
||||
android.util.Log.d("ReplyDebug", "✅ [DB LOAD] Reply data parsed successfully from DB - RETURNING")
|
||||
return result
|
||||
} else {
|
||||
android.util.Log.e("ReplyDebug", " - ❌ Messages array is empty")
|
||||
}
|
||||
} else {
|
||||
android.util.Log.d("ReplyDebug", " - ⏭️ Skipping attachment: type != 1")
|
||||
}
|
||||
}
|
||||
}
|
||||
android.util.Log.d("ReplyDebug", "💾 [DB] No MESSAGES attachment found")
|
||||
android.util.Log.d("ReplyDebug", "💾 [DB LOAD] No MESSAGES attachment found")
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ReplyDebug", "❌ [DB] Failed to parse reply from attachments:", e)
|
||||
android.util.Log.e("ReplyDebug", "❌ [DB LOAD] Failed to parse reply from attachments:", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ fun ForwardChatPickerBottomSheet(
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 400.dp)
|
||||
.heightIn(min = 300.dp, max = 400.dp) // 🔥 Минимальная высота для лучшего UX
|
||||
) {
|
||||
items(dialogs, key = { it.opponentKey }) { dialog ->
|
||||
ForwardDialogItem(
|
||||
|
||||
Reference in New Issue
Block a user