feat: Update PacketOnlineSubscribe and PacketTyping to include privateKey and enhance logging

This commit is contained in:
k1ngsterr1
2026-01-12 04:14:28 +05:00
parent 8237c72c17
commit e04010d720
6 changed files with 126 additions and 35 deletions

View File

@@ -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) * Online State packet (ID: 0x05)
* Notify about user online status * Notify about user online status
* Формат как в React Native: массив {publicKey, state}
*/ */
class PacketOnlineState : Packet() { class PacketOnlineState : Packet() {
var publicKey: String = "" var publicKeysState: MutableList<PublicKeyOnlineState> = mutableListOf()
var online: Int = 0
var lastSeen: Long = 0
override fun getPacketId(): Int = 0x05 override fun getPacketId(): Int = 0x05
override fun receive(stream: Stream) { override fun receive(stream: Stream) {
publicKey = stream.readString() val count = stream.readInt8()
online = stream.readInt8() publicKeysState.clear()
lastSeen = stream.readInt64() repeat(count) {
val publicKey = stream.readString()
val isOnline = stream.readBoolean()
publicKeysState.add(PublicKeyOnlineState(publicKey, OnlineState.fromBoolean(isOnline)))
}
} }
override fun send(): Stream { override fun send(): Stream {
val stream = Stream() val stream = Stream()
stream.writeInt16(getPacketId()) 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 return stream
} }
} }
@@ -169,22 +196,37 @@ class PacketOnlineState : Packet() {
/** /**
* Online Subscribe packet (ID: 0x04) * Online Subscribe packet (ID: 0x04)
* Subscribe to user online status updates * Subscribe to user online status updates
* Формат как в React Native: privateKey + array of publicKeys
*/ */
class PacketOnlineSubscribe : Packet() { class PacketOnlineSubscribe : Packet() {
var publicKey: String = "" var privateKey: String = ""
var publicKeys: MutableList<String> = mutableListOf()
override fun getPacketId(): Int = 0x04 override fun getPacketId(): Int = 0x04
override fun receive(stream: Stream) { 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 { override fun send(): Stream {
val stream = Stream() val stream = Stream()
stream.writeInt16(getPacketId()) stream.writeInt16(getPacketId())
stream.writeString(publicKey) stream.writeString(privateKey)
stream.writeInt16(publicKeys.size)
publicKeys.forEach { key ->
stream.writeString(key)
}
return stream return stream
} }
fun addPublicKey(publicKey: String) {
publicKeys.add(publicKey)
}
} }
// ============================================================================ // ============================================================================
@@ -347,14 +389,19 @@ class PacketDelivery : Packet() {
* Typing packet (ID: 0x0B) * Typing packet (ID: 0x0B)
* Уведомление "печатает..." * Уведомление "печатает..."
*/ */
/**
* Typing packet (ID: 0x0B)
* Порядок полей как в React Native: privateKey, fromPublicKey, toPublicKey
*/
class PacketTyping : Packet() { class PacketTyping : Packet() {
var privateKey: String = ""
var fromPublicKey: String = "" var fromPublicKey: String = ""
var toPublicKey: String = "" var toPublicKey: String = ""
var privateKey: String = ""
override fun getPacketId(): Int = 0x0B override fun getPacketId(): Int = 0x0B
override fun receive(stream: Stream) { override fun receive(stream: Stream) {
privateKey = stream.readString()
fromPublicKey = stream.readString() fromPublicKey = stream.readString()
toPublicKey = stream.readString() toPublicKey = stream.readString()
} }
@@ -362,9 +409,9 @@ class PacketTyping : Packet() {
override fun send(): Stream { override fun send(): Stream {
val stream = Stream() val stream = Stream()
stream.writeInt16(getPacketId()) stream.writeInt16(getPacketId())
stream.writeString(privateKey)
stream.writeString(fromPublicKey) stream.writeString(fromPublicKey)
stream.writeString(toPublicKey) stream.writeString(toPublicKey)
stream.writeString(privateKey)
return stream return stream
} }
} }

View File

@@ -301,12 +301,14 @@ class Protocol(
handshakeComplete = false handshakeComplete = false
handshakeJob?.cancel() handshakeJob?.cancel()
heartbeatJob?.cancel() heartbeatJob?.cancel()
webSocket = null // Обнуляем сокет чтобы connect() мог создать новый
// Автоматический reconnect (как в Архиве) // Автоматический reconnect (как в Архиве)
if (!isManuallyClosed) { if (!isManuallyClosed) {
// Сбрасываем счетчик если до этого были подключены // Логируем потерю соединения
if (previousState == ProtocolState.AUTHENTICATED || previousState == ProtocolState.CONNECTED) { 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) { if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {

View File

@@ -330,10 +330,10 @@ fun ChatDetailScreen(
} }
} }
// Аватар // Аватар - используем publicKey для консистентности цвета везде
val avatarColors = val avatarColors =
getAvatarColor( getAvatarColor(
if (isSavedMessages) "SavedMessages" else user.title.ifEmpty { user.publicKey }, if (isSavedMessages) "SavedMessages" else user.publicKey,
isDarkTheme isDarkTheme
) )

View File

@@ -151,19 +151,30 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
// Typing // Typing
ProtocolManager.waitPacket(0x0B) { packet -> ProtocolManager.waitPacket(0x0B) { packet ->
val typingPacket = packet as PacketTyping 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) { if (typingPacket.fromPublicKey == opponentKey) {
ProtocolManager.addLog(" ✅ Match! Showing typing indicator")
showTypingIndicator() showTypingIndicator()
} else {
ProtocolManager.addLog(" ❌ No match, ignoring")
} }
} }
// 🟢 Онлайн статус // 🟢 Онлайн статус (массив publicKey+state как в React Native)
ProtocolManager.waitPacket(0x05) { packet -> ProtocolManager.waitPacket(0x05) { packet ->
val onlinePacket = packet as PacketOnlineState val onlinePacket = packet as PacketOnlineState
if (onlinePacket.publicKey == opponentKey) { ProtocolManager.addLog("🟢 ONLINE STATUS received: ${onlinePacket.publicKeysState.size} entries")
viewModelScope.launch {
_opponentOnline.value = onlinePacket.online == 1 onlinePacket.publicKeysState.forEach { item ->
_opponentLastSeen.value = onlinePacket.lastSeen ProtocolManager.addLog(" Key: ${item.publicKey.take(16)}... State: ${item.state}")
ProtocolManager.addLog("🟢 Online status: ${if (onlinePacket.online == 1) "online" else "offline"}") 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() val now = System.currentTimeMillis()
if (now - lastTypingSentTime < TYPING_THROTTLE_MS) return if (now - lastTypingSentTime < TYPING_THROTTLE_MS) return
val opponent = opponentKey ?: return val opponent = opponentKey ?: run {
val sender = myPublicKey ?: return ProtocolManager.addLog("❌ Typing: No opponent key")
val privateKey = myPrivateKey ?: return 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 lastTypingSentTime = now
@@ -694,16 +714,22 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
try { try {
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey) 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 { val packet = PacketTyping().apply {
this.privateKey = privateKeyHash
fromPublicKey = sender fromPublicKey = sender
toPublicKey = opponent toPublicKey = opponent
this.privateKey = privateKeyHash
} }
ProtocolManager.send(packet) ProtocolManager.send(packet)
ProtocolManager.addLog("⌨️ Typing indicator sent") ProtocolManager.addLog("⌨️ Typing indicator sent")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Typing send error", e) 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() { fun subscribeToOnlineStatus() {
val opponent = opponentKey ?: return val opponent = opponentKey ?: return
val privateKey = myPrivateKey ?: return
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
val packet = PacketOnlineSubscribe().apply { val packet = PacketOnlineSubscribe().apply {
publicKey = opponent this.privateKey = privateKeyHash
addPublicKey(opponent)
} }
ProtocolManager.send(packet) ProtocolManager.send(packet)
ProtocolManager.addLog("🟢 Subscribed to online status: ${opponent.take(16)}...") ProtocolManager.addLog("🟢 Subscribed to online status: ${opponent.take(16)}...")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Online subscribe error", e) Log.e(TAG, "Online subscribe error", e)
ProtocolManager.addLog("❌ Online subscribe error: ${e.message}")
} }
} }
} }

View File

@@ -172,10 +172,9 @@ fun ChatsListScreen(
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E8E) else Color(0xFF8E8E93) val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E8E) else Color(0xFF8E8E93)
// Protocol connection state - commented out due to compilation errors // Protocol connection state
// val protocolState by ProtocolManager.state.collectAsState() val protocolState by ProtocolManager.state.collectAsState()
// val debugLogs by ProtocolManager.debugLogs.collectAsState() val debugLogs by ProtocolManager.debugLogs.collectAsState()
val protocolState = ProtocolState.AUTHENTICATED // Temporary fix
// Dialogs from database // Dialogs from database
val dialogsList by chatsViewModel.dialogs.collectAsState() val dialogsList by chatsViewModel.dialogs.collectAsState()
@@ -279,11 +278,18 @@ fun ChatsListScreen(
Divider(color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)) 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( NavigationDrawerItem(
icon = { Icon(Icons.Outlined.Person, contentDescription = null) }, icon = { Icon(Icons.Outlined.Person, contentDescription = null) },
label = { Text("My Profile") }, label = { Text("My Profile") },
selected = false, selected = false,
colors = drawerItemColors,
onClick = { onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
onProfileClick() onProfileClick()
@@ -294,6 +300,7 @@ fun ChatsListScreen(
icon = { Icon(Icons.Outlined.Settings, contentDescription = null) }, icon = { Icon(Icons.Outlined.Settings, contentDescription = null) },
label = { Text("Settings") }, label = { Text("Settings") },
selected = false, selected = false,
colors = drawerItemColors,
onClick = { onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
onSettingsClick() onSettingsClick()
@@ -310,6 +317,7 @@ fun ChatsListScreen(
}, },
label = { Text(if (isDarkTheme) "Light Mode" else "Dark Mode") }, label = { Text(if (isDarkTheme) "Light Mode" else "Dark Mode") },
selected = false, selected = false,
colors = drawerItemColors,
onClick = { onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
onToggleTheme() onToggleTheme()
@@ -331,6 +339,9 @@ fun ChatsListScreen(
}, },
label = { Text("Log Out", color = Color(0xFFFF3B30)) }, label = { Text("Log Out", color = Color(0xFFFF3B30)) },
selected = false, selected = false,
colors = NavigationDrawerItemDefaults.colors(
unselectedContainerColor = Color.Transparent
),
onClick = { onClick = {
scope.launch { scope.launch {
drawerState.close() drawerState.close()

View File

@@ -111,10 +111,10 @@ private fun SearchResultItem(
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8) val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
// Получаем цвета аватара // Получаем цвета аватара - используем publicKey для консистентности
val avatarColors = val avatarColors =
getAvatarColor( getAvatarColor(
if (isOwnAccount) "SavedMessages" else (user.title.ifEmpty { user.publicKey }), if (isOwnAccount) "SavedMessages" else user.publicKey,
isDarkTheme isDarkTheme
) )