Ускорено подключение, исправлен кэш участников групп и обновлена версия до 1.1.5
This commit is contained in:
@@ -23,8 +23,8 @@ val gitShortSha = safeGitOutput("rev-parse", "--short", "HEAD") ?: "unknown"
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Rosetta versioning — bump here on each release
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
val rosettaVersionName = "1.1.4"
|
||||
val rosettaVersionCode = 16 // Increment on each release
|
||||
val rosettaVersionName = "1.1.5"
|
||||
val rosettaVersionCode = 17 // Increment on each release
|
||||
|
||||
android {
|
||||
namespace = "com.rosetta.messenger"
|
||||
|
||||
@@ -17,20 +17,17 @@ object ReleaseNotes {
|
||||
val RELEASE_NOTICE = """
|
||||
Update v$VERSION_PLACEHOLDER
|
||||
|
||||
Профиль и группы
|
||||
- Фиксированные табы в профиле и группах
|
||||
- Fast-scroll с отображением даты в медиа-галерее
|
||||
- Поддержка Apple Emoji в аватарах и интерфейсе
|
||||
- Восстановление ключей шифрования группы по инвайт-ссылке
|
||||
Подключение
|
||||
- Ускорен старт соединения и handshake при входе в аккаунт
|
||||
- Логика reconnect синхронизирована с desktop-поведением
|
||||
- Обновлён серверный endpoint на основной production (wss)
|
||||
|
||||
Аватары
|
||||
- Улучшено отображение аватаров: поддержка текста с эмодзи
|
||||
- Улучшена логика отображения в компоненте AvatarImage
|
||||
Группы
|
||||
- Добавлено предзагруженное кэширование участников группы
|
||||
- Убран скачок "0 members" при повторном открытии группы
|
||||
|
||||
Исправления
|
||||
- Исправлен переход по своему тэгу в группах
|
||||
- Убрана лишняя подсветка в чатах
|
||||
- Корректное отображение fast-scroll при изменении размера экрана
|
||||
Интерфейс
|
||||
- Исправлено вертикальное выравнивание verified-галочки в списке чатов
|
||||
""".trimIndent()
|
||||
|
||||
fun getNotice(version: String): String =
|
||||
|
||||
@@ -31,8 +31,8 @@ object ProtocolManager {
|
||||
private const val DEBUG_LOG_FLUSH_DELAY_MS = 60L
|
||||
private const val TYPING_INDICATOR_TIMEOUT_MS = 3_000L
|
||||
|
||||
// Server address - same as React Native version
|
||||
private const val SERVER_ADDRESS = "ws://46.28.71.12:3000"
|
||||
// Desktop parity: use the same primary WebSocket endpoint as desktop client.
|
||||
private const val SERVER_ADDRESS = "wss://wss.rosetta.im"
|
||||
private const val DEVICE_PREFS = "rosetta_protocol"
|
||||
private const val DEVICE_ID_KEY = "device_id"
|
||||
private const val DEVICE_ID_LENGTH = 128
|
||||
|
||||
@@ -163,11 +163,8 @@ class AuthStateManager(
|
||||
|
||||
// Step 8: Connect and authenticate with protocol
|
||||
ProtocolManager.connect()
|
||||
|
||||
// Give WebSocket time to connect before authenticating
|
||||
kotlinx.coroutines.delay(500)
|
||||
|
||||
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
|
||||
ProtocolManager.reconnectNowIfNeeded("auth_state_create")
|
||||
|
||||
Result.success(decryptedAccount)
|
||||
} catch (e: Exception) {
|
||||
@@ -210,11 +207,8 @@ class AuthStateManager(
|
||||
|
||||
// Connect and authenticate with protocol
|
||||
ProtocolManager.connect()
|
||||
|
||||
// Give WebSocket time to connect before authenticating
|
||||
kotlinx.coroutines.delay(500)
|
||||
|
||||
ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash)
|
||||
ProtocolManager.reconnectNowIfNeeded("auth_state_unlock")
|
||||
|
||||
Result.success(decryptedAccount)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -2,20 +2,24 @@ package com.rosetta.messenger.ui.auth
|
||||
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
import com.rosetta.messenger.network.ProtocolState
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
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(
|
||||
publicKey: String,
|
||||
privateKeyHash: String,
|
||||
attempts: Int = 2,
|
||||
timeoutMs: Long = 25_000L
|
||||
): ProtocolState? {
|
||||
repeat(attempts) {
|
||||
ProtocolManager.disconnect()
|
||||
delay(200)
|
||||
ProtocolManager.authenticate(publicKey, privateKeyHash)
|
||||
repeat(attempts) { attempt ->
|
||||
startAuthHandshakeFast(publicKey, privateKeyHash)
|
||||
|
||||
val state = withTimeoutOrNull(timeoutMs) {
|
||||
ProtocolManager.state.first {
|
||||
@@ -26,6 +30,7 @@ internal suspend fun awaitAuthHandshakeState(
|
||||
if (state != null) {
|
||||
return state
|
||||
}
|
||||
ProtocolManager.reconnectNowIfNeeded("auth_handshake_retry_${attempt + 1}")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -527,17 +527,10 @@ fun SetPasswordScreen(
|
||||
keyPair.privateKey
|
||||
)
|
||||
|
||||
val handshakeState =
|
||||
awaitAuthHandshakeState(
|
||||
startAuthHandshakeFast(
|
||||
keyPair.publicKey,
|
||||
privateKeyHash
|
||||
)
|
||||
if (handshakeState == null) {
|
||||
error =
|
||||
"Failed to connect to server. Please try again."
|
||||
isCreating = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
accountManager.setCurrentAccount(keyPair.publicKey)
|
||||
|
||||
|
||||
@@ -116,12 +116,7 @@ val decryptedPrivateKey = CryptoManager.decryptWithPassword(
|
||||
name = selectedAccount.name
|
||||
)
|
||||
|
||||
val handshakeState = awaitAuthHandshakeState(account.publicKey, privateKeyHash)
|
||||
if (handshakeState == null) {
|
||||
onError("Failed to connect to server")
|
||||
onUnlocking(false)
|
||||
return
|
||||
}
|
||||
startAuthHandshakeFast(account.publicKey, privateKeyHash)
|
||||
|
||||
accountManager.setCurrentAccount(account.publicKey)
|
||||
onSuccess(decryptedAccount)
|
||||
|
||||
@@ -3891,7 +3891,7 @@ fun DialogItemContent(
|
||||
VerifiedBadge(
|
||||
verified = if (dialog.verified > 0) dialog.verified else 1,
|
||||
size = 16,
|
||||
modifier = Modifier.offset(y = (-1).dp)
|
||||
modifier = Modifier.offset(y = (-2).dp)
|
||||
)
|
||||
}
|
||||
// 🔒 Красная иконка замочка для заблокированных пользователей
|
||||
|
||||
@@ -395,7 +395,7 @@ fun GroupInfoScreen(
|
||||
var showEncryptionPage by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
|
||||
var encryptionKey by rememberSaveable(dialogPublicKey) { mutableStateOf("") }
|
||||
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 showGroupAvatarPicker by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
|
||||
var showGroupAvatarViewer by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
|
||||
@@ -414,18 +414,21 @@ fun GroupInfoScreen(
|
||||
val initialDiskMembersCache = remember(membersCacheKey) {
|
||||
GroupMembersDiskCache.getAny(context, membersCacheKey)
|
||||
}
|
||||
val hasInitialMembersCache = remember(initialMemoryMembersCache, initialDiskMembersCache) {
|
||||
initialMemoryMembersCache != null || initialDiskMembersCache != null
|
||||
}
|
||||
val initialMembers = remember(initialMemoryMembersCache, initialDiskMembersCache) {
|
||||
initialMemoryMembersCache?.members ?: initialDiskMembersCache?.members.orEmpty()
|
||||
}
|
||||
var members by remember(dialogPublicKey) { mutableStateOf(initialMembers) }
|
||||
var members by remember(dialogPublicKey, membersCacheKey) { mutableStateOf(initialMembers) }
|
||||
val memberInfoByKey =
|
||||
remember(dialogPublicKey) {
|
||||
remember(dialogPublicKey, membersCacheKey) {
|
||||
mutableStateMapOf<String, SearchUser>().apply {
|
||||
initialMemoryMembersCache?.memberInfoByKey?.let { putAll(it) }
|
||||
}
|
||||
}
|
||||
// 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?>(
|
||||
initialValue = null,
|
||||
@@ -1089,7 +1092,7 @@ fun GroupInfoScreen(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = if (membersLoading) {
|
||||
text = if (membersLoading || (members.isEmpty() && !hasInitialMembersCache)) {
|
||||
"Loading members..."
|
||||
} else {
|
||||
"${members.size} members, $onlineCount online"
|
||||
|
||||
Reference in New Issue
Block a user