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
|
@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) {
|
||||||
|
|||||||
@@ -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 чтобы избежать дублирования!
|
||||||
|
|||||||
Reference in New Issue
Block a user