feat: Update PacketOnlineSubscribe and PacketTyping to include privateKey and enhance logging
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user