feat: Integrate AccountManager to retrieve last logged account in AuthFlow and update MainActivity

This commit is contained in:
2026-01-16 06:18:47 +05:00
parent c52e6dda53
commit b1046f88e5
12 changed files with 24 additions and 242 deletions

View File

@@ -333,18 +333,12 @@ fun ChatDetailScreen(
// Логирование изменений selection mode
LaunchedEffect(isSelectionMode, selectedMessages.size) {
android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════")
android.util.Log.d("ChatDetailScreen", "📝 SELECTION MODE CHANGED")
android.util.Log.d("ChatDetailScreen", " 📊 isSelectionMode: $isSelectionMode")
android.util.Log.d("ChatDetailScreen", " 📊 selectedMessages.size: ${selectedMessages.size}")
android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════")
}
// 🔥 Backup: если клавиатура ещё открыта когда selection mode активировался
// (клавиатура уже должна быть закрыта в onLongClick, это только backup)
LaunchedEffect(isSelectionMode) {
if (isSelectionMode) {
android.util.Log.d("ChatDetailScreen", "⚠️ Backup keyboard hide triggered")
// Backup закрытие клавиатуры (основное в onLongClick)
keyboardController?.hide()
}
@@ -445,13 +439,9 @@ fun ChatDetailScreen(
// 🔥 Функция для скролла к сообщению с подсветкой
val scrollToMessage: (String) -> Unit = { messageId ->
android.util.Log.d("ChatDetail", "🔍 scrollToMessage called for: '$messageId'")
android.util.Log.d("ChatDetail", " - messageId length: ${messageId.length}")
android.util.Log.d("ChatDetail", " - Total messages: ${messagesWithDates.size}")
// Логируем все ID сообщений для отладки
messagesWithDates.forEachIndexed { index, pair ->
android.util.Log.d("ChatDetail", " - [$index] id='${pair.first.id}', text='${pair.first.text.take(20)}...'")
}
scope.launch {
@@ -461,18 +451,15 @@ fun ChatDetailScreen(
// Находим индекс сообщения в списке
val messageIndex = messagesWithDates.indexOfFirst { it.first.id == messageId }
android.util.Log.d("ChatDetail", " - Found at index: $messageIndex")
if (messageIndex != -1) {
// Скроллим к сообщению
listState.animateScrollToItem(messageIndex)
android.util.Log.d("ChatDetail", " ✅ Scrolled to message")
// Подсвечиваем на 2 секунды
highlightedMessageId = messageId
delay(2000)
highlightedMessageId = null
} else {
android.util.Log.d("ChatDetail", " ❌ Message not found in list")
}
}
}
@@ -1018,14 +1005,6 @@ fun ChatDetailScreen(
// Логирование состояния
LaunchedEffect(isSelectionMode, useImePadding, coordinator.isEmojiBoxVisible, coordinator.keyboardHeight) {
android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════")
android.util.Log.d("ChatDetailScreen", "🔄 BOTTOM BAR STATE CHANGED")
android.util.Log.d("ChatDetailScreen", " 📊 isSelectionMode: $isSelectionMode")
android.util.Log.d("ChatDetailScreen", " 📊 useImePadding: $useImePadding")
android.util.Log.d("ChatDetailScreen", " 📊 isEmojiBoxVisible: ${coordinator.isEmojiBoxVisible}")
android.util.Log.d("ChatDetailScreen", " 📊 keyboardHeight: ${coordinator.keyboardHeight}")
android.util.Log.d("ChatDetailScreen", " 📊 emojiHeight: ${coordinator.emojiHeight}")
android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════")
}
Column(modifier = bottomModifier) {
@@ -1057,7 +1036,6 @@ fun ChatDetailScreen(
},
label = "bottomBarContent"
) { selectionMode ->
android.util.Log.d("ChatDetailScreen", "🎬 AnimatedContent to selectionMode=$selectionMode")
if (selectionMode) {
// SELECTION ACTION BAR - Reply/Forward
@@ -1360,19 +1338,13 @@ fun ChatDetailScreen(
isSelected = selectedMessages.contains(selectionKey),
isHighlighted = highlightedMessageId == message.id,
onLongClick = {
android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════")
android.util.Log.d("ChatDetailScreen", "👆 LONG CLICK on message")
android.util.Log.d("ChatDetailScreen", " 📊 isSelectionMode BEFORE: $isSelectionMode")
android.util.Log.d("ChatDetailScreen", " 📊 selectedMessages.size BEFORE: ${selectedMessages.size}")
// 🔥 СНАЧАЛА закрываем клавиатуру МГНОВЕННО (до изменения state)
if (!isSelectionMode) {
android.util.Log.d("ChatDetailScreen", " ⌨️ Closing keyboard...")
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
focusManager.clearFocus()
showEmojiPicker = false
android.util.Log.d("ChatDetailScreen", " ✅ Keyboard closed")
}
// Toggle selection on long press
selectedMessages = if (selectedMessages.contains(selectionKey)) {
@@ -1381,8 +1353,6 @@ fun ChatDetailScreen(
selectedMessages + selectionKey
}
android.util.Log.d("ChatDetailScreen", " 📊 selectedMessages.size AFTER: ${selectedMessages.size}")
android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════")
},
onClick = {
// If in selection mode, toggle selection
@@ -1905,7 +1875,6 @@ private fun MessageBubble(
isOutgoing = message.isOutgoing,
isDarkTheme = isDarkTheme,
onClick = {
android.util.Log.d("ChatDetail", "🖱️ Reply clicked: ${reply.messageId}")
onReplyClick(reply.messageId)
}
)
@@ -2238,22 +2207,16 @@ private fun MessageInputBar(
// 🔥 Автофокус при открытии reply панели
LaunchedEffect(hasReply, editTextView) {
if (hasReply) {
android.util.Log.d("EmojiPicker", "═══════════════════════════════════════════════════════")
android.util.Log.d("EmojiPicker", "💬 Reply panel opened, hasReply=$hasReply")
android.util.Log.d("EmojiPicker", " 📊 editTextView=$editTextView, showEmojiPicker=$showEmojiPicker")
// Даём время на создание view если ещё null
kotlinx.coroutines.delay(50)
editTextView?.let { editText ->
// 🔥 НЕ открываем клавиатуру если emoji уже открыт
if (!showEmojiPicker) {
android.util.Log.d("EmojiPicker", " ⌨️ Requesting focus and keyboard for reply...")
editText.requestFocus()
// Открываем клавиатуру
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
android.util.Log.d("EmojiPicker", " ✅ Auto-opened keyboard for reply")
} else {
android.util.Log.d("EmojiPicker", " ⏭️ Skip auto-keyboard for reply (emoji is open)")
}
}
}
@@ -2356,7 +2319,6 @@ private fun MessageInputBar(
val timeSinceLastToggle = currentTime - lastToggleTime
if (timeSinceLastToggle < toggleCooldownMs) {
android.util.Log.d("EmojiPicker", "⏸️ Toggle blocked: ${timeSinceLastToggle}ms < ${toggleCooldownMs}ms")
return
}
@@ -2364,45 +2326,34 @@ private fun MessageInputBar(
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
android.util.Log.d("EmojiPicker", "=".repeat(60))
android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker START")
android.util.Log.d("EmojiPicker", " showEmojiPicker(local)=$showEmojiPicker")
coordinator.logState()
// 🔥 ИСПОЛЬЗУЕМ coordinator.isEmojiVisible вместо showEmojiPicker для более точного состояния
if (coordinator.isEmojiVisible) {
// ========== EMOJI → KEYBOARD ==========
android.util.Log.d("EmojiPicker", "🔄 Switching: Emoji → Keyboard")
coordinator.requestShowKeyboard(
showKeyboard = {
editTextView?.let { editText ->
editText.requestFocus()
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
android.util.Log.d("EmojiPicker", "📱 Keyboard show requested")
}
},
hideEmoji = {
onToggleEmojiPicker(false)
android.util.Log.d("EmojiPicker", "😊 Emoji panel hidden")
}
)
} else {
// ========== KEYBOARD → EMOJI ==========
android.util.Log.d("EmojiPicker", "🔄 Switching: Keyboard → Emoji")
coordinator.requestShowEmoji(
hideKeyboard = {
imm.hideSoftInputFromWindow(view.windowToken, 0)
android.util.Log.d("EmojiPicker", "⌨️ Keyboard hide requested")
},
showEmoji = {
onToggleEmojiPicker(true)
android.util.Log.d("EmojiPicker", "😊 Emoji panel shown")
}
)
}
android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker END")
android.util.Log.d("EmojiPicker", "=".repeat(60))
}
// Функция отправки - НЕ закрывает клавиатуру (UX правило #6)
@@ -2643,24 +2594,12 @@ private fun MessageInputBar(
editTextView = view
},
onFocusChanged = { hasFocus ->
android.util.Log.d("EmojiPicker", "═══════════════════════════════════════════════════════")
android.util.Log.d("EmojiPicker", "🎯 TextField focus changed: hasFocus=$hasFocus")
android.util.Log.d("EmojiPicker", " 📊 Current state:")
android.util.Log.d("EmojiPicker", " - showEmojiPicker=$showEmojiPicker")
android.util.Log.d("EmojiPicker", " - coordinator.isEmojiVisible=${coordinator.isEmojiVisible}")
android.util.Log.d("EmojiPicker", " - coordinator.isKeyboardVisible=${coordinator.isKeyboardVisible}")
android.util.Log.d("EmojiPicker", " - coordinator.currentState=${coordinator.currentState}")
// Если TextField получил фокус И emoji открыт → закрываем emoji
if (hasFocus && showEmojiPicker) {
android.util.Log.d("EmojiPicker", "🔄 TextField focused while emoji open → closing emoji")
android.util.Log.d("EmojiPicker", " 📞 Calling onToggleEmojiPicker(false)...")
onToggleEmojiPicker(false)
android.util.Log.d("EmojiPicker", " ✅ Emoji close requested")
} else if (hasFocus && !showEmojiPicker) {
android.util.Log.d("EmojiPicker", "⌨️ TextField focused with emoji closed → normal keyboard behavior")
} else if (!hasFocus) {
android.util.Log.d("EmojiPicker", "👋 TextField lost focus")
}
}
)

View File

@@ -229,36 +229,24 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
var blobToStore = att.blob // По умолчанию сохраняем оригинальный blob
if (att.type == AttachmentType.MESSAGES && att.blob.isNotEmpty()) {
try {
android.util.Log.d("ReplyDebug", "📥 [RECEIVE] Processing reply attachment:")
android.util.Log.d("ReplyDebug", " - Encrypted blob length: ${att.blob.length}")
android.util.Log.d("ReplyDebug", " - Encrypted blob preview: ${att.blob.take(100)}")
android.util.Log.d("ReplyDebug", " - Decrypting with plainKeyAndNonce (${plainKeyAndNonce.size} bytes)")
android.util.Log.d("ReplyDebug", " - plainKeyAndNonce hex: ${plainKeyAndNonce.joinToString("") { "%02x".format(it) }}")
// 🔥 Расшифровываем с полным plainKeyAndNonce (56 bytes)
// Desktop использует chachaDecryptedKey.toString('utf-8') = полные 56 байт!
val decryptedBlob = MessageCrypto.decryptReplyBlob(att.blob, plainKeyAndNonce)
android.util.Log.d("ReplyDebug", " - Decrypted blob length: ${decryptedBlob.length}")
android.util.Log.d("ReplyDebug", " - Decrypted blob preview: ${decryptedBlob.take(200)}")
// 🔥 Сохраняем расшифрованный blob в БД
blobToStore = decryptedBlob
// Парсим JSON массив с цитируемыми сообщениями
val replyArray = JSONArray(decryptedBlob)
android.util.Log.d("ReplyDebug", " - Reply array length: ${replyArray.length()}")
if (replyArray.length() > 0) {
val firstReply = replyArray.getJSONObject(0)
val replyPublicKey = firstReply.optString("publicKey", "")
val replyText = firstReply.optString("message", "")
val replyMessageId = firstReply.optString("message_id", "")
android.util.Log.d("ReplyDebug", " - Parsed reply: id=$replyMessageId")
android.util.Log.d("ReplyDebug", " publicKey=${replyPublicKey.take(20)}...")
android.util.Log.d("ReplyDebug", " message=${replyText.take(50)}")
// Определяем автора цитаты
val isReplyFromMe = replyPublicKey == myPublicKey
android.util.Log.d("ReplyDebug", " - Is reply from me: $isReplyFromMe")
replyData = ReplyData(
messageId = replyMessageId,
@@ -266,11 +254,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
text = replyText,
isFromMe = isReplyFromMe
)
android.util.Log.d("ReplyDebug", "✅ [RECEIVE] Reply data created successfully")
}
} catch (e: Exception) {
android.util.Log.e("ReplyDebug", "❌ [RECEIVE] Failed to decrypt/parse reply:", e)
android.util.Log.e("ReplyDebug", " - Encrypted blob: ${att.blob.take(100)}")
}
}
@@ -364,7 +349,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
val forwardMessages = ForwardManager.getForwardMessagesForChat(publicKey)
val hasForward = forwardMessages.isNotEmpty()
if (hasForward) {
android.util.Log.d("ChatViewModel", "📨 Will apply ${forwardMessages.size} forward messages")
}
// Сбрасываем состояние
@@ -380,7 +364,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
// 📨 Применяем Forward сообщения СРАЗУ после сброса
if (hasForward) {
android.util.Log.d("ChatViewModel", "📨 Applying ${forwardMessages.size} forward messages NOW")
// Конвертируем ForwardMessage в ReplyMessage
_replyMessages.value = forwardMessages.map { fm ->
ReplyMessage(
@@ -392,7 +375,6 @@ 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 {
@@ -446,7 +428,21 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
return@launch
}
// 🔥 Нет кэша - показываем скелетон и загружаем с задержкой для анимации
// 🔥 Нет кэша - проверяем есть ли вообще сообщения в БД
// Если диалог пустой - не показываем скелетон!
val totalCount = messageDao.getMessageCount(account, dialogKey)
if (totalCount == 0) {
// Пустой диалог - сразу показываем пустое состояние без скелетона
withContext(Dispatchers.Main.immediate) {
_messages.value = emptyList()
_isLoading.value = false
}
isLoadingMessages = false
return@launch
}
// 🔥 Есть сообщения - показываем скелетон и загружаем с задержкой для анимации
if (delayMs > 0) {
withContext(Dispatchers.Main.immediate) {
_isLoading.value = true // Показываем скелетон
@@ -455,9 +451,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
}
// 🔍 Проверяем общее количество сообщений в диалоге
val totalCount = messageDao.getMessageCount(account, dialogKey)
// 🔥 Получаем первую страницу - БЕЗ suspend задержки
val entities = messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0)
@@ -635,10 +628,6 @@ 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 {
@@ -677,35 +666,27 @@ 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,
@@ -758,139 +739,98 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
content: String,
chachaKey: String
): ReplyData? {
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 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", " - type: $type")
// MESSAGES = 1 (цитата)
if (type == 1) {
android.util.Log.d("ReplyDebug", " - ✅ Found MESSAGES type!")
// Данные могут быть в blob или 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
}
// 🔥 Проверяем формат blob - если содержит ":", то это зашифрованный формат "iv:ciphertext"
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 (iv:ciphertext format)")
val privateKey = myPrivateKey
var decryptionSuccess = false
// 🔥 Способ 1: Пробуем расшифровать с приватным ключом (для исходящих сообщений)
if (privateKey != null) {
android.util.Log.d("ReplyDebug", " - Attempting to decrypt with private key...")
try {
val decrypted = CryptoManager.decryptWithPassword(dataJson, privateKey)
if (decrypted != null) {
android.util.Log.d("ReplyDebug", " - ✅ Decrypted with private key")
android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}")
dataJson = decrypted
decryptionSuccess = true
}
} catch (e: Exception) {
android.util.Log.d("ReplyDebug", " - Private key decryption failed: ${e.message}")
}
}
// 🔥 Способ 2: Пробуем расшифровать с ChaCha ключом сообщения (для входящих)
if (!decryptionSuccess && content.isNotEmpty() && chachaKey.isNotEmpty() && privateKey != null) {
android.util.Log.d("ReplyDebug", " - Attempting to decrypt with ChaCha key...")
try {
val decrypted = MessageCrypto.decryptAttachmentBlob(dataJson, chachaKey, privateKey)
if (decrypted != null) {
android.util.Log.d("ReplyDebug", " - ✅ Decrypted with ChaCha key")
android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}")
dataJson = decrypted
decryptionSuccess = true
}
} catch (e: Exception) {
android.util.Log.d("ReplyDebug", " - ChaCha decryption failed: ${e.message}")
}
}
// 🔥 Способ 3: Пробуем decryptReplyBlob с plainKeyAndNonce
if (!decryptionSuccess && content.isNotEmpty() && chachaKey.isNotEmpty() && privateKey != null) {
android.util.Log.d("ReplyDebug", " - Attempting to decrypt with plainKeyAndNonce...")
try {
val decryptResult = MessageCrypto.decryptIncomingFull(content, chachaKey, privateKey)
val plainKeyAndNonce = decryptResult.plainKeyAndNonce
val decrypted = MessageCrypto.decryptReplyBlob(dataJson, plainKeyAndNonce)
if (decrypted.isNotEmpty()) {
android.util.Log.d("ReplyDebug", " - ✅ Decrypted with plainKeyAndNonce")
android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}")
dataJson = decrypted
decryptionSuccess = true
}
} catch (e: Exception) {
android.util.Log.d("ReplyDebug", " - plainKeyAndNonce decryption failed: ${e.message}")
}
}
if (!decryptionSuccess) {
android.util.Log.e("ReplyDebug", " - ❌ All decryption methods failed")
continue
}
} else {
android.util.Log.d("ReplyDebug", " - 📄 Blob is PLAINTEXT (not encrypted)")
}
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 replyMessageIdFromJson = replyMessage.optString("message_id", "")
val replyTimestamp = replyMessage.optLong("timestamp", 0L)
android.util.Log.d("ReplyDebug", " - replyMessageId from JSON: $replyMessageIdFromJson")
android.util.Log.d("ReplyDebug", " - replyPublicKey: ${replyPublicKey.take(20)}...")
android.util.Log.d("ReplyDebug", " - replyText: ${replyText.take(50)}")
android.util.Log.d("ReplyDebug", " - replyTimestamp: $replyTimestamp")
// 🔥 ВАЖНО: message_id из JSON может не совпадать с messageId в Android БД!
// Пытаемся найти реальный messageId в текущих сообщениях по тексту и timestamp
@@ -906,15 +846,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
timestampTo = replyTimestamp + 5000
)?.messageId ?: replyMessageIdFromJson
} catch (e: Exception) {
android.util.Log.w("ReplyDebug", " - ⚠️ Could not find real messageId, using JSON id")
replyMessageIdFromJson
}
android.util.Log.d("ReplyDebug", " - Real messageId: $realMessageId")
// Определяем, кто автор цитируемого сообщения
val isReplyFromMe = replyPublicKey == myPublicKey
android.util.Log.d("ReplyDebug", " - isReplyFromMe: $isReplyFromMe")
val result = ReplyData(
messageId = realMessageId,
@@ -922,20 +859,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
text = replyText,
isFromMe = isReplyFromMe
)
android.util.Log.d("ReplyDebug", " - ✅ Created ReplyData: senderName=${result.senderName}, messageId=${result.messageId}")
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 LOAD] No MESSAGES attachment found")
null
} catch (e: Exception) {
android.util.Log.e("ReplyDebug", "❌ [DB LOAD] Failed to parse reply from attachments:", e)
null
}
}
@@ -1124,13 +1055,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
var replyBlobForDatabase = "" // Зашифрованный blob для БД (приватным ключом)
if (replyMsgsToSend.isNotEmpty()) {
android.util.Log.d("ReplyDebug", "📤 [SEND] Creating reply attachment:")
android.util.Log.d("ReplyDebug", " - Reply messages count: ${replyMsgsToSend.size}")
// Формируем JSON массив с цитируемыми сообщениями (как в RN)
val replyJsonArray = JSONArray()
replyMsgsToSend.forEach { msg ->
android.util.Log.d("ReplyDebug", " - Adding reply: id=${msg.messageId}, publicKey=${msg.publicKey.take(20)}..., text=${msg.text.take(30)}")
val replyJson = JSONObject().apply {
put("message_id", msg.messageId)
put("publicKey", msg.publicKey)
@@ -1142,18 +1070,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
}
val replyBlobPlaintext = replyJsonArray.toString()
android.util.Log.d("ReplyDebug", " - Reply blob plaintext length: ${replyBlobPlaintext.length}")
android.util.Log.d("ReplyDebug", " - Reply blob preview: ${replyBlobPlaintext.take(100)}")
// 🔥 Шифруем reply blob (для network transmission) с ChaCha ключом
android.util.Log.d("ReplyDebug", " - Encrypting reply blob with plainKeyAndNonce (${plainKeyAndNonce.size} bytes)")
val encryptedReplyBlob = MessageCrypto.encryptReplyBlob(replyBlobPlaintext, plainKeyAndNonce)
android.util.Log.d("ReplyDebug", " - Encrypted blob length: ${encryptedReplyBlob.length}")
android.util.Log.d("ReplyDebug", " - Encrypted blob preview: ${encryptedReplyBlob.take(100)}")
// 🔥 Re-encrypt с приватным ключом для хранения в БД (как в Desktop Архиве)
replyBlobForDatabase = CryptoManager.encryptWithPassword(replyBlobPlaintext, privateKey)
android.util.Log.d("ReplyDebug", " - Re-encrypted for DB length: ${replyBlobForDatabase.length}")
val replyAttachmentId = "reply_${timestamp}"
messageAttachments.add(MessageAttachment(
@@ -1162,7 +1084,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
type = AttachmentType.MESSAGES,
preview = ""
))
android.util.Log.d("ReplyDebug", "✅ [SEND] Reply attachment added to packet (encrypted)")
}
val packet = PacketMessage().apply {
@@ -1177,17 +1098,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
}
// 🔥 DEBUG: Log packet before sending
android.util.Log.d("ReplyDebug", "📤 [SEND] About to send packet:")
android.util.Log.d("ReplyDebug", " - messageId: $messageId")
android.util.Log.d("ReplyDebug", " - from: ${sender.take(20)}...")
android.util.Log.d("ReplyDebug", " - to: ${recipient.take(20)}...")
android.util.Log.d("ReplyDebug", " - attachments count: ${packet.attachments.size}")
packet.attachments.forEachIndexed { idx, att ->
android.util.Log.d("ReplyDebug", " - Attachment $idx:")
android.util.Log.d("ReplyDebug", " type: ${att.type.value}")
android.util.Log.d("ReplyDebug", " id: ${att.id}")
android.util.Log.d("ReplyDebug", " blob length: ${att.blob.length}")
android.util.Log.d("ReplyDebug", " blob preview: ${att.blob.take(100)}")
}
// Отправляем пакет

View File

@@ -186,7 +186,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
"$opponentKey:$currentAccount"
}
android.util.Log.d("ChatsListViewModel", "Deleting dialog with key: $dialogKey")
// Удаляем все сообщения из диалога по dialog_key
database.messageDao().deleteDialog(
@@ -205,9 +204,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
opponentKey = opponentKey
)
android.util.Log.d("ChatsListViewModel", "Dialog deleted successfully: $opponentKey")
} catch (e: Exception) {
android.util.Log.e("ChatsListViewModel", "Error deleting dialog", e)
// В случае ошибки - возвращаем диалог обратно (откатываем оптимистичное обновление)
// Flow обновится автоматически из БД
}
@@ -227,7 +224,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
)
)
} catch (e: Exception) {
android.util.Log.e("ChatsListViewModel", "Error blocking user", e)
}
}
@@ -240,7 +236,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
try {
database.blacklistDao().unblockUser(publicKey, currentAccount)
} catch (e: Exception) {
android.util.Log.e("ChatsListViewModel", "Error unblocking user", e)
}
}