feat: Integrate AccountManager to retrieve last logged account in AuthFlow and update MainActivity
This commit is contained in:
@@ -158,6 +158,7 @@ class MainActivity : ComponentActivity() {
|
||||
isDarkTheme = isDarkTheme,
|
||||
hasExistingAccount = screen == "auth_unlock",
|
||||
accounts = accountInfoList,
|
||||
accountManager = accountManager,
|
||||
onAuthComplete = { account ->
|
||||
currentAccount = account
|
||||
hasExistingAccount = true
|
||||
|
||||
@@ -332,11 +332,6 @@ object CryptoManager {
|
||||
// Decompress (zlib inflate - совместимо с pako.inflate в JS)
|
||||
String(decompress(decrypted), Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ReplyDebug", "❌ [DECRYPT] decryptWithPassword failed:", e)
|
||||
android.util.Log.e("ReplyDebug", " - Input length: ${encryptedData.length}")
|
||||
android.util.Log.e("ReplyDebug", " - Input preview: ${encryptedData.take(100)}")
|
||||
android.util.Log.e("ReplyDebug", " - Password length: ${password.length}")
|
||||
android.util.Log.e("ReplyDebug", " - Exception: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,15 +606,10 @@ object MessageCrypto {
|
||||
*/
|
||||
fun encryptReplyBlob(replyJson: String, plainKeyAndNonce: ByteArray): String {
|
||||
return try {
|
||||
android.util.Log.d("ReplyDebug", "🔐 encryptReplyBlob called:")
|
||||
android.util.Log.d("ReplyDebug", " - plainKeyAndNonce size: ${plainKeyAndNonce.size}")
|
||||
android.util.Log.d("ReplyDebug", " - plainKeyAndNonce hex: ${plainKeyAndNonce.joinToString("") { "%02x".format(it) }}")
|
||||
|
||||
// Convert plainKeyAndNonce to string - simulate JS Buffer.toString('utf-8') behavior
|
||||
// which replaces invalid UTF-8 sequences with U+FFFD
|
||||
val password = bytesToJsUtf8String(plainKeyAndNonce)
|
||||
android.util.Log.d("ReplyDebug", " - password length: ${password.length}")
|
||||
android.util.Log.d("ReplyDebug", " - password bytes (first 20): ${password.toByteArray(Charsets.UTF_8).take(20).joinToString(",")}")
|
||||
|
||||
// Compress with pako (deflate)
|
||||
val deflater = java.util.zip.Deflater()
|
||||
@@ -817,37 +812,25 @@ object MessageCrypto {
|
||||
*/
|
||||
fun decryptReplyBlob(encryptedBlob: String, plainKeyAndNonce: ByteArray): String {
|
||||
return try {
|
||||
android.util.Log.d("ReplyDebug", "🔓 decryptReplyBlob called:")
|
||||
android.util.Log.d("ReplyDebug", " - Input length: ${encryptedBlob.length}")
|
||||
android.util.Log.d("ReplyDebug", " - plainKeyAndNonce size: ${plainKeyAndNonce.size}")
|
||||
android.util.Log.d("ReplyDebug", " - plainKeyAndNonce hex: ${plainKeyAndNonce.joinToString("") { "%02x".format(it) }}")
|
||||
|
||||
// Check if it's encrypted format (contains ':')
|
||||
if (!encryptedBlob.contains(':')) {
|
||||
android.util.Log.d("ReplyDebug", " - No ':' found, returning as-is")
|
||||
return encryptedBlob
|
||||
}
|
||||
|
||||
// Parse ivBase64:ciphertextBase64
|
||||
val parts = encryptedBlob.split(':')
|
||||
if (parts.size != 2) {
|
||||
android.util.Log.d("ReplyDebug", " - Invalid format (not 2 parts), returning as-is")
|
||||
return encryptedBlob
|
||||
}
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - IV part length: ${parts[0].length}")
|
||||
android.util.Log.d("ReplyDebug", " - Ciphertext part length: ${parts[1].length}")
|
||||
|
||||
val iv = Base64.decode(parts[0], Base64.DEFAULT)
|
||||
val ciphertext = Base64.decode(parts[1], Base64.DEFAULT)
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Decoded IV size: ${iv.size}")
|
||||
android.util.Log.d("ReplyDebug", " - Decoded ciphertext size: ${ciphertext.size}")
|
||||
|
||||
// Password from plainKeyAndNonce - use same JS-like UTF-8 conversion
|
||||
val password = bytesToJsUtf8String(plainKeyAndNonce)
|
||||
android.util.Log.d("ReplyDebug", " - Password length: ${password.length}")
|
||||
android.util.Log.d("ReplyDebug", " - Password bytes hex: ${password.toByteArray(Charsets.UTF_8).joinToString("") { "%02x".format(it) }}")
|
||||
|
||||
// PBKDF2 key derivation
|
||||
// CRITICAL: Must use SHA256 to match React Native (not SHA1!)
|
||||
@@ -861,7 +844,6 @@ object MessageCrypto {
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val keyBytes = secretKey.encoded
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Derived key size: ${keyBytes.size}")
|
||||
|
||||
// AES-CBC decryption
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
@@ -870,7 +852,6 @@ object MessageCrypto {
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
|
||||
val decompressed = cipher.doFinal(ciphertext)
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Decrypted (compressed) size: ${decompressed.size}")
|
||||
|
||||
// Decompress with inflate
|
||||
val inflater = java.util.zip.Inflater()
|
||||
@@ -880,14 +861,9 @@ object MessageCrypto {
|
||||
inflater.end()
|
||||
val plaintext = String(outputBuffer, 0, outputSize, Charsets.UTF_8)
|
||||
|
||||
android.util.Log.d("ReplyDebug", " - Decompressed plaintext length: ${plaintext.length}")
|
||||
android.util.Log.d("ReplyDebug", " - Plaintext preview: ${plaintext.take(100)}")
|
||||
android.util.Log.d("ReplyDebug", "✅ decryptReplyBlob success")
|
||||
|
||||
plaintext
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ReplyDebug", "❌ decryptReplyBlob failed:", e)
|
||||
android.util.Log.e("ReplyDebug", " - Exception: ${e.javaClass.simpleName}: ${e.message}")
|
||||
// Return as-is, might be plain JSON
|
||||
encryptedBlob
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ object ForwardManager {
|
||||
messages: List<ForwardMessage>,
|
||||
showPicker: Boolean = true
|
||||
) {
|
||||
android.util.Log.d("ForwardManager", "📨 Setting forward messages: ${messages.size}")
|
||||
_forwardMessages.value = messages
|
||||
if (showPicker) {
|
||||
_showChatPicker.value = true
|
||||
@@ -60,7 +59,6 @@ object ForwardManager {
|
||||
* Выбрать чат для пересылки
|
||||
*/
|
||||
fun selectChat(publicKey: String) {
|
||||
android.util.Log.d("ForwardManager", "📨 Selected chat: $publicKey")
|
||||
_selectedChatPublicKey.value = publicKey
|
||||
_showChatPicker.value = false
|
||||
}
|
||||
@@ -69,7 +67,6 @@ object ForwardManager {
|
||||
* Скрыть выбор чата (отмена)
|
||||
*/
|
||||
fun hideChatPicker() {
|
||||
android.util.Log.d("ForwardManager", "📨 Hide chat picker")
|
||||
_showChatPicker.value = false
|
||||
}
|
||||
|
||||
@@ -79,7 +76,6 @@ object ForwardManager {
|
||||
*/
|
||||
fun consumeForwardMessages(): List<ForwardMessage> {
|
||||
val messages = _forwardMessages.value
|
||||
android.util.Log.d("ForwardManager", "📨 Consuming forward messages: ${messages.size}")
|
||||
return messages
|
||||
}
|
||||
|
||||
@@ -87,7 +83,6 @@ object ForwardManager {
|
||||
* Очистить все данные (после применения или отмены)
|
||||
*/
|
||||
fun clear() {
|
||||
android.util.Log.d("ForwardManager", "📨 Clearing forward state")
|
||||
_forwardMessages.value = emptyList()
|
||||
_showChatPicker.value = false
|
||||
_selectedChatPublicKey.value = null
|
||||
@@ -104,7 +99,6 @@ object ForwardManager {
|
||||
fun hasForwardMessagesForChat(publicKey: String): Boolean {
|
||||
val selectedKey = _selectedChatPublicKey.value
|
||||
val hasMessages = _forwardMessages.value.isNotEmpty()
|
||||
android.util.Log.d("ForwardManager", "📨 hasForwardMessagesForChat($publicKey): selectedKey=$selectedKey, hasMessages=$hasMessages")
|
||||
return selectedKey == publicKey && hasMessages
|
||||
}
|
||||
|
||||
@@ -115,7 +109,6 @@ object ForwardManager {
|
||||
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()
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.*
|
||||
import com.rosetta.messenger.data.DecryptedAccount
|
||||
import com.rosetta.messenger.data.AccountManager
|
||||
|
||||
enum class AuthScreen {
|
||||
SELECT_ACCOUNT,
|
||||
@@ -21,6 +22,7 @@ fun AuthFlow(
|
||||
isDarkTheme: Boolean,
|
||||
hasExistingAccount: Boolean,
|
||||
accounts: List<AccountInfo> = emptyList(),
|
||||
accountManager: AccountManager,
|
||||
onAuthComplete: (DecryptedAccount?) -> Unit,
|
||||
onLogout: () -> Unit = {}
|
||||
) {
|
||||
@@ -33,7 +35,12 @@ fun AuthFlow(
|
||||
)
|
||||
}
|
||||
var seedPhrase by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||
var selectedAccountId by remember { mutableStateOf<String?>(accounts.firstOrNull()?.id) }
|
||||
// Use last logged account or fallback to first account
|
||||
var selectedAccountId by remember {
|
||||
mutableStateOf<String?>(
|
||||
accountManager.getLastLoggedPublicKey() ?: accounts.firstOrNull()?.id
|
||||
)
|
||||
}
|
||||
var showCreateModal by remember { mutableStateOf(false) }
|
||||
|
||||
// Handle system back button
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -269,11 +269,7 @@ fun AppleEmojiTextField(
|
||||
editTextView = this
|
||||
// Подключаем callback для изменения фокуса
|
||||
setOnFocusChangeListener { _, hasFocus ->
|
||||
android.util.Log.d("AppleEmojiTextField", "═══════════════════════════════════════════════════════")
|
||||
android.util.Log.d("AppleEmojiTextField", "🎯 Native EditText focus changed: hasFocus=$hasFocus")
|
||||
android.util.Log.d("AppleEmojiTextField", " 📍 Calling onFocusChanged callback...")
|
||||
onFocusChanged?.invoke(hasFocus)
|
||||
android.util.Log.d("AppleEmojiTextField", " ✅ onFocusChanged callback completed")
|
||||
}
|
||||
// Уведомляем о создании view
|
||||
onViewCreated?.invoke(this)
|
||||
|
||||
@@ -36,11 +36,6 @@ object KeyboardHeightProvider {
|
||||
val savedPx = prefs.getInt(KEY_KEYBOARD_HEIGHT, defaultPx)
|
||||
val isDefault = savedPx == defaultPx
|
||||
|
||||
android.util.Log.d(TAG, "═══════════════════════════════════════")
|
||||
android.util.Log.d(TAG, "📖 getSavedKeyboardHeight()")
|
||||
android.util.Log.d(TAG, " 📏 Height: ${savedPx}px (${pxToDp(context, savedPx)}dp)")
|
||||
android.util.Log.d(TAG, " 📦 Source: ${if (isDefault) "DEFAULT" else "SAVED"}")
|
||||
android.util.Log.d(TAG, "═══════════════════════════════════════")
|
||||
return savedPx
|
||||
}
|
||||
|
||||
@@ -48,8 +43,6 @@ object KeyboardHeightProvider {
|
||||
* Сохранить высоту клавиатуры
|
||||
*/
|
||||
fun saveKeyboardHeight(context: Context, heightPx: Int) {
|
||||
android.util.Log.d(TAG, "═══════════════════════════════════════")
|
||||
android.util.Log.d(TAG, "💾 saveKeyboardHeight($heightPx px)")
|
||||
|
||||
if (heightPx > 0) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
@@ -58,19 +51,12 @@ object KeyboardHeightProvider {
|
||||
|
||||
prefs.edit().putInt(KEY_KEYBOARD_HEIGHT, heightPx).apply()
|
||||
|
||||
android.util.Log.d(TAG, " 📏 New: ${heightPx}px (${pxToDp(context, heightPx)}dp)")
|
||||
android.util.Log.d(TAG, " 📏 Old: ${oldHeight}px (${pxToDp(context, oldHeight)}dp)")
|
||||
android.util.Log.d(TAG, " 🔄 Changed: $changed")
|
||||
|
||||
if (changed) {
|
||||
android.util.Log.d(TAG, " ✅ SAVED successfully!")
|
||||
} else {
|
||||
android.util.Log.d(TAG, " ⏭️ Same height, no change")
|
||||
}
|
||||
} else {
|
||||
android.util.Log.w(TAG, " ⚠️ INVALID height: ${heightPx}px - NOT saved!")
|
||||
}
|
||||
android.util.Log.d(TAG, "═══════════════════════════════════════")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +64,6 @@ object KeyboardHeightProvider {
|
||||
* Telegram использует: AdjustPanLayoutHelper.keyboardDuration (250ms обычно)
|
||||
*/
|
||||
fun getKeyboardAnimationDuration(): Long {
|
||||
android.util.Log.d(TAG, "⏱️ getKeyboardAnimationDuration() = 250ms")
|
||||
return 250L
|
||||
}
|
||||
|
||||
@@ -107,9 +92,6 @@ fun rememberSavedKeyboardHeight(): Dp {
|
||||
val density = context.resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).dp
|
||||
|
||||
android.util.Log.d("KeyboardHeight", "🎯 rememberSavedKeyboardHeight()")
|
||||
android.util.Log.d("KeyboardHeight", " 📏 Result: $heightDp (${heightPx}px)")
|
||||
android.util.Log.d("KeyboardHeight", " 📱 Density: $density")
|
||||
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@@ -61,11 +61,9 @@ object OptimizedEmojiCache {
|
||||
loadProgress = 1f
|
||||
}
|
||||
|
||||
android.util.Log.d("EmojiCache", "✅ Предзагрузка завершена за $duration ms")
|
||||
isLoaded = true
|
||||
isPreloading = false
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("EmojiCache", "❌ Ошибка предзагрузки", e)
|
||||
allEmojis = emptyList()
|
||||
emojisByCategory = emptyMap()
|
||||
isLoaded = true
|
||||
@@ -85,7 +83,6 @@ object OptimizedEmojiCache {
|
||||
?: emptyList()
|
||||
|
||||
allEmojis = emojis
|
||||
android.util.Log.d("EmojiCache", "📦 Загружено ${emojis.size} эмодзи")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +122,6 @@ object OptimizedEmojiCache {
|
||||
}
|
||||
}
|
||||
|
||||
android.util.Log.d("EmojiCache", "🗂️ Эмодзи сгруппированы по ${result.size} категориям")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +136,6 @@ object OptimizedEmojiCache {
|
||||
?.take(PRELOAD_COUNT)
|
||||
?: emptyList()
|
||||
|
||||
android.util.Log.d("EmojiCache", "🎨 Предзагружаем ${smileysToPreload.size} популярных эмодзи...")
|
||||
|
||||
// Предзагружаем параллельно, но с ограничением
|
||||
val jobs = smileysToPreload.chunked(20).map { chunk ->
|
||||
@@ -164,7 +159,6 @@ object OptimizedEmojiCache {
|
||||
}
|
||||
|
||||
jobs.awaitAll()
|
||||
android.util.Log.d("EmojiCache", "✅ Предзагружено $preloadedCount изображений")
|
||||
isPreloading = false
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ fun OptimizedEmojiPicker(
|
||||
|
||||
// 🔥 Логирование изменений видимости
|
||||
LaunchedEffect(isVisible) {
|
||||
android.util.Log.d("EmojiPicker", "🎭 OptimizedEmojiPicker visibility: $isVisible (height=${savedKeyboardHeight})")
|
||||
}
|
||||
|
||||
// 🔥 Рендерим контент напрямую без AnimatedVisibility
|
||||
@@ -102,19 +101,15 @@ private fun EmojiPickerContent(
|
||||
var shouldRenderContent by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
android.util.Log.d("EmojiPicker", "🚀 EmojiPickerContent started, keyboardHeight=$keyboardHeight")
|
||||
|
||||
// Ждём 1 кадр чтобы анимация началась плавно
|
||||
kotlinx.coroutines.delay(16) // ~1 frame at 60fps
|
||||
shouldRenderContent = true
|
||||
android.util.Log.d("EmojiPicker", "✅ Content rendering enabled after 16ms delay")
|
||||
|
||||
// Загружаем эмодзи если еще не загружены
|
||||
if (!OptimizedEmojiCache.isLoaded) {
|
||||
android.util.Log.d("EmojiPicker", "📦 Starting emoji preload...")
|
||||
OptimizedEmojiCache.preload(context)
|
||||
} else {
|
||||
android.util.Log.d("EmojiPicker", "✅ Emojis already loaded")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,11 +126,9 @@ private fun EmojiPickerContent(
|
||||
|
||||
// 🚀 При смене категории плавно скроллим наверх
|
||||
LaunchedEffect(selectedCategory) {
|
||||
android.util.Log.d("EmojiPicker", "📂 Category changed: ${selectedCategory.key} (${displayedEmojis.size} emojis)")
|
||||
if (displayedEmojis.isNotEmpty()) {
|
||||
scope.launch {
|
||||
gridState.animateScrollToItem(0)
|
||||
android.util.Log.v("EmojiPicker", "⬆️ Scrolled to top")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user