Правки

This commit is contained in:
Халимов Рустам
2026-05-13 13:48:34 +03:00
parent 00ed4b5959
commit df4feeeee5
12 changed files with 263 additions and 82 deletions
@@ -149,31 +149,44 @@ fun ChatDetailScreen(
viewModel.setChatId(chatId)
}
// Безопасная группировка сообщений с обработкой ошибок
val listItems = remember(state.messages) {
val items = mutableListOf<MessageListItem>()
if (state.messages.isEmpty()) return@remember items
// Группируем от старых к новым (для normal layout)
val reversedMessages = state.messages.reversed()
var i = 0
while (i < reversedMessages.size) {
val isoDate = reversedMessages[i].createdAt
val localDate = viewModel.getLocalDateString(isoDate)
val dayMessages = mutableListOf<chats.domain.model.Message>()
while (i < reversedMessages.size && viewModel.getLocalDateString(reversedMessages[i].createdAt) == localDate) {
dayMessages.add(reversedMessages[i])
i++
}
// Добавляем заголовок даты
items.add(MessageListItem.DateHeader(viewModel.formatDateHeader(isoDate)))
// Добавляем сообщения
dayMessages.forEach { msg ->
items.add(MessageListItem.MessageItem(msg))
try {
// Удаляем дубликаты по ID сообщения
val uniqueMessages = state.messages.distinctBy { it.id }
if (uniqueMessages.isEmpty()) return@remember items
// Группируем от старых к новым (для normal layout)
val reversedMessages = uniqueMessages.reversed()
var i = 0
while (i < reversedMessages.size) {
val isoDate = reversedMessages[i].createdAt
val localDate = viewModel.getLocalDateString(isoDate)
val dayMessages = mutableListOf<chats.domain.model.Message>()
while (i < reversedMessages.size && viewModel.getLocalDateString(reversedMessages[i].createdAt) == localDate) {
dayMessages.add(reversedMessages[i])
i++
}
// Добавляем заголовок даты
val safeDateHeader = try {
viewModel.formatDateHeader(isoDate)
} catch (e: Exception) {
isoDate.take(10)
}
items.add(MessageListItem.DateHeader(safeDateHeader))
// Добавляем сообщения
dayMessages.forEach { msg ->
items.add(MessageListItem.MessageItem(msg))
}
}
} catch (e: Exception) {
android.util.Log.e("ChatDetailScreen", "Error building listItems", e)
}
items
}
@@ -255,28 +268,38 @@ fun ChatDetailScreen(
}
// Авто-скролл к новым сообщениям - только если мы уже внизу
val lastMessageId = state.messages.lastOrNull()?.id
// Берём firstOrNull, т.к. messages отсортированы по sequenceId убыванию (новые в начале)
val lastMessageId = state.messages.firstOrNull()?.id
LaunchedEffect(lastMessageId) {
if (lastMessageId != null && listItems.isNotEmpty()) {
val isAtBottom = listState.layoutInfo.run {
val visibleItems = visibleItemsInfo
val totalCount = totalItemsCount
if (totalCount == 0 || visibleItems.isEmpty()) return@run true
visibleItems.last().index >= totalCount - 2
}
if (isAtBottom) {
listState.animateScrollToItem(listItems.size - 1)
// Даём время LazyColumn отрисовать новое сообщение и обновить layout
delay(200)
val layoutInfo = listState.layoutInfo
val visibleItems = layoutInfo.visibleItemsInfo
val totalCount = layoutInfo.totalItemsCount
if (totalCount > 0 && visibleItems.isNotEmpty()) {
val lastVisibleIndex = visibleItems.last().index
val atBottom = lastVisibleIndex >= totalCount - 2
android.util.Log.d("ChatDetailScreen", "Auto-scroll check: lastVisible=$lastVisibleIndex, total=$totalCount, atBottom=$atBottom, msgId=$lastMessageId")
if (atBottom) {
listState.animateScrollToItem(listItems.size - 1)
}
}
}
}
// Автоматическая прокрутка при инициализации
// Автоматическая прокрутка при инициализации - только один раз
var initialScrollDone by remember { mutableStateOf(false) }
LaunchedEffect(listItems.size) {
if (listItems.isNotEmpty()) {
listState.scrollToItem(listItems.size - 1)
viewModel.onInitialScrollDone()
viewModel.markAsRead()
if (!initialScrollDone && listItems.isNotEmpty() && listItems.size > 1) {
try {
listState.scrollToItem(listItems.size - 1)
initialScrollDone = true
viewModel.onInitialScrollDone()
viewModel.markAsRead()
} catch (e: Exception) {
android.util.Log.e("ChatDetailScreen", "Error in auto-scroll", e)
}
}
}
@@ -300,6 +323,20 @@ fun ChatDetailScreen(
state.messages.count { !it.isRead && it.senderId != viewModel.getCurrentUserId() }
}
// Авто-прочитка при открытии чата - если мы внизу
LaunchedEffect(Unit) {
delay(500)
val layoutInfo = listState.layoutInfo
val visibleItems = layoutInfo.visibleItemsInfo
val totalCount = layoutInfo.totalItemsCount
if (totalCount > 0 && visibleItems.isNotEmpty()) {
val lastVisibleIndex = visibleItems.last().index
if (lastVisibleIndex >= totalCount - 2) {
viewModel.markAsRead()
}
}
}
// Авто-прочитка при нахождении внизу списка
LaunchedEffect(listState.isScrollInProgress) {
if (!listState.isScrollInProgress) {