fix: refine swipe-back thresholds for improved navigation responsiveness
This commit is contained in:
@@ -1949,32 +1949,7 @@ fun ChatDetailScreen(
|
||||
// 📸 Отправляем фото с caption напрямую
|
||||
showMediaPicker = false
|
||||
inputFocusTrigger++
|
||||
scope.launch {
|
||||
val base64 =
|
||||
MediaUtils.uriToBase64Image(
|
||||
context,
|
||||
mediaItem.uri
|
||||
)
|
||||
val blurhash =
|
||||
MediaUtils.generateBlurhash(
|
||||
context,
|
||||
mediaItem.uri
|
||||
)
|
||||
val (width, height) =
|
||||
MediaUtils.getImageDimensions(
|
||||
context,
|
||||
mediaItem.uri
|
||||
)
|
||||
if (base64 != null) {
|
||||
viewModel.sendImageMessage(
|
||||
base64,
|
||||
blurhash,
|
||||
caption,
|
||||
width,
|
||||
height
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModel.sendImageFromUri(mediaItem.uri, caption)
|
||||
},
|
||||
onOpenCamera = {
|
||||
// 📷 Открываем встроенную камеру (без системного превью!)
|
||||
|
||||
@@ -41,6 +41,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private const val DECRYPT_CHUNK_SIZE = 15 // Расшифровываем по 15 сообщений за раз
|
||||
private const val DECRYPT_PARALLELISM = 4 // Параллельная расшифровка
|
||||
private const val MAX_CACHE_SIZE = 500 // 🔥 Максимум сообщений в кэше для защиты от OOM
|
||||
private val backgroundUploadScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
// 🔥 ГЛОБАЛЬНЫЙ кэш сообщений на уровне диалогов (dialogKey -> List<ChatMessage>)
|
||||
// Сделан глобальным чтобы можно было очистить при удалении диалога
|
||||
@@ -86,6 +87,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
// 🔥 Кэш расшифрованных сообщений (messageId -> plainText)
|
||||
private val decryptionCache = ConcurrentHashMap<String, String>()
|
||||
@Volatile private var isCleared = false
|
||||
|
||||
// Информация о собеседнике
|
||||
private var opponentTitle: String = ""
|
||||
@@ -1759,13 +1761,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
addMessageSafely(optimisticMessage)
|
||||
_inputText.value = ""
|
||||
|
||||
// 2. 💾 СРАЗУ сохраняем в БД со статусом SENDING (delivered = 0)
|
||||
// Чтобы при выходе из диалога сообщение не пропадало
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// 2. 🔄 В фоне, независимо от жизненного цикла экрана:
|
||||
// сохраняем optimistic в БД -> конвертируем -> загружаем -> отправляем пакет.
|
||||
backgroundUploadScope.launch {
|
||||
try {
|
||||
// Сохраняем с localUri и размерами в attachments для восстановления при возврате в
|
||||
// чат
|
||||
val attachmentsJson =
|
||||
val optimisticAttachmentsJson =
|
||||
JSONArray()
|
||||
.apply {
|
||||
put(
|
||||
@@ -1774,66 +1774,52 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
put("type", AttachmentType.IMAGE.value)
|
||||
put("preview", "")
|
||||
put("blob", "")
|
||||
put(
|
||||
"width",
|
||||
imageWidth
|
||||
) // 🔥 Сохраняем размеры в БД
|
||||
put(
|
||||
"height",
|
||||
imageHeight
|
||||
) // 🔥 Сохраняем размеры в БД
|
||||
put(
|
||||
"localUri",
|
||||
imageUri.toString()
|
||||
) // 🔥 Сохраняем localUri
|
||||
put("width", imageWidth)
|
||||
put("height", imageHeight)
|
||||
put("localUri", imageUri.toString())
|
||||
}
|
||||
)
|
||||
}
|
||||
.toString()
|
||||
|
||||
// Сохраняем optimistic сообщение в БД
|
||||
saveMessageToDatabase(
|
||||
messageId = messageId,
|
||||
text = text,
|
||||
encryptedContent = "", // Пустой пока не зашифровали
|
||||
encryptedContent = "",
|
||||
encryptedKey = "",
|
||||
timestamp = timestamp,
|
||||
isFromMe = true,
|
||||
delivered = 0, // SENDING
|
||||
attachmentsJson = attachmentsJson
|
||||
delivered = 0,
|
||||
attachmentsJson = optimisticAttachmentsJson,
|
||||
opponentPublicKey = recipient
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// Игнорируем ошибку - главное что в UI показали
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 🔄 В ФОНЕ: конвертируем, генерируем blurhash и отправляем
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
// Получаем размеры изображения
|
||||
val (width, height) =
|
||||
com.rosetta.messenger.utils.MediaUtils.getImageDimensions(context, imageUri)
|
||||
|
||||
// Конвертируем в base64
|
||||
val imageBase64 =
|
||||
com.rosetta.messenger.utils.MediaUtils.uriToBase64Image(context, imageUri)
|
||||
if (imageBase64 == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.ERROR)
|
||||
if (!isCleared) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.ERROR)
|
||||
}
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Генерируем blurhash
|
||||
val blurhash =
|
||||
com.rosetta.messenger.utils.MediaUtils.generateBlurhash(context, imageUri)
|
||||
|
||||
// 4. 🔄 Обновляем optimistic сообщение с реальными данными
|
||||
withContext(Dispatchers.Main) {
|
||||
updateOptimisticImageMessage(messageId, imageBase64, blurhash, width, height)
|
||||
if (!isCleared) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateOptimisticImageMessage(messageId, imageBase64, blurhash, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 📤 Отправляем (шифрование + загрузка на сервер)
|
||||
sendImageMessageInternal(
|
||||
messageId = messageId,
|
||||
imageBase64 = imageBase64,
|
||||
@@ -1846,9 +1832,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
sender = sender,
|
||||
privateKey = privateKey
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.ERROR)
|
||||
} catch (_: Exception) {
|
||||
if (!isCleared) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1991,7 +1979,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
updateMessageAttachments(messageId, null)
|
||||
}
|
||||
|
||||
saveDialog(if (caption.isNotEmpty()) caption else "photo", timestamp)
|
||||
saveDialog(
|
||||
lastMessage = if (caption.isNotEmpty()) caption else "photo",
|
||||
timestamp = timestamp,
|
||||
opponentPublicKey = recipient
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.ERROR) }
|
||||
}
|
||||
@@ -2053,7 +2045,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
addMessageSafely(optimisticMessage)
|
||||
_inputText.value = ""
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
backgroundUploadScope.launch {
|
||||
try {
|
||||
// Шифрование текста
|
||||
val encryptResult = MessageCrypto.encryptForSending(text, recipient)
|
||||
@@ -2144,7 +2136,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
timestamp = timestamp,
|
||||
isFromMe = true,
|
||||
delivered = if (isSavedMessages) 2 else 0, // SENDING для обычных
|
||||
attachmentsJson = attachmentsJson
|
||||
attachmentsJson = attachmentsJson,
|
||||
opponentPublicKey = recipient
|
||||
)
|
||||
|
||||
// 🔥 После успешной отправки обновляем статус на SENT (2) в БД и UI
|
||||
@@ -2154,7 +2147,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.SENT) }
|
||||
|
||||
saveDialog(if (text.isNotEmpty()) text else "photo", timestamp)
|
||||
saveDialog(
|
||||
lastMessage = if (text.isNotEmpty()) text else "photo",
|
||||
timestamp = timestamp,
|
||||
opponentPublicKey = recipient
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.SENT) }
|
||||
} finally {
|
||||
@@ -2234,7 +2231,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
addMessageSafely(optimisticMessage)
|
||||
_inputText.value = ""
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
backgroundUploadScope.launch {
|
||||
try {
|
||||
// Шифрование текста
|
||||
val encryptResult = MessageCrypto.encryptForSending(text, recipient)
|
||||
@@ -2326,7 +2323,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
timestamp = timestamp,
|
||||
isFromMe = true,
|
||||
delivered = if (isSavedMessages) 2 else 0,
|
||||
attachmentsJson = attachmentsJsonArray.toString()
|
||||
attachmentsJson = attachmentsJsonArray.toString(),
|
||||
opponentPublicKey = recipient
|
||||
)
|
||||
|
||||
// 🔥 Обновляем статус в БД после отправки
|
||||
@@ -2337,7 +2335,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
// Обновляем UI
|
||||
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.SENT) }
|
||||
|
||||
saveDialog(if (text.isNotEmpty()) text else "📷 ${images.size} photos", timestamp)
|
||||
saveDialog(
|
||||
lastMessage = if (text.isNotEmpty()) text else "📷 ${images.size} photos",
|
||||
timestamp = timestamp,
|
||||
opponentPublicKey = recipient
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) { updateMessageStatus(messageId, MessageStatus.SENT) }
|
||||
} finally {
|
||||
@@ -2724,9 +2726,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Сохранить диалог в базу данных 🔥 Используем updateDialogFromMessages для пересчета счетчиков
|
||||
* из messages 📁 SAVED MESSAGES: Использует специальный метод для saved messages
|
||||
*/
|
||||
private suspend fun saveDialog(lastMessage: String, timestamp: Long) {
|
||||
private suspend fun saveDialog(lastMessage: String, timestamp: Long, opponentPublicKey: String? = null) {
|
||||
val account = myPublicKey ?: return
|
||||
val opponent = opponentKey ?: return
|
||||
val opponent = opponentPublicKey ?: opponentKey ?: return
|
||||
|
||||
try {
|
||||
// 🔥 КРИТИЧНО: Используем updateDialogFromMessages который пересчитывает счетчики
|
||||
@@ -3076,6 +3078,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
isCleared = true
|
||||
|
||||
// 🔥 КРИТИЧНО: Удаляем обработчики пакетов чтобы избежать накопления и дубликатов
|
||||
ProtocolManager.unwaitPacket(0x0B, typingPacketHandler)
|
||||
|
||||
Reference in New Issue
Block a user