From 8bfbba3159ff6ea897e9eae4f96f36a0856eec05 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Wed, 8 Apr 2026 16:24:11 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81:=20=D0=BC=D0=BE=D0=BD?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D1=83=D1=81=20=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=E2=80=94=20DELIVERED=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B5=20=D0=BD=D0=B5=20=D0=BE=D1=82=D0=BA=D0=B0=D1=82=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=20SENT.=20?= =?UTF-8?q?=D0=9B=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8/deliv?= =?UTF-8?q?ery=20=D0=B2=20rosettadev1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messenger/data/MessageRepository.kt | 16 +++- .../messenger/network/ProtocolManager.kt | 20 ++++- .../messenger/ui/chats/ChatViewModel.kt | 83 ++++++++++++++----- 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt index 0c76272..8cce209 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -1009,18 +1009,28 @@ class MessageRepository private constructor(private val context: Context) { } /** Обработка подтверждения доставки */ + private fun devLog(msg: String) { + val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date()) + val line = "$ts [MsgRepo] $msg" + android.util.Log.d("MsgRepo", msg) + try { + val dir = java.io.File(context.filesDir, "crash_reports") + if (!dir.exists()) dir.mkdirs() + java.io.File(dir, "rosettadev1.txt").appendText("$line\n") + } catch (_: Exception) {} + } + suspend fun handleDelivery(packet: PacketDelivery) { val account = currentAccount ?: return - // 📝 LOG: Получено подтверждение доставки + devLog("DELIVERY RECEIVED: msgId=${packet.messageId.take(8)}..., to=${packet.toPublicKey.take(12)}...") + MessageLogger.logDeliveryStatus( messageId = packet.messageId, toPublicKey = packet.toPublicKey, status = "DELIVERED" ) - // Desktop parity: update both delivery status AND timestamp on delivery confirmation. - // Desktop sets timestamp = Date.now() when PacketDelivery arrives (useSynchronize.ts). val deliveryTimestamp = System.currentTimeMillis() messageDao.updateDeliveryStatusAndTimestamp( account, packet.messageId, DeliveryStatus.DELIVERED.value, deliveryTimestamp diff --git a/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt b/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt index b487533..b98e545 100644 --- a/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt +++ b/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt @@ -57,6 +57,18 @@ object ProtocolManager { private var groupRepository: GroupRepository? = null private var appContext: Context? = null private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + private fun protocolDevLog(msg: String) { + val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date()) + val line = "$ts [Protocol] $msg" + android.util.Log.d("Protocol", msg) + try { + val ctx = appContext ?: return + val dir = java.io.File(ctx.filesDir, "crash_reports") + if (!dir.exists()) dir.mkdirs() + java.io.File(dir, "rosettadev1.txt").appendText("$line\n") + } catch (_: Exception) {} + } @Volatile private var packetHandlersRegistered = false @Volatile private var stateMonitoringStarted = false @Volatile private var syncRequestInFlight = false @@ -335,22 +347,24 @@ object ProtocolManager { } // Обработчик доставки (0x08) - // Desktop parity: useDialogFiber.ts updates sync time on delivery (await updateSyncTime(Date.now())) waitPacket(0x08) { packet -> val deliveryPacket = packet as PacketDelivery - + protocolDevLog("PACKET 0x08 DELIVERY: msgId=${deliveryPacket.messageId.take(8)}..., to=${deliveryPacket.toPublicKey.take(12)}...") + launchInboundPacketTask { val repository = messageRepository if (repository == null || !repository.isInitialized()) { + protocolDevLog(" DELIVERY SKIPPED: repo not init") requireResyncAfterAccountInit("⏳ Delivery status before account init, scheduling re-sync") markInboundProcessingFailure("Delivery packet skipped before account init") return@launchInboundPacketTask } try { repository.handleDelivery(deliveryPacket) - // iOS parity: cancel retry timer on delivery ACK resolveOutgoingRetry(deliveryPacket.messageId) + protocolDevLog(" DELIVERY HANDLED OK: ${deliveryPacket.messageId.take(8)}...") } catch (e: Exception) { + protocolDevLog(" DELIVERY ERROR: ${e.javaClass.simpleName}: ${e.message}") markInboundProcessingFailure("Delivery processing failed", e) return@launchInboundPacketTask } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 9925835..0f14dfc 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -37,6 +37,18 @@ import org.json.JSONObject */ class ChatViewModel(application: Application) : AndroidViewModel(application) { + private fun devLog(msg: String) { + val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date()) + val line = "$ts [SendMsg] $msg" + android.util.Log.d("SendMsg", msg) + try { + val ctx = getApplication() + val dir = java.io.File(ctx.filesDir, "crash_reports") + if (!dir.exists()) dir.mkdirs() + java.io.File(dir, "rosettadev1.txt").appendText("$line\n") + } catch (_: Exception) {} + } + companion object { private const val TAG = "ChatViewModel" private const val PAGE_SIZE = 30 @@ -529,23 +541,26 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val opponent = opponentKey ?: return@collect val currentDialogKey = getDialogKey(account, opponent) + devLog("DELIVERY EVENT: msgId=${update.messageId.take(8)}..., status=${update.status}, dialogKey=${update.dialogKey.take(20)}..., currentKey=${currentDialogKey.take(20)}..., match=${update.dialogKey == currentDialogKey}") + if (update.dialogKey == currentDialogKey) { - if (!isDialogActive) return@collect when (update.status) { DeliveryStatus.DELIVERED -> { - // Обновляем конкретное сообщение + devLog(" → updateMessageStatus DELIVERED: ${update.messageId.take(8)}...") updateMessageStatus(update.messageId, MessageStatus.DELIVERED) } DeliveryStatus.ERROR -> { - // Синхронизируем ошибку отправки с открытым диалогом + devLog(" → updateMessageStatus ERROR: ${update.messageId.take(8)}...") updateMessageStatus(update.messageId, MessageStatus.ERROR) } DeliveryStatus.READ -> { - // Помечаем все исходящие как прочитанные + devLog(" → markAllOutgoingAsRead") markAllOutgoingAsRead() } else -> {} } + } else { + devLog(" → SKIPPED (dialog mismatch)") } } } @@ -602,22 +617,45 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { } private fun updateMessageStatus(messageId: String, status: MessageStatus) { + val currentMessages = _messages.value + val currentStatus = currentMessages.find { it.id == messageId }?.status + + devLog("updateMessageStatus: msgId=${messageId.take(8)}..., newStatus=$status, currentStatus=$currentStatus, totalMsgs=${currentMessages.size}") + _messages.value = - _messages.value.map { msg -> + currentMessages.map { msg -> if (msg.id != messageId) return@map msg - // Keep read status monotonic: late DELIVERED must not downgrade READ. - val mergedStatus = - when (status) { - MessageStatus.DELIVERED -> - if (msg.status == MessageStatus.READ) MessageStatus.READ - else MessageStatus.DELIVERED - else -> status - } - if (mergedStatus != msg.status) msg.copy(status = mergedStatus) else msg + // Monotonic status: never downgrade + // SENDING < SENT < DELIVERED < READ + val statusOrder = mapOf( + MessageStatus.ERROR to 0, + MessageStatus.SENDING to 1, + MessageStatus.SENT to 2, + MessageStatus.DELIVERED to 3, + MessageStatus.READ to 4 + ) + val currentOrd = statusOrder[msg.status] ?: 0 + val newOrd = statusOrder[status] ?: 0 + + // ERROR can always override, otherwise only upgrade + val mergedStatus = if (status == MessageStatus.ERROR) { + status + } else if (newOrd > currentOrd) { + status + } else { + msg.status + } + + if (mergedStatus != msg.status) { + devLog(" → STATUS CHANGED: ${msg.status} → $mergedStatus") + msg.copy(status = mergedStatus) + } else { + devLog(" → STATUS NOT UPGRADED: ${msg.status} ≥ $status, skip") + msg + } } - // 🔥 Также обновляем кэш! updateCacheFromCurrentMessages() } @@ -2834,6 +2872,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val messageId = UUID.randomUUID().toString().replace("-", "").take(32) val timestamp = System.currentTimeMillis() + devLog("=== SEND START: msgId=${messageId.take(8)}..., to=${recipient.take(12)}..., text='${text.take(30)}' ===") + devLog(" isForward=$isForward, replyMsgs=${replyMsgsToSend.size}, isConnected=${ProtocolManager.isConnected()}, isAuth=${ProtocolManager.isAuthenticated()}") + // 🔥 Формируем ReplyData для отображения в UI (только первое сообщение) // Используется для обычного reply (не forward). val replyData: ReplyData? = @@ -3093,15 +3134,15 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { packet.attachments.forEachIndexed { idx, att -> } // 📁 Для Saved Messages - НЕ отправляем пакет на сервер - // Только сохраняем локально val isSavedMessages = (sender == recipient) if (!isSavedMessages) { - // Отправляем пакет только для обычных диалогов + devLog(" SENDING packet: msgId=${messageId.take(8)}..., isConnected=${ProtocolManager.isConnected()}, isAuth=${ProtocolManager.isAuthenticated()}") ProtocolManager.send(packet) + devLog(" SENT packet OK: msgId=${messageId.take(8)}...") + } else { + devLog(" Saved Messages — skip send") } - // 3. 🎯 UI обновление в Main потоке - // Для обычных диалогов статус остаётся SENDING до PacketDelivery(messageId). withContext(Dispatchers.Main) { if (isSavedMessages) { updateMessageStatus(messageId, MessageStatus.SENT) @@ -3168,10 +3209,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { saveDialog(text, timestamp) } catch (e: Exception) { + devLog(" SEND ERROR: msgId=${messageId.take(8)}..., ${e.javaClass.simpleName}: ${e.message}") withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.ERROR) } - // Update error status in DB + dialog updateMessageStatusInDb(messageId, DeliveryStatus.ERROR.value) saveDialog(text, timestamp) } finally { @@ -3389,7 +3430,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { attachments = finalMessageAttachments } if (!isSavedMessages) { + devLog(" FWD SENDING: msgId=${messageId.take(8)}..., to=${recipientPublicKey.take(12)}..., isConn=${ProtocolManager.isConnected()}, isAuth=${ProtocolManager.isAuthenticated()}") ProtocolManager.send(packet) + devLog(" FWD SENT OK: msgId=${messageId.take(8)}...") } val finalAttachmentsJson =