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 {
|
fun hasForwardMessagesForChat(publicKey: String): Boolean {
|
||||||
val selectedKey = _selectedChatPublicKey.value
|
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
|
opponentTitle = title
|
||||||
opponentUsername = username
|
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()
|
_messages.value = emptyList()
|
||||||
_opponentOnline.value = false
|
_opponentOnline.value = false
|
||||||
@@ -366,11 +374,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
readReceiptSentForCurrentDialog = false
|
readReceiptSentForCurrentDialog = false
|
||||||
isDialogActive = true // 🔥 Диалог активен!
|
isDialogActive = true // 🔥 Диалог активен!
|
||||||
|
|
||||||
// 📨 Проверяем ForwardManager - если есть сообщения для пересылки в этот чат
|
// 📨 Применяем Forward сообщения СРАЗУ после сброса
|
||||||
if (ForwardManager.hasForwardMessagesForChat(publicKey)) {
|
if (hasForward) {
|
||||||
val forwardMessages = ForwardManager.consumeForwardMessages()
|
android.util.Log.d("ChatViewModel", "📨 Applying ${forwardMessages.size} forward messages NOW")
|
||||||
if (forwardMessages.isNotEmpty()) {
|
|
||||||
android.util.Log.d("ChatViewModel", "📨 Received ${forwardMessages.size} forward messages")
|
|
||||||
// Конвертируем ForwardMessage в ReplyMessage
|
// Конвертируем ForwardMessage в ReplyMessage
|
||||||
_replyMessages.value = forwardMessages.map { fm ->
|
_replyMessages.value = forwardMessages.map { fm ->
|
||||||
ReplyMessage(
|
ReplyMessage(
|
||||||
@@ -382,9 +388,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
_isForwardMode.value = true
|
_isForwardMode.value = true
|
||||||
|
android.util.Log.d("ChatViewModel", "📨 isForwardMode set to TRUE, replyMessages.size = ${_replyMessages.value.size}")
|
||||||
// Очищаем ForwardManager после применения
|
// Очищаем ForwardManager после применения
|
||||||
ForwardManager.clear()
|
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 {
|
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
|
// Расшифровываем сообщение из content + chachaKey
|
||||||
var displayText = try {
|
var displayText = try {
|
||||||
val privateKey = myPrivateKey
|
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 (цитата)
|
// Парсим attachments для поиска MESSAGES (цитата)
|
||||||
// 🔥 ВАЖНО: Передаем content и chachaKey для расшифровки reply blob если нужно
|
// 🔥 ВАЖНО: Передаем content и chachaKey для расшифровки reply blob если нужно
|
||||||
|
android.util.Log.d("ReplyDebug", " - Calling parseReplyFromAttachments...")
|
||||||
var replyData = parseReplyFromAttachments(
|
var replyData = parseReplyFromAttachments(
|
||||||
attachmentsJson = entity.attachments,
|
attachmentsJson = entity.attachments,
|
||||||
isFromMe = entity.fromMe == 1,
|
isFromMe = entity.fromMe == 1,
|
||||||
content = entity.content,
|
content = entity.content,
|
||||||
chachaKey = entity.chachaKey
|
chachaKey = entity.chachaKey
|
||||||
)
|
)
|
||||||
|
android.util.Log.d("ReplyDebug", " - parseReplyFromAttachments returned: ${if (replyData != null) "NOT NULL" else "NULL"}")
|
||||||
|
|
||||||
// Если не нашли reply в attachments, пробуем распарсить из текста
|
// Если не нашли reply в attachments, пробуем распарсить из текста
|
||||||
if (replyData == null) {
|
if (replyData == null) {
|
||||||
|
android.util.Log.d("ReplyDebug", " - Reply is null, trying to parse from text...")
|
||||||
val parseResult = parseReplyFromText(displayText)
|
val parseResult = parseReplyFromText(displayText)
|
||||||
if (parseResult != null) {
|
if (parseResult != null) {
|
||||||
|
android.util.Log.d("ReplyDebug", " - ✅ Parsed reply from text")
|
||||||
replyData = parseResult.first
|
replyData = parseResult.first
|
||||||
displayText = parseResult.second
|
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(
|
return ChatMessage(
|
||||||
id = entity.messageId,
|
id = entity.messageId,
|
||||||
text = displayText,
|
text = displayText,
|
||||||
@@ -714,74 +741,132 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
content: String,
|
content: String,
|
||||||
chachaKey: String
|
chachaKey: String
|
||||||
): ReplyData? {
|
): 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 {
|
return try {
|
||||||
android.util.Log.d("ReplyDebug", "💾 [DB] Parsing reply from attachments JSON")
|
android.util.Log.d("ReplyDebug", "💾 [DB LOAD] Parsing reply from attachments JSON")
|
||||||
android.util.Log.d("ReplyDebug", " - Attachments JSON preview: ${attachmentsJson.take(200)}")
|
android.util.Log.d("ReplyDebug", " - Full JSON: $attachmentsJson")
|
||||||
|
android.util.Log.d("ReplyDebug", " - JSON length: ${attachmentsJson.length}")
|
||||||
val attachments = JSONArray(attachmentsJson)
|
val attachments = JSONArray(attachmentsJson)
|
||||||
android.util.Log.d("ReplyDebug", " - Attachments count: ${attachments.length()}")
|
android.util.Log.d("ReplyDebug", " - Attachments count: ${attachments.length()}")
|
||||||
|
|
||||||
for (i in 0 until attachments.length()) {
|
for (i in 0 until attachments.length()) {
|
||||||
|
android.util.Log.d("ReplyDebug", " - Processing attachment $i...")
|
||||||
val attachment = attachments.getJSONObject(i)
|
val attachment = attachments.getJSONObject(i)
|
||||||
val type = attachment.optInt("type", 0)
|
val type = attachment.optInt("type", 0)
|
||||||
android.util.Log.d("ReplyDebug", " - Attachment $i type: $type")
|
android.util.Log.d("ReplyDebug", " - type: $type")
|
||||||
|
|
||||||
// MESSAGES = 1 (цитата)
|
// MESSAGES = 1 (цитата)
|
||||||
if (type == 1) {
|
if (type == 1) {
|
||||||
|
android.util.Log.d("ReplyDebug", " - ✅ Found MESSAGES type!")
|
||||||
|
|
||||||
// Данные могут быть в blob или preview
|
// Данные могут быть в blob или preview
|
||||||
var dataJson = attachment.optString("blob", "").ifEmpty {
|
var dataJson = attachment.optString("blob", "")
|
||||||
attachment.optString("preview", "")
|
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"
|
// 🔥 Проверяем формат 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) {
|
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
|
val privateKey = myPrivateKey
|
||||||
|
android.util.Log.d("ReplyDebug", " - Private key available: ${privateKey != null}")
|
||||||
|
|
||||||
if (privateKey != null) {
|
if (privateKey != null) {
|
||||||
|
android.util.Log.d("ReplyDebug", " - Private key length: ${privateKey.length}")
|
||||||
try {
|
try {
|
||||||
dataJson = CryptoManager.decryptWithPassword(dataJson, privateKey) ?: dataJson
|
val decrypted = CryptoManager.decryptWithPassword(dataJson, privateKey)
|
||||||
android.util.Log.d("ReplyDebug", " - Decrypted successfully, length: ${dataJson.length}")
|
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) {
|
} 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
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
android.util.Log.e("ReplyDebug", " - Cannot decrypt: private key is null")
|
android.util.Log.e("ReplyDebug", " - ❌ Cannot decrypt: private key is NULL")
|
||||||
continue
|
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) {
|
if (messagesArray.length() > 0) {
|
||||||
|
android.util.Log.d("ReplyDebug", " - Extracting first message from array...")
|
||||||
val replyMessage = messagesArray.getJSONObject(0)
|
val replyMessage = messagesArray.getJSONObject(0)
|
||||||
val replyPublicKey = replyMessage.optString("publicKey", "")
|
val replyPublicKey = replyMessage.optString("publicKey", "")
|
||||||
val replyText = replyMessage.optString("message", "")
|
val replyText = replyMessage.optString("message", "")
|
||||||
val replyMessageId = replyMessage.optString("message_id", "")
|
val replyMessageId = replyMessage.optString("message_id", "")
|
||||||
|
|
||||||
// Определяем, кто автор цитируемого сообщения
|
android.util.Log.d("ReplyDebug", " - replyMessageId: $replyMessageId")
|
||||||
// Если publicKey == myPublicKey - цитата от меня
|
android.util.Log.d("ReplyDebug", " - replyPublicKey: ${replyPublicKey.take(20)}...")
|
||||||
val isReplyFromMe = replyPublicKey == myPublicKey
|
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(
|
val result = ReplyData(
|
||||||
messageId = replyMessageId,
|
messageId = replyMessageId,
|
||||||
senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } },
|
senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } },
|
||||||
text = replyText,
|
text = replyText,
|
||||||
isFromMe = isReplyFromMe
|
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
|
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 LOAD] No MESSAGES attachment found")
|
||||||
android.util.Log.d("ReplyDebug", "💾 [DB] No MESSAGES attachment found")
|
|
||||||
null
|
null
|
||||||
} catch (e: Exception) {
|
} 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
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ fun ForwardChatPickerBottomSheet(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(max = 400.dp)
|
.heightIn(min = 300.dp, max = 400.dp) // 🔥 Минимальная высота для лучшего UX
|
||||||
) {
|
) {
|
||||||
items(dialogs, key = { it.opponentKey }) { dialog ->
|
items(dialogs, key = { it.opponentKey }) { dialog ->
|
||||||
ForwardDialogItem(
|
ForwardDialogItem(
|
||||||
|
|||||||
Reference in New Issue
Block a user