feat: Optimize message handling by incrementally adding new messages; prevent UI jumps and improve performance
This commit is contained in:
@@ -215,7 +215,7 @@ private fun Message.toChatMessage() =
|
||||
)
|
||||
|
||||
/** Экран детального чата с пользователем */
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ChatDetailScreen(
|
||||
user: SearchUser,
|
||||
@@ -1179,7 +1179,16 @@ fun ChatDetailScreen(
|
||||
nextMessage.isOutgoing != message.isOutgoing ||
|
||||
(message.timestamp.time - nextMessage.timestamp.time) > 60_000 // 1 минута
|
||||
|
||||
Column {
|
||||
// 🚀 ОПТИМИЗАЦИЯ: animateItemPlacement() для плавной анимации при добавлении/удалении
|
||||
// Это предотвращает "прыжки" пузырьков при изменении списка
|
||||
Column(
|
||||
modifier = Modifier.animateItemPlacement(
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
stiffness = Spring.StiffnessMedium
|
||||
)
|
||||
)
|
||||
) {
|
||||
// В reversed layout: дата показывается ПОСЛЕ сообщения
|
||||
// (визуально СВЕРХУ группы сообщений)
|
||||
if (showDate) {
|
||||
|
||||
@@ -129,6 +129,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
/**
|
||||
* 🔔 Подписка на события новых сообщений от MessageRepository
|
||||
* Обновляет UI в реальном времени когда приходит новое сообщение
|
||||
* 🚀 ОПТИМИЗАЦИЯ: Добавляем сообщение инкрементально, а не перезагружаем весь список
|
||||
* Это предотвращает "прыгание" пузырьков при добавлении нового сообщения
|
||||
*/
|
||||
private fun setupNewMessageListener() {
|
||||
viewModelScope.launch {
|
||||
@@ -139,13 +141,60 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val currentDialogKey = getDialogKey(account, opponent)
|
||||
|
||||
if (dialogKey == currentDialogKey) {
|
||||
// Обновляем сообщения из БД без показа скелетона
|
||||
loadMessagesFromDatabase(delayMs = 0L)
|
||||
// 🚀 ОПТИМИЗАЦИЯ: Загружаем только последнее сообщение и добавляем инкрементально
|
||||
addLatestMessageFromDb(account, currentDialogKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 Инкрементальное добавление последнего сообщения из БД
|
||||
* Вместо полной перезагрузки списка - добавляем только новое сообщение
|
||||
* Это предотвращает "прыгание" пузырьков в Compose
|
||||
*/
|
||||
private fun addLatestMessageFromDb(account: String, dialogKey: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
// Получаем последнее сообщение из БД
|
||||
val latestEntity = messageDao.getMessages(account, dialogKey, limit = 1, offset = 0).firstOrNull()
|
||||
?: return@launch
|
||||
|
||||
// Проверяем, есть ли это сообщение уже в списке
|
||||
val existingIds = _messages.value.map { it.id }.toSet()
|
||||
if (latestEntity.messageId in existingIds) {
|
||||
return@launch // Сообщение уже есть, не добавляем
|
||||
}
|
||||
|
||||
// Конвертируем в ChatMessage
|
||||
val newMessage = entityToChatMessage(latestEntity)
|
||||
|
||||
// 🚀 КЛЮЧЕВОЕ: Добавляем в конец списка (новые сообщения = больший timestamp)
|
||||
// НЕ пересортировываем весь список - просто добавляем!
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
val currentList = _messages.value
|
||||
_messages.value = currentList + newMessage
|
||||
}
|
||||
|
||||
// Обновляем кэш
|
||||
dialogMessagesCache[dialogKey] = _messages.value
|
||||
|
||||
// 👁️ Фоновые операции
|
||||
if (isDialogActive) {
|
||||
messageDao.markDialogAsRead(account, dialogKey)
|
||||
// Отправляем read receipt
|
||||
if (!newMessage.isOutgoing) {
|
||||
sendReadReceiptToOpponent()
|
||||
}
|
||||
}
|
||||
dialogDao.updateDialogFromMessages(account, opponentKey ?: return@launch)
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e(TAG, "Error adding latest message", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPacketListeners() {
|
||||
// ✅ Входящие сообщения обрабатываются ТОЛЬКО в MessageRepository (ProtocolManager)
|
||||
// Здесь НЕ обрабатываем 0x06 чтобы избежать дублирования!
|
||||
|
||||
Reference in New Issue
Block a user