feat: Save last logged account public key after authentication for improved user experience
This commit is contained in:
@@ -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()
|
||||
|
||||
68
app/src/main/java/com/rosetta/messenger/network/Chunker.kt
Normal file
68
app/src/main/java/com/rosetta/messenger/network/Chunker.kt
Normal 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("")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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') происходит здесь
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user