feat: Enhance ForwardManager and ChatViewModel for improved message handling and logging; adjust ForwardChatPickerBottomSheet height for better UX

This commit is contained in:
k1ngsterr1
2026-01-16 03:38:33 +05:00
parent 81d2b744ba
commit fbab2d0f80
3 changed files with 144 additions and 43 deletions

View File

@@ -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()
}
}
}

View File

@@ -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
}
}

View File

@@ -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(