Критический фикс отправки после верификации устройства и релиз 1.5.2

This commit is contained in:
2026-04-16 23:00:07 +05:00
parent 0c150a3113
commit 2066eb9f03
3 changed files with 74 additions and 30 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.5.1" val rosettaVersionName = "1.5.2"
val rosettaVersionCode = 53 // Increment on each release val rosettaVersionCode = 54 // Increment on each release
val customWebRtcAar = file("libs/libwebrtc-custom.aar") val customWebRtcAar = file("libs/libwebrtc-custom.aar")
android { android {

View File

@@ -17,22 +17,27 @@ object ReleaseNotes {
val RELEASE_NOTICE = """ val RELEASE_NOTICE = """
Update v$VERSION_PLACEHOLDER Update v$VERSION_PLACEHOLDER
- Полностью переработан UX записи голосовых: удержание для записи, отправка по отпусканию, Slide to cancel - Перемотка голосовых полностью переработана в Telegram-style: drag по waveform и точный seek по отпусканию
- Пересобрана панель записи ГС в Telegram-style с новым layout, волнами и анимациями - Устранены конфликты жестов у ГС: tap/drag/scrub больше не конфликтуют со swipe-to-reply и swipe-back
- Добавлена и доработана анимация удаления ГС (корзина), устранены рывки и визуальные артефакты - Голосовой плеер доработан: стабильный scrub в паузе, корректный keepPaused, более надежный прогресс
- Исправлены зависания/ANR при записи и отмене голосовых (race-condition, stuck-состояния, watchdog-сценарии) - Добавлена очередь ГС внутри диалога с автопереходом к следующему голосовому по хронологии
- Исправлены скачки и наложения input-панели во время записи (включая Type message/overlay конфликты) - Улучшена совместимость payload голосовых (hex/base64 decode fallback) и восстановление аудиофайла из кэша
- Добавлены улучшения плеера голосовых: мини-плеер, интеграция в чат, корректная работа скоростей - Исправлено позиционирование и clipping кнопки записи в input-панели
- В чат-листе улучшено отображение и поведение активного воспроизведения голосовых - Добавлен haptic при старте записи и обновлены иконки записи voice/video
- Добавлена и отшлифована система выделения текста: handles, magnifier, toolbar (Copy/Select All), haptic - В сайдбаре ограничен список аккаунтов (до 3) для более чистого Telegram-like layout
- Исправлены координаты и стабильность выделения текста в сложных сценариях - Исправлен transition emoji -> keyboard: убран «пустой» зазор при закрытии emoji-панели
- Исправлена обработка reply в группах с Desktop (fallback на hex-ключ для reply blob) - В selection header чата добавлена кнопка Pin/Unpin для выбранного сообщения
- Оптимизированы тяжелые UI-сценарии: prewarm для circular reveal, ускорена анимация онбординга - В Forward-пикере всегда показывается Saved Messages (даже если self-диалог ещё не создан)
- Улучшены миниатюры медиа через BlurHash и стабильность загрузки вложений - Переработаны media-permissions в attach/media picker: корректный permanently denied flow с переходом в Settings
- Доработан экран звонков и related UI (включая пустой экран с Lottie-анимацией) - Улучшена инициализация аккаунта после login/unlock/create-account, устранён race «Sync postponed until account is initialized»
- Доработаны элементы профиля и сайдбара (включая обновления аккаунт-блока и действий) - Доработана синхронизация профиля аккаунта (name/username/verified), включая замену placeholder-имён
- Добавлена смена иконки приложения (калькулятор, погода, заметки) через настройки - Исправлен критичный баг отправки после верификации нового устройства на втором девайсе
- Выполнен большой пакет фиксов по чатам/звонкам/коннекту и визуальному паритету с Telegram - Исправлен reconnect overflow: устранена отрицательная задержка (-2147483648000ms) и дубли disconnect/reconnect
- Улучшена обработка device verification (ACCEPT/DECLINE) и reconnect-логика протокола
- Звонки: добавлен proximity manager (экран гаснет возле уха), добавлен WAKE_LOCK, учтён speaker on/off
- Звонки: рингтон теперь учитывает системный ringer mode (silent/vibrate), снижены ложные звуковые срабатывания
- Убраны дубли CALL-attachments у callee: источник call-события теперь единый (каноничный от caller)
- Групповые сообщения: fallback для plaintext-пакетов без group key и расширенная диагностика decrypt-ошибок
""".trimIndent() """.trimIndent()
fun getNotice(version: String): String = fun getNotice(version: String): String =

View File

@@ -311,14 +311,35 @@ class Protocol(
when (resolve.solution) { when (resolve.solution) {
DeviceResolveSolution.ACCEPT -> { DeviceResolveSolution.ACCEPT -> {
log("✅ DEVICE VERIFICATION ACCEPTED (deviceId=${shortKey(resolve.deviceId, 12)})") log("✅ DEVICE VERIFICATION ACCEPTED (deviceId=${shortKey(resolve.deviceId, 12)})")
if (_state.value == ProtocolState.DEVICE_VERIFICATION_REQUIRED) { val stateAtAccept = _state.value
if (stateAtAccept == ProtocolState.AUTHENTICATED) {
log("✅ ACCEPT ignored: already authenticated")
return@waitPacket
}
if (stateAtAccept == ProtocolState.DEVICE_VERIFICATION_REQUIRED) {
setState(ProtocolState.CONNECTED, "Device verification accepted") setState(ProtocolState.CONNECTED, "Device verification accepted")
val publicKey = lastPublicKey }
val privateHash = lastPrivateHash
if (!publicKey.isNullOrBlank() && !privateHash.isNullOrBlank()) { val publicKey = lastPublicKey
val privateHash = lastPrivateHash
if (publicKey.isNullOrBlank() || privateHash.isNullOrBlank()) {
log("⚠️ ACCEPT received but credentials are missing, waiting for reconnect")
return@waitPacket
}
when (_state.value) {
ProtocolState.DISCONNECTED -> {
log("🔄 ACCEPT while disconnected -> reconnecting")
connect()
}
ProtocolState.CONNECTING -> {
log("⏳ ACCEPT while connecting -> waiting for onOpen auto-handshake")
}
else -> {
startHandshake(publicKey, privateHash, lastDevice) startHandshake(publicKey, privateHash, lastDevice)
} else {
log("⚠️ ACCEPT received but credentials are missing, waiting for reconnect")
} }
} }
} }
@@ -644,7 +665,14 @@ class Protocol(
val currentState = _state.value val currentState = _state.value
val socket = webSocket val socket = webSocket
val socketReady = socket != null val socketReady = socket != null
val authReady = handshakeComplete && currentState == ProtocolState.AUTHENTICATED val authReady = currentState == ProtocolState.AUTHENTICATED
if (authReady && !handshakeComplete) {
// Defensive self-heal:
// AUTHENTICATED state must imply completed handshake.
// If these flags diverge, message sending can be stuck in queue forever.
log("⚠️ AUTHENTICATED with handshakeComplete=false -> self-heal handshakeComplete=true")
handshakeComplete = true
}
val preAuthAllowedPacket = val preAuthAllowedPacket =
packet is PacketSignalPeer || packet is PacketWebRTC || packet is PacketIceServers packet is PacketSignalPeer || packet is PacketWebRTC || packet is PacketIceServers
val preAuthReady = val preAuthReady =
@@ -773,6 +801,13 @@ class Protocol(
val previousState = _state.value val previousState = _state.value
log("🔌 DISCONNECT HANDLER: previousState=$previousState, manuallyClosed=$isManuallyClosed, reconnectAttempts=$reconnectAttempts, isConnecting=$isConnecting") log("🔌 DISCONNECT HANDLER: previousState=$previousState, manuallyClosed=$isManuallyClosed, reconnectAttempts=$reconnectAttempts, isConnecting=$isConnecting")
// Duplicate callbacks are possible (e.g. heartbeat failure + onFailure/onClosed).
// If we are already disconnected and a reconnect is pending, avoid scheduling another one.
if (previousState == ProtocolState.DISCONNECTED && reconnectJob?.isActive == true) {
log("⚠️ DISCONNECT DUPLICATE: reconnect already scheduled, skipping")
return
}
// КРИТИЧНО: если уже идет подключение, не делаем ничего // КРИТИЧНО: если уже идет подключение, не делаем ничего
if (isConnecting) { if (isConnecting) {
log("⚠️ DISCONNECT IGNORED: connection already in progress") log("⚠️ DISCONNECT IGNORED: connection already in progress")
@@ -799,12 +834,16 @@ class Protocol(
// КРИТИЧНО: отменяем предыдущий reconnect job если есть // КРИТИЧНО: отменяем предыдущий reconnect job если есть
reconnectJob?.cancel() reconnectJob?.cancel()
// Экспоненциальная задержка: 1s, 2s, 4s, 8s, максимум 30s // Экспоненциальная задержка: 1s, 2s, 4s, 8s, 16s, максимум 30s.
val delayMs = minOf(1000L * (1 shl minOf(reconnectAttempts - 1, 4)), 30000L) // IMPORTANT: reconnectAttempts may be 0 right after AUTHENTICATED reset.
log("🔄 SCHEDULING RECONNECT: attempt #$reconnectAttempts, delay=${delayMs}ms") // Using (1 shl -1) causes overflow (seen in logs as -2147483648000ms).
val nextAttemptNumber = (reconnectAttempts + 1).coerceAtLeast(1)
val exponent = (nextAttemptNumber - 1).coerceIn(0, 4)
val delayMs = minOf(1000L * (1L shl exponent), 30000L)
log("🔄 SCHEDULING RECONNECT: attempt #$nextAttemptNumber, delay=${delayMs}ms")
if (reconnectAttempts > 20) { if (nextAttemptNumber > 20) {
log("⚠️ WARNING: Too many reconnect attempts ($reconnectAttempts), may be stuck in loop") log("⚠️ WARNING: Too many reconnect attempts ($nextAttemptNumber), may be stuck in loop")
} }
reconnectJob = scope.launch { reconnectJob = scope.launch {