feat: Integrate AccountManager to retrieve last logged account in AuthFlow and update MainActivity
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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)}")
|
||||
}
|
||||
|
||||
// Отправляем пакет
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user