Refactor image handling and decoding logic

- Introduced a maximum bitmap decode dimension to prevent excessive memory usage.
- Enhanced base64 to bitmap conversion by extracting payload and applying EXIF orientation.
- Improved error handling for image downloads and decoding processes.
- Simplified media picker and chat input components to manage keyboard visibility more effectively.
- Updated color selection grid to adaptively adjust based on available width.
- Added safety checks for notifications and call actions in profile screens.
- Optimized bitmap decoding in uriToBase64Image to handle large images more efficiently.
This commit is contained in:
2026-02-20 02:45:00 +05:00
parent 5cf8b2866f
commit 88e2084f8b
26 changed files with 943 additions and 464 deletions

View File

@@ -591,7 +591,38 @@ class Protocol(
* Check if connected and authenticated
*/
fun isAuthenticated(): Boolean = _state.value == ProtocolState.AUTHENTICATED
/**
* Foreground fast reconnect:
* on app resume we should not wait scheduled exponential backoff.
*/
fun reconnectNowIfNeeded(reason: String = "foreground") {
val currentState = _state.value
val hasCredentials = !lastPublicKey.isNullOrBlank() && !lastPrivateHash.isNullOrBlank()
log(
"⚡ FAST RECONNECT CHECK: state=$currentState, hasCredentials=$hasCredentials, isConnecting=$isConnecting, reason=$reason"
)
if (!hasCredentials) return
if (
currentState == ProtocolState.AUTHENTICATED ||
currentState == ProtocolState.HANDSHAKING ||
currentState == ProtocolState.DEVICE_VERIFICATION_REQUIRED ||
currentState == ProtocolState.CONNECTED ||
(currentState == ProtocolState.CONNECTING && isConnecting)
) {
return
}
// Reset backoff and connect immediately.
reconnectAttempts = 0
reconnectJob?.cancel()
reconnectJob = null
connect()
}
/**
* Check if connected (may not be authenticated yet)
*/

View File

@@ -9,6 +9,8 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.security.SecureRandom
import java.text.SimpleDateFormat
import java.util.*
@@ -66,8 +68,18 @@ object ProtocolManager {
private var uiLogsEnabled = false
private var lastProtocolState: ProtocolState? = null
@Volatile private var syncBatchInProgress = false
private val _syncInProgress = MutableStateFlow(false)
val syncInProgress: StateFlow<Boolean> = _syncInProgress.asStateFlow()
@Volatile private var resyncRequiredAfterAccountInit = false
private val inboundPacketTasks = AtomicInteger(0)
private val inboundPacketMutex = Mutex()
private fun setSyncInProgress(value: Boolean) {
syncBatchInProgress = value
if (_syncInProgress.value != value) {
_syncInProgress.value = value
}
}
fun addLog(message: String) {
val timestamp = dateFormat.format(Date())
@@ -107,6 +119,9 @@ object ProtocolManager {
if (newState == ProtocolState.AUTHENTICATED && previous != ProtocolState.AUTHENTICATED) {
onAuthenticated()
}
if (newState != ProtocolState.AUTHENTICATED && newState != ProtocolState.HANDSHAKING) {
setSyncInProgress(false)
}
lastProtocolState = newState
}
}
@@ -117,7 +132,7 @@ object ProtocolManager {
* Должен вызываться после авторизации пользователя
*/
fun initializeAccount(publicKey: String, privateKey: String) {
syncBatchInProgress = false
setSyncInProgress(false)
messageRepository?.initialize(publicKey, privateKey)
if (resyncRequiredAfterAccountInit || protocol?.isAuthenticated() == true) {
resyncRequiredAfterAccountInit = false
@@ -309,7 +324,10 @@ object ProtocolManager {
inboundPacketTasks.incrementAndGet()
scope.launch {
try {
block()
// Preserve packet handling order to avoid read/message races during sync.
inboundPacketMutex.withLock {
block()
}
} finally {
inboundPacketTasks.decrementAndGet()
}
@@ -331,6 +349,7 @@ object ProtocolManager {
}
private fun onAuthenticated() {
setSyncInProgress(false)
TransportManager.requestTransportServer()
fetchOwnProfile()
requestSynchronize()
@@ -378,17 +397,16 @@ object ProtocolManager {
scope.launch {
when (packet.status) {
SyncStatus.BATCH_START -> {
syncBatchInProgress = true
setSyncInProgress(true)
}
SyncStatus.BATCH_END -> {
syncBatchInProgress = true
setSyncInProgress(true)
waitInboundPacketTasks()
messageRepository?.updateLastSyncTimestamp(packet.timestamp)
syncBatchInProgress = false
sendSynchronize(packet.timestamp)
}
SyncStatus.NOT_NEEDED -> {
syncBatchInProgress = false
setSyncInProgress(false)
messageRepository?.updateLastSyncTimestamp(packet.timestamp)
}
}
@@ -423,6 +441,13 @@ object ProtocolManager {
fun connect() {
getProtocol().connect()
}
/**
* Trigger immediate reconnect on app foreground (skip waiting backoff timer).
*/
fun reconnectNowIfNeeded(reason: String = "foreground_resume") {
getProtocol().reconnectNowIfNeeded(reason)
}
/**
* Authenticate with server
@@ -647,7 +672,7 @@ object ProtocolManager {
protocol?.clearCredentials()
_devices.value = emptyList()
_pendingDeviceVerification.value = null
syncBatchInProgress = false
setSyncInProgress(false)
inboundPacketTasks.set(0)
}
@@ -659,7 +684,7 @@ object ProtocolManager {
protocol = null
_devices.value = emptyList()
_pendingDeviceVerification.value = null
syncBatchInProgress = false
setSyncInProgress(false)
inboundPacketTasks.set(0)
scope.cancel()
}