Ускорено подключение, исправлен кэш участников групп и обновлена версия до 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
|
// 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"
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// 🔒 Красная иконка замочка для заблокированных пользователей
|
// 🔒 Красная иконка замочка для заблокированных пользователей
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user