Фикс клавиатуры, скругления input, iOS 26 layout, доставка сообщений и синхронизация

This commit is contained in:
2026-03-24 20:31:30 +05:00
parent 1cdd392cf3
commit d482cdf62b
20 changed files with 565 additions and 235 deletions

View File

@@ -124,6 +124,35 @@ final class ProtocolManager: @unchecked Sendable {
}
}
/// Android parity: `reconnectNowIfNeeded(reason: "foreground")`.
/// On foreground resume, always force reconnect iOS suspends the process in
/// background, server RSTs TCP, but `didCloseWith` never fires (zombie socket).
/// Android doesn't have this because OkHttp fires onFailure in background.
/// Previously iOS used ping-first (3s timeout) which was too slow.
func forceReconnectOnForeground() {
guard savedPublicKey != nil, savedPrivateHash != nil else { return }
// Android parity: skip if handshake or device verification is in progress.
// These are active flows that should not be interrupted.
switch connectionState {
case .handshaking, .deviceVerificationRequired:
return
case .connecting:
if client.isConnecting { return }
case .authenticated, .connected, .disconnected:
break // Always reconnect .authenticated/.connected may be zombie on iOS
}
Self.logger.info("⚡ Foreground reconnect — tearing down potential zombie socket")
pingVerificationInProgress = false
pingTimeoutTask?.cancel()
pingTimeoutTask = nil
handshakeComplete = false
heartbeatTask?.cancel()
connectionState = .connecting
client.forceReconnect()
}
/// Android parity: `reconnectNowIfNeeded()` if already in an active state,
/// skip reconnect. Otherwise reset backoff and connect immediately.
func reconnectIfNeeded() {
@@ -191,7 +220,13 @@ final class ProtocolManager: @unchecked Sendable {
handshakeComplete = false
heartbeatTask?.cancel()
Task { @MainActor in
self.connectionState = .connecting
// Guard: only downgrade to .connecting if reconnect hasn't already progressed.
// forceReconnect() is called synchronously below if it completes fast,
// this async Task could overwrite .authenticated/.handshaking with .connecting.
let s = self.connectionState
if s != .authenticated && s != .handshaking && s != .connected {
self.connectionState = .connecting
}
}
client.forceReconnect()
}
@@ -291,7 +326,11 @@ final class ProtocolManager: @unchecked Sendable {
self.handshakeComplete = false
self.heartbeatTask?.cancel()
Task { @MainActor in
self.connectionState = .connecting
// Guard: only downgrade to .connecting if reconnect hasn't already progressed.
let s = self.connectionState
if s != .authenticated && s != .handshaking && s != .connected {
self.connectionState = .connecting
}
}
self.client.forceReconnect()
}
@@ -339,7 +378,11 @@ final class ProtocolManager: @unchecked Sendable {
self.handshakeComplete = false
self.heartbeatTask?.cancel()
Task { @MainActor in
self.connectionState = .connecting
// Guard: only downgrade to .connecting if reconnect hasn't already progressed.
let s = self.connectionState
if s != .authenticated && s != .handshaking && s != .connected {
self.connectionState = .connecting
}
}
self.client.forceReconnect()
}