feat: Save last logged account public key after authentication for improved user experience

This commit is contained in:
k1ngsterr1
2026-01-12 17:16:44 +05:00
parent 67e99901be
commit 8aa17383cf
6 changed files with 117 additions and 27 deletions

View File

@@ -146,6 +146,8 @@ class MainActivity : ComponentActivity() {
onAuthComplete = { account ->
currentAccount = account
hasExistingAccount = true
// Save as last logged account
accountManager.setLastLoggedPublicKey(account.publicKey)
// Reload accounts list
scope.launch {
val accounts = accountManager.getAllAccounts()

View File

@@ -0,0 +1,68 @@
package com.rosetta.messenger.network
import okhttp3.WebSocket
import okio.ByteString
import kotlin.random.Random
/**
* Chunker for splitting large packets into chunks
* Matches Desktop implementation exactly
*/
class Chunker(
private val webSocket: WebSocket,
private val logger: (String) -> Unit = {}
) {
companion object {
private const val MAX_PACKET_SIZE_BEFORE_SLICE = 1024 // bytes
private const val MAX_CHUNK_SIZE = 1024 // bytes
}
private fun log(message: String) {
logger(message)
}
/**
* Send stream, chunking if necessary
*/
fun send(stream: Stream) {
val data = stream.getStream()
// If packet is small enough, send directly
if (data.size <= MAX_PACKET_SIZE_BEFORE_SLICE) {
webSocket.send(ByteString.of(*data))
return
}
// Split into chunks
val totalChunks = (data.size + MAX_CHUNK_SIZE - 1) / MAX_CHUNK_SIZE
val chunkId = generateChunkId()
log("📦 Chunking packet: ${data.size} bytes -> $totalChunks chunks (id: $chunkId)")
for (i in 0 until totalChunks) {
val start = i * MAX_CHUNK_SIZE
val end = minOf((i + 1) * MAX_CHUNK_SIZE, data.size)
val chunkData = data.sliceArray(start until end)
val chunkPacket = PacketChunk().apply {
this.chunkId = chunkId
this.chunkIndex = i
this.totalChunks = totalChunks
this.data = chunkData
}
val chunkStream = chunkPacket.send()
webSocket.send(ByteString.of(*chunkStream.getStream()))
log("📦 Sent chunk $i/$totalChunks (${chunkData.size} bytes)")
}
}
/**
* Generate random chunk ID (like Desktop version)
*/
private fun generateChunkId(): String {
val chars = "abcdefghijklmnopqrstuvwxyz0123456789"
return (1..26).map { chars[Random.nextInt(chars.length)] }.joinToString("")
}
}

View File

@@ -418,3 +418,33 @@ class PacketTyping : Packet() {
return stream
}
}
/**
* Chunk packet (ID: 0x09)
* Для разбиения больших пакетов на части (как в Desktop)
*/
class PacketChunk : Packet() {
var chunkId: String = ""
var chunkIndex: Int = 0
var totalChunks: Int = 0
var data: ByteArray = ByteArray(0)
override fun getPacketId(): Int = 0x09
override fun receive(stream: Stream) {
chunkId = stream.readString()
chunkIndex = stream.readInt32()
totalChunks = stream.readInt32()
data = stream.readBytes()
}
override fun send(): Stream {
val stream = Stream()
stream.writeInt16(getPacketId())
stream.writeString(chunkId)
stream.writeInt32(chunkIndex)
stream.writeInt32(totalChunks)
stream.writeBytes(data)
return stream
}
}

View File

@@ -31,7 +31,6 @@ class Protocol(
companion object {
private const val TAG = "RosettaProtocol"
private const val RECONNECT_INTERVAL = 5000L // 5 seconds (как в Архиве)
private const val MAX_RECONNECT_ATTEMPTS = 10 // Увеличил лимит
private const val HANDSHAKE_TIMEOUT = 10000L // 10 seconds
}
@@ -47,7 +46,6 @@ class Protocol(
.build()
private var webSocket: WebSocket? = null
private var reconnectAttempts = 0
private var isManuallyClosed = false
private var handshakeComplete = false
private var handshakeJob: Job? = null
@@ -84,6 +82,7 @@ class Protocol(
0x06 to { PacketMessage() },
0x07 to { PacketRead() },
0x08 to { PacketDelivery() },
0x09 to { PacketChunk() },
0x0B to { PacketTyping() }
)
@@ -180,7 +179,6 @@ class Protocol(
webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
log("✅ WebSocket connected")
reconnectAttempts = 0
_state.value = ProtocolState.CONNECTED
// If we have saved credentials, start handshake automatically
@@ -294,12 +292,11 @@ class Protocol(
}
try {
val sent = socket.send(ByteString.of(*data))
if (sent) {
log("✅ Packet ${packet.getPacketId()} sent successfully")
} else {
log("❌ Failed to send packet ${packet.getPacketId()} - send() returned false")
}
// 📦 Используем Chunker для отправки (как в Desktop)
// Если пакет большой, он будет разбит на части автоматически
val chunker = Chunker(socket) { msg -> log(msg) }
chunker.send(stream)
log("✅ Packet ${packet.getPacketId()} sent successfully")
} catch (e: Exception) {
log("❌ Exception sending packet ${packet.getPacketId()}: ${e.message}")
e.printStackTrace()
@@ -358,25 +355,22 @@ class Protocol(
handshakeJob?.cancel()
heartbeatJob?.cancel()
// Автоматический reconnect (упрощённая логика как в Архиве)
// Автоматический reconnect (простая логика как в Архиве - без счётчиков)
if (!isManuallyClosed) {
log("🔄 Connection lost from $previousState, reconnecting...")
reconnectAttempts++
val delay = if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
RECONNECT_INTERVAL
} else {
// После максимума попыток - ждём дольше
reconnectAttempts = 0
30000L
}
log("🔄 Reconnecting in ${delay}ms (attempt $reconnectAttempts)")
log("🔄 Connection lost from $previousState, attempting to reconnect...")
log("🔄 Reconnecting in ${RECONNECT_INTERVAL}ms")
scope.launch {
delay(delay)
delay(RECONNECT_INTERVAL)
if (!isManuallyClosed) {
connect()
// В Desktop проверяется socket?.readyState == WebSocket.OPEN
// В Android аналог - проверка state
if (_state.value == ProtocolState.CONNECTED ||
_state.value == ProtocolState.AUTHENTICATED) {
return@launch
}
// В Desktop emit('reconnect') происходит здесь
}
}
}

View File

@@ -494,8 +494,6 @@ fun SetPasswordScreen(
accountManager.saveAccount(account)
accountManager.setCurrentAccount(keyPair.publicKey)
// Save as last logged account for next time
accountManager.setLastLoggedPublicKey(keyPair.publicKey)
// 🔌 Connect to server and authenticate
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)

View File

@@ -587,8 +587,6 @@ fun UnlockScreen(
ProtocolManager.authenticate(account.publicKey, privateKeyHash)
accountManager.setCurrentAccount(account.publicKey)
// Save as last logged account for next time
accountManager.setLastLoggedPublicKey(account.publicKey)
onUnlocked(decryptedAccount)
} catch (e: Exception) {