Исправление аватарки на экране разблокировки, плавная анимация инпута, онлайн-статус по входящим сообщениям, push-навигация в чат, оптимизация debug-логов

This commit is contained in:
2026-03-13 00:12:30 +05:00
parent 70deaaf7f7
commit c7bea82c3a
30 changed files with 1245 additions and 270 deletions

View File

@@ -57,8 +57,10 @@ final class ProtocolManager: @unchecked Sendable {
private var heartbeatTask: Task<Void, Never>?
private var handshakeTimeoutTask: Task<Void, Never>?
private let searchHandlersLock = NSLock()
private let resultHandlersLock = NSLock()
private let packetQueueLock = NSLock()
private var searchResultHandlers: [UUID: (PacketSearch) -> Void] = [:]
private var resultHandlers: [UUID: (PacketResult) -> Void] = [:]
// Saved credentials for auto-reconnect
private var savedPublicKey: String?
@@ -98,6 +100,15 @@ final class ProtocolManager: @unchecked Sendable {
savedPrivateHash = nil
}
/// Immediately reconnect after returning from background, bypassing backoff.
func reconnectIfNeeded() {
guard savedPublicKey != nil, savedPrivateHash != nil else { return }
if connectionState == .authenticated || connectionState == .handshaking { return }
Self.logger.info("Force reconnect from foreground")
connectionState = .connecting
client.forceReconnect()
}
// MARK: - Sending
func sendPacket(_ packet: any Packet) {
@@ -128,6 +139,24 @@ final class ProtocolManager: @unchecked Sendable {
searchHandlersLock.unlock()
}
// MARK: - Result Handlers (Android parity: waitPacket(0x02))
/// Register a one-shot handler for PacketResult (0x02).
@discardableResult
func addResultHandler(_ handler: @escaping (PacketResult) -> Void) -> UUID {
let id = UUID()
resultHandlersLock.lock()
resultHandlers[id] = handler
resultHandlersLock.unlock()
return id
}
func removeResultHandler(_ id: UUID) {
resultHandlersLock.lock()
resultHandlers.removeValue(forKey: id)
resultHandlersLock.unlock()
}
// MARK: - Private Setup
private func setupClientCallbacks() {
@@ -173,7 +202,7 @@ final class ProtocolManager: @unchecked Sendable {
}
let device = HandshakeDevice(
deviceId: UIDevice.current.identifierForVendor?.uuidString ?? "unknown",
deviceId: DeviceIdentityManager.shared.currentDeviceId(),
deviceName: UIDevice.current.name,
deviceOs: "iOS \(UIDevice.current.systemVersion)"
)
@@ -184,7 +213,7 @@ final class ProtocolManager: @unchecked Sendable {
protocolVersion: 1,
heartbeatInterval: 15,
device: device,
handshakeState: .completed
handshakeState: .needDeviceVerification
)
sendPacketDirect(handshake)
@@ -238,7 +267,8 @@ final class ProtocolManager: @unchecked Sendable {
}
case 0x02:
if let p = packet as? PacketResult {
let _ = ResultCode(rawValue: p.resultCode)
Self.logger.info("📥 PacketResult: code=\(p.resultCode)")
notifyResultHandlers(p)
}
case 0x03:
if let p = packet as? PacketSearch {
@@ -293,6 +323,18 @@ final class ProtocolManager: @unchecked Sendable {
}
}
private func notifyResultHandlers(_ packet: PacketResult) {
resultHandlersLock.lock()
let handlers = resultHandlers
// One-shot: clear all handlers after dispatch (Android parity)
resultHandlers.removeAll()
resultHandlersLock.unlock()
for (_, handler) in handlers {
handler(packet)
}
}
private func handleHandshakeResponse(_ packet: PacketHandshake) {
handshakeTimeoutTask?.cancel()
handshakeTimeoutTask = nil