Ускорено подключение, исправлен кэш участников групп и обновлена версия до 1.1.5

This commit is contained in:
2026-03-09 20:20:27 +05:00
parent 41faa98130
commit c2e27cf543
9 changed files with 39 additions and 52 deletions

View File

@@ -23,8 +23,8 @@ val gitShortSha = safeGitOutput("rev-parse", "--short", "HEAD") ?: "unknown"
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// Rosetta versioning — bump here on each release // Rosetta versioning — bump here on each release
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
val rosettaVersionName = "1.1.4" val rosettaVersionName = "1.1.5"
val rosettaVersionCode = 16 // Increment on each release val rosettaVersionCode = 17 // Increment on each release
android { android {
namespace = "com.rosetta.messenger" namespace = "com.rosetta.messenger"

View File

@@ -17,20 +17,17 @@ object ReleaseNotes {
val RELEASE_NOTICE = """ val RELEASE_NOTICE = """
Update v$VERSION_PLACEHOLDER Update v$VERSION_PLACEHOLDER
Профиль и группы Подключение
- Фиксированные табы в профиле и группах - Ускорен старт соединения и handshake при входе в аккаунт
- Fast-scroll с отображением даты в медиа-галерее - Логика reconnect синхронизирована с desktop-поведением
- Поддержка Apple Emoji в аватарах и интерфейсе - Обновлён серверный endpoint на основной production (wss)
- Восстановление ключей шифрования группы по инвайт-ссылке
Аватары Группы
- Улучшено отображение аватаров: поддержка текста с эмодзи - Добавлено предзагруженное кэширование участников группы
- Улучшена логика отображения в компоненте AvatarImage - Убран скачок "0 members" при повторном открытии группы
Исправления Интерфейс
- Исправлен переход по своему тэгу в группах - Исправлено вертикальное выравнивание verified-галочки в списке чатов
- Убрана лишняя подсветка в чатах
- Корректное отображение fast-scroll при изменении размера экрана
""".trimIndent() """.trimIndent()
fun getNotice(version: String): String = fun getNotice(version: String): String =

View File

@@ -31,8 +31,8 @@ object ProtocolManager {
private const val DEBUG_LOG_FLUSH_DELAY_MS = 60L private const val DEBUG_LOG_FLUSH_DELAY_MS = 60L
private const val TYPING_INDICATOR_TIMEOUT_MS = 3_000L private const val TYPING_INDICATOR_TIMEOUT_MS = 3_000L
// Server address - same as React Native version // Desktop parity: use the same primary WebSocket endpoint as desktop client.
private const val SERVER_ADDRESS = "ws://46.28.71.12:3000" private const val SERVER_ADDRESS = "wss://wss.rosetta.im"
private const val DEVICE_PREFS = "rosetta_protocol" private const val DEVICE_PREFS = "rosetta_protocol"
private const val DEVICE_ID_KEY = "device_id" private const val DEVICE_ID_KEY = "device_id"
private const val DEVICE_ID_LENGTH = 128 private const val DEVICE_ID_LENGTH = 128

View File

@@ -163,11 +163,8 @@ class AuthStateManager(
// Step 8: Connect and authenticate with protocol // Step 8: Connect and authenticate with protocol
ProtocolManager.connect() ProtocolManager.connect()
// Give WebSocket time to connect before authenticating
kotlinx.coroutines.delay(500)
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash) ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
ProtocolManager.reconnectNowIfNeeded("auth_state_create")
Result.success(decryptedAccount) Result.success(decryptedAccount)
} catch (e: Exception) { } catch (e: Exception) {
@@ -210,11 +207,8 @@ class AuthStateManager(
// Connect and authenticate with protocol // Connect and authenticate with protocol
ProtocolManager.connect() ProtocolManager.connect()
// Give WebSocket time to connect before authenticating
kotlinx.coroutines.delay(500)
ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash) ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash)
ProtocolManager.reconnectNowIfNeeded("auth_state_unlock")
Result.success(decryptedAccount) Result.success(decryptedAccount)
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -2,20 +2,24 @@ package com.rosetta.messenger.ui.auth
import com.rosetta.messenger.network.ProtocolManager import com.rosetta.messenger.network.ProtocolManager
import com.rosetta.messenger.network.ProtocolState import com.rosetta.messenger.network.ProtocolState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
internal fun startAuthHandshakeFast(publicKey: String, privateKeyHash: String) {
// Desktop parity: start connection+handshake immediately, without artificial waits.
ProtocolManager.connect()
ProtocolManager.authenticate(publicKey, privateKeyHash)
ProtocolManager.reconnectNowIfNeeded("auth_fast_start")
}
internal suspend fun awaitAuthHandshakeState( internal suspend fun awaitAuthHandshakeState(
publicKey: String, publicKey: String,
privateKeyHash: String, privateKeyHash: String,
attempts: Int = 2, attempts: Int = 2,
timeoutMs: Long = 25_000L timeoutMs: Long = 25_000L
): ProtocolState? { ): ProtocolState? {
repeat(attempts) { repeat(attempts) { attempt ->
ProtocolManager.disconnect() startAuthHandshakeFast(publicKey, privateKeyHash)
delay(200)
ProtocolManager.authenticate(publicKey, privateKeyHash)
val state = withTimeoutOrNull(timeoutMs) { val state = withTimeoutOrNull(timeoutMs) {
ProtocolManager.state.first { ProtocolManager.state.first {
@@ -26,6 +30,7 @@ internal suspend fun awaitAuthHandshakeState(
if (state != null) { if (state != null) {
return state return state
} }
ProtocolManager.reconnectNowIfNeeded("auth_handshake_retry_${attempt + 1}")
} }
return null return null
} }

View File

@@ -527,17 +527,10 @@ fun SetPasswordScreen(
keyPair.privateKey keyPair.privateKey
) )
val handshakeState = startAuthHandshakeFast(
awaitAuthHandshakeState(
keyPair.publicKey, keyPair.publicKey,
privateKeyHash privateKeyHash
) )
if (handshakeState == null) {
error =
"Failed to connect to server. Please try again."
isCreating = false
return@launch
}
accountManager.setCurrentAccount(keyPair.publicKey) accountManager.setCurrentAccount(keyPair.publicKey)

View File

@@ -116,12 +116,7 @@ val decryptedPrivateKey = CryptoManager.decryptWithPassword(
name = selectedAccount.name name = selectedAccount.name
) )
val handshakeState = awaitAuthHandshakeState(account.publicKey, privateKeyHash) startAuthHandshakeFast(account.publicKey, privateKeyHash)
if (handshakeState == null) {
onError("Failed to connect to server")
onUnlocking(false)
return
}
accountManager.setCurrentAccount(account.publicKey) accountManager.setCurrentAccount(account.publicKey)
onSuccess(decryptedAccount) onSuccess(decryptedAccount)

View File

@@ -3891,7 +3891,7 @@ fun DialogItemContent(
VerifiedBadge( VerifiedBadge(
verified = if (dialog.verified > 0) dialog.verified else 1, verified = if (dialog.verified > 0) dialog.verified else 1,
size = 16, size = 16,
modifier = Modifier.offset(y = (-1).dp) modifier = Modifier.offset(y = (-2).dp)
) )
} }
// 🔒 Красная иконка замочка для заблокированных пользователей // 🔒 Красная иконка замочка для заблокированных пользователей

View File

@@ -395,7 +395,7 @@ fun GroupInfoScreen(
var showEncryptionPage by rememberSaveable(dialogPublicKey) { mutableStateOf(false) } var showEncryptionPage by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
var encryptionKey by rememberSaveable(dialogPublicKey) { mutableStateOf("") } var encryptionKey by rememberSaveable(dialogPublicKey) { mutableStateOf("") }
var encryptionKeyLoading by remember { mutableStateOf(false) } var encryptionKeyLoading by remember { mutableStateOf(false) }
var membersLoading by remember { mutableStateOf(false) } var membersLoading by remember(dialogPublicKey) { mutableStateOf(false) }
var isMuted by remember { mutableStateOf(false) } var isMuted by remember { mutableStateOf(false) }
var showGroupAvatarPicker by rememberSaveable(dialogPublicKey) { mutableStateOf(false) } var showGroupAvatarPicker by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
var showGroupAvatarViewer by rememberSaveable(dialogPublicKey) { mutableStateOf(false) } var showGroupAvatarViewer by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
@@ -414,18 +414,21 @@ fun GroupInfoScreen(
val initialDiskMembersCache = remember(membersCacheKey) { val initialDiskMembersCache = remember(membersCacheKey) {
GroupMembersDiskCache.getAny(context, membersCacheKey) GroupMembersDiskCache.getAny(context, membersCacheKey)
} }
val hasInitialMembersCache = remember(initialMemoryMembersCache, initialDiskMembersCache) {
initialMemoryMembersCache != null || initialDiskMembersCache != null
}
val initialMembers = remember(initialMemoryMembersCache, initialDiskMembersCache) { val initialMembers = remember(initialMemoryMembersCache, initialDiskMembersCache) {
initialMemoryMembersCache?.members ?: initialDiskMembersCache?.members.orEmpty() initialMemoryMembersCache?.members ?: initialDiskMembersCache?.members.orEmpty()
} }
var members by remember(dialogPublicKey) { mutableStateOf(initialMembers) } var members by remember(dialogPublicKey, membersCacheKey) { mutableStateOf(initialMembers) }
val memberInfoByKey = val memberInfoByKey =
remember(dialogPublicKey) { remember(dialogPublicKey, membersCacheKey) {
mutableStateMapOf<String, SearchUser>().apply { mutableStateMapOf<String, SearchUser>().apply {
initialMemoryMembersCache?.memberInfoByKey?.let { putAll(it) } initialMemoryMembersCache?.memberInfoByKey?.let { putAll(it) }
} }
} }
// Real online status from PacketOnlineState (0x05), NOT from SearchUser.online // Real online status from PacketOnlineState (0x05), NOT from SearchUser.online
val memberOnlineStatus = remember(dialogPublicKey) { mutableStateMapOf<String, Boolean>() } val memberOnlineStatus = remember(dialogPublicKey, membersCacheKey) { mutableStateMapOf<String, Boolean>() }
val groupEntity by produceState<com.rosetta.messenger.database.GroupEntity?>( val groupEntity by produceState<com.rosetta.messenger.database.GroupEntity?>(
initialValue = null, initialValue = null,
@@ -1089,7 +1092,7 @@ fun GroupInfoScreen(
) )
Text( Text(
text = if (membersLoading) { text = if (membersLoading || (members.isEmpty() && !hasInitialMembersCache)) {
"Loading members..." "Loading members..."
} else { } else {
"${members.size} members, $onlineCount online" "${members.size} members, $onlineCount online"