From e04010d7205e094abe303b1a5a9ceab25011388c Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 12 Jan 2026 04:14:28 +0500 Subject: [PATCH] feat: Update PacketOnlineSubscribe and PacketTyping to include privateKey and enhance logging --- .../com/rosetta/messenger/network/Packets.kt | 71 +++++++++++++++---- .../com/rosetta/messenger/network/Protocol.kt | 6 +- .../messenger/ui/chats/ChatDetailScreen.kt | 4 +- .../messenger/ui/chats/ChatViewModel.kt | 55 ++++++++++---- .../messenger/ui/chats/ChatsListScreen.kt | 21 ++++-- .../messenger/ui/chats/SearchResultsList.kt | 4 +- 6 files changed, 126 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/network/Packets.kt b/app/src/main/java/com/rosetta/messenger/network/Packets.kt index e9572f2..5cfca51 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Packets.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Packets.kt @@ -141,27 +141,54 @@ class PacketUserInfo : Packet() { } } +/** + * Online State enum + */ +enum class OnlineState(val value: Int) { + ONLINE(0), + OFFLINE(1); + + companion object { + fun fromBoolean(isOnline: Boolean) = if (isOnline) ONLINE else OFFLINE + } +} + +/** + * Public key with online state + */ +data class PublicKeyOnlineState( + val publicKey: String, + val state: OnlineState +) + /** * Online State packet (ID: 0x05) * Notify about user online status + * Формат как в React Native: массив {publicKey, state} */ class PacketOnlineState : Packet() { - var publicKey: String = "" - var online: Int = 0 - var lastSeen: Long = 0 + var publicKeysState: MutableList = mutableListOf() override fun getPacketId(): Int = 0x05 override fun receive(stream: Stream) { - publicKey = stream.readString() - online = stream.readInt8() - lastSeen = stream.readInt64() + val count = stream.readInt8() + publicKeysState.clear() + repeat(count) { + val publicKey = stream.readString() + val isOnline = stream.readBoolean() + publicKeysState.add(PublicKeyOnlineState(publicKey, OnlineState.fromBoolean(isOnline))) + } } override fun send(): Stream { val stream = Stream() stream.writeInt16(getPacketId()) - stream.writeString(publicKey) + stream.writeInt8(publicKeysState.size) + publicKeysState.forEach { item -> + stream.writeString(item.publicKey) + stream.writeBoolean(item.state == OnlineState.ONLINE) + } return stream } } @@ -169,22 +196,37 @@ class PacketOnlineState : Packet() { /** * Online Subscribe packet (ID: 0x04) * Subscribe to user online status updates + * Формат как в React Native: privateKey + array of publicKeys */ class PacketOnlineSubscribe : Packet() { - var publicKey: String = "" + var privateKey: String = "" + var publicKeys: MutableList = mutableListOf() override fun getPacketId(): Int = 0x04 override fun receive(stream: Stream) { - publicKey = stream.readString() + privateKey = stream.readString() + val keysCount = stream.readInt16() + publicKeys.clear() + repeat(keysCount) { + publicKeys.add(stream.readString()) + } } override fun send(): Stream { val stream = Stream() stream.writeInt16(getPacketId()) - stream.writeString(publicKey) + stream.writeString(privateKey) + stream.writeInt16(publicKeys.size) + publicKeys.forEach { key -> + stream.writeString(key) + } return stream } + + fun addPublicKey(publicKey: String) { + publicKeys.add(publicKey) + } } // ============================================================================ @@ -347,14 +389,19 @@ class PacketDelivery : Packet() { * Typing packet (ID: 0x0B) * Уведомление "печатает..." */ +/** + * Typing packet (ID: 0x0B) + * Порядок полей как в React Native: privateKey, fromPublicKey, toPublicKey + */ class PacketTyping : Packet() { + var privateKey: String = "" var fromPublicKey: String = "" var toPublicKey: String = "" - var privateKey: String = "" override fun getPacketId(): Int = 0x0B override fun receive(stream: Stream) { + privateKey = stream.readString() fromPublicKey = stream.readString() toPublicKey = stream.readString() } @@ -362,9 +409,9 @@ class PacketTyping : Packet() { override fun send(): Stream { val stream = Stream() stream.writeInt16(getPacketId()) + stream.writeString(privateKey) stream.writeString(fromPublicKey) stream.writeString(toPublicKey) - stream.writeString(privateKey) return stream } } diff --git a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt index 9543288..78dcf8a 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt @@ -301,12 +301,14 @@ class Protocol( handshakeComplete = false handshakeJob?.cancel() heartbeatJob?.cancel() + webSocket = null // Обнуляем сокет чтобы connect() мог создать новый // Автоматический reconnect (как в Архиве) if (!isManuallyClosed) { - // Сбрасываем счетчик если до этого были подключены + // Логируем потерю соединения if (previousState == ProtocolState.AUTHENTICATED || previousState == ProtocolState.CONNECTED) { - log("🔄 Connection lost, will reconnect...") + log("🔄 Connection lost from $previousState, will reconnect...") + reconnectAttempts = 0 // Сбрасываем счётчик при неожиданной потере } if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 5451fbe..ae92b83 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -330,10 +330,10 @@ fun ChatDetailScreen( } } - // Аватар + // Аватар - используем publicKey для консистентности цвета везде val avatarColors = getAvatarColor( - if (isSavedMessages) "SavedMessages" else user.title.ifEmpty { user.publicKey }, + if (isSavedMessages) "SavedMessages" else user.publicKey, isDarkTheme ) 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 c700d86..6f0e4bc 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 @@ -151,19 +151,30 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { // Typing ProtocolManager.waitPacket(0x0B) { packet -> val typingPacket = packet as PacketTyping + ProtocolManager.addLog("⌨️ TYPING received from: ${typingPacket.fromPublicKey.take(16)}...") + ProtocolManager.addLog(" My opponent: ${opponentKey?.take(16)}...") if (typingPacket.fromPublicKey == opponentKey) { + ProtocolManager.addLog(" ✅ Match! Showing typing indicator") showTypingIndicator() + } else { + ProtocolManager.addLog(" ❌ No match, ignoring") } } - // 🟢 Онлайн статус + // 🟢 Онлайн статус (массив publicKey+state как в React Native) ProtocolManager.waitPacket(0x05) { packet -> val onlinePacket = packet as PacketOnlineState - if (onlinePacket.publicKey == opponentKey) { - viewModelScope.launch { - _opponentOnline.value = onlinePacket.online == 1 - _opponentLastSeen.value = onlinePacket.lastSeen - ProtocolManager.addLog("🟢 Online status: ${if (onlinePacket.online == 1) "online" else "offline"}") + ProtocolManager.addLog("🟢 ONLINE STATUS received: ${onlinePacket.publicKeysState.size} entries") + + onlinePacket.publicKeysState.forEach { item -> + ProtocolManager.addLog(" Key: ${item.publicKey.take(16)}... State: ${item.state}") + ProtocolManager.addLog(" My opponent: ${opponentKey?.take(16)}...") + + if (item.publicKey == opponentKey) { + ProtocolManager.addLog(" ✅ Match! Updating UI - online: ${item.state == OnlineState.ONLINE}") + viewModelScope.launch { + _opponentOnline.value = item.state == OnlineState.ONLINE + } } } } @@ -684,9 +695,18 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val now = System.currentTimeMillis() if (now - lastTypingSentTime < TYPING_THROTTLE_MS) return - val opponent = opponentKey ?: return - val sender = myPublicKey ?: return - val privateKey = myPrivateKey ?: return + val opponent = opponentKey ?: run { + ProtocolManager.addLog("❌ Typing: No opponent key") + return + } + val sender = myPublicKey ?: run { + ProtocolManager.addLog("❌ Typing: No sender key") + return + } + val privateKey = myPrivateKey ?: run { + ProtocolManager.addLog("❌ Typing: No private key") + return + } lastTypingSentTime = now @@ -694,16 +714,22 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { try { val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey) + ProtocolManager.addLog("⌨️ Sending typing...") + ProtocolManager.addLog(" From: ${sender.take(16)}...") + ProtocolManager.addLog(" To: ${opponent.take(16)}...") + ProtocolManager.addLog(" PrivateHash: ${privateKeyHash.take(16)}...") + val packet = PacketTyping().apply { + this.privateKey = privateKeyHash fromPublicKey = sender toPublicKey = opponent - this.privateKey = privateKeyHash } ProtocolManager.send(packet) - ProtocolManager.addLog("⌨️ Typing indicator sent") + ProtocolManager.addLog("⌨️ Typing indicator sent ✅") } catch (e: Exception) { Log.e(TAG, "Typing send error", e) + ProtocolManager.addLog("❌ Typing send error: ${e.message}") } } } @@ -762,17 +788,22 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { */ fun subscribeToOnlineStatus() { val opponent = opponentKey ?: return + val privateKey = myPrivateKey ?: return viewModelScope.launch(Dispatchers.IO) { try { + val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey) + val packet = PacketOnlineSubscribe().apply { - publicKey = opponent + this.privateKey = privateKeyHash + addPublicKey(opponent) } ProtocolManager.send(packet) ProtocolManager.addLog("🟢 Subscribed to online status: ${opponent.take(16)}...") } catch (e: Exception) { Log.e(TAG, "Online subscribe error", e) + ProtocolManager.addLog("❌ Online subscribe error: ${e.message}") } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index 5801d3d..365ab27 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -172,10 +172,9 @@ fun ChatsListScreen( val textColor = if (isDarkTheme) Color.White else Color.Black val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E8E) else Color(0xFF8E8E93) - // Protocol connection state - commented out due to compilation errors - // val protocolState by ProtocolManager.state.collectAsState() - // val debugLogs by ProtocolManager.debugLogs.collectAsState() - val protocolState = ProtocolState.AUTHENTICATED // Temporary fix + // Protocol connection state + val protocolState by ProtocolManager.state.collectAsState() + val debugLogs by ProtocolManager.debugLogs.collectAsState() // Dialogs from database val dialogsList by chatsViewModel.dialogs.collectAsState() @@ -279,11 +278,18 @@ fun ChatsListScreen( Divider(color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)) - // Menu Items + // Menu Items - прозрачный фон без странных цветов + val drawerItemColors = NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent, + unselectedIconColor = if (isDarkTheme) Color.White else Color.Black, + unselectedTextColor = textColor + ) + NavigationDrawerItem( icon = { Icon(Icons.Outlined.Person, contentDescription = null) }, label = { Text("My Profile") }, selected = false, + colors = drawerItemColors, onClick = { scope.launch { drawerState.close() } onProfileClick() @@ -294,6 +300,7 @@ fun ChatsListScreen( icon = { Icon(Icons.Outlined.Settings, contentDescription = null) }, label = { Text("Settings") }, selected = false, + colors = drawerItemColors, onClick = { scope.launch { drawerState.close() } onSettingsClick() @@ -310,6 +317,7 @@ fun ChatsListScreen( }, label = { Text(if (isDarkTheme) "Light Mode" else "Dark Mode") }, selected = false, + colors = drawerItemColors, onClick = { scope.launch { drawerState.close() } onToggleTheme() @@ -331,6 +339,9 @@ fun ChatsListScreen( }, label = { Text("Log Out", color = Color(0xFFFF3B30)) }, selected = false, + colors = NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent + ), onClick = { scope.launch { drawerState.close() diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/SearchResultsList.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/SearchResultsList.kt index 4a49788..3adb33b 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/SearchResultsList.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/SearchResultsList.kt @@ -111,10 +111,10 @@ private fun SearchResultItem( val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8) - // Получаем цвета аватара + // Получаем цвета аватара - используем publicKey для консистентности val avatarColors = getAvatarColor( - if (isOwnAccount) "SavedMessages" else (user.title.ifEmpty { user.publicKey }), + if (isOwnAccount) "SavedMessages" else user.publicKey, isDarkTheme )