feat: Optimize message handling by incrementally adding new messages; prevent UI jumps and improve performance

This commit is contained in:
k1ngsterr1
2026-01-17 04:29:15 +05:00
parent 2a16d39e02
commit c9724b3bb7
2 changed files with 62 additions and 4 deletions

View File

@@ -215,7 +215,7 @@ private fun Message.toChatMessage() =
) )
/** Экран детального чата с пользователем */ /** Экран детального чата с пользователем */
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun ChatDetailScreen( fun ChatDetailScreen(
user: SearchUser, user: SearchUser,
@@ -1179,7 +1179,16 @@ fun ChatDetailScreen(
nextMessage.isOutgoing != message.isOutgoing || nextMessage.isOutgoing != message.isOutgoing ||
(message.timestamp.time - nextMessage.timestamp.time) > 60_000 // 1 минута (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: дата показывается ПОСЛЕ сообщения // В reversed layout: дата показывается ПОСЛЕ сообщения
// (визуально СВЕРХУ группы сообщений) // (визуально СВЕРХУ группы сообщений)
if (showDate) { if (showDate) {

View File

@@ -129,6 +129,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
/** /**
* 🔔 Подписка на события новых сообщений от MessageRepository * 🔔 Подписка на события новых сообщений от MessageRepository
* Обновляет UI в реальном времени когда приходит новое сообщение * Обновляет UI в реальном времени когда приходит новое сообщение
* 🚀 ОПТИМИЗАЦИЯ: Добавляем сообщение инкрементально, а не перезагружаем весь список
* Это предотвращает "прыгание" пузырьков при добавлении нового сообщения
*/ */
private fun setupNewMessageListener() { private fun setupNewMessageListener() {
viewModelScope.launch { viewModelScope.launch {
@@ -139,13 +141,60 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
val currentDialogKey = getDialogKey(account, opponent) val currentDialogKey = getDialogKey(account, opponent)
if (dialogKey == currentDialogKey) { 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() { private fun setupPacketListeners() {
// ✅ Входящие сообщения обрабатываются ТОЛЬКО в MessageRepository (ProtocolManager) // ✅ Входящие сообщения обрабатываются ТОЛЬКО в MessageRepository (ProtocolManager)
// Здесь НЕ обрабатываем 0x06 чтобы избежать дублирования! // Здесь НЕ обрабатываем 0x06 чтобы избежать дублирования!