diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatSections.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatSections.kt index 542e9e6184..be0bd36cac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatSections.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatSections.kt @@ -23,7 +23,8 @@ data class ChatSectionAreaBoundary ( data class ChatSection ( val items: MutableList, val area: ChatSectionArea, - val boundary: ChatSectionAreaBoundary + val boundary: ChatSectionAreaBoundary, + val itemPositions: MutableMap ) data class SectionItems ( @@ -31,7 +32,6 @@ data class SectionItems ( val items: MutableList, val revealed: Boolean, val showAvatar: MutableSet, - val itemPositions: MutableMap ) data class ChatSectionLoad ( @@ -104,8 +104,7 @@ fun List.putIntoSections(revealedItems: Set): List it.add(first.id) } } - }, - itemPositions = mutableMapOf(first.id to 0), + } ) } else { return emptyList() @@ -117,12 +116,14 @@ fun List.putIntoSections(revealedItems: Set): List ChatSection( items = mutableListOf(recent), area = area, - boundary = ChatSectionAreaBoundary(minIndex = 0, maxIndex = 0, area = area) + boundary = ChatSectionAreaBoundary(minIndex = 0, maxIndex = 0, area = area), + itemPositions = mutableMapOf(recent.items[0].id to 0) ) ) var prev = this[0] var index = 0 + var positionInList = 0; while (index < size) { if (index == 0) { index++ @@ -133,6 +134,7 @@ fun List.putIntoSections(revealedItems: Set): List val existingSection = sections.find { it.area == itemArea } if (existingSection == null) { + positionInList++ val newSection = SectionItems( mergeCategory = item.mergeCategory, items = mutableListOf().also { it.add(item) }, @@ -140,22 +142,30 @@ fun List.putIntoSections(revealedItems: Set): List showAvatar = mutableSetOf().also { it.add(item.id) }, - itemPositions = mutableMapOf(item.id to index), ) sections.add( - ChatSection(items = mutableListOf(newSection), area = itemArea, boundary = ChatSectionAreaBoundary(minIndex = index, maxIndex = index, area = itemArea)) + ChatSection( + items = mutableListOf(newSection), + area = itemArea, + boundary = ChatSectionAreaBoundary(minIndex = index, maxIndex = index, area = itemArea), + itemPositions = mutableMapOf(item.id to positionInList) + ) ) } else { recent = existingSection.items.last() val category = item.mergeCategory if (recent.mergeCategory == category) { + if (category == null || recent.revealed || revealedItems.contains(item.id)) { + positionInList++ + } if (item.chatDir is CIDirection.GroupRcv && prev.chatDir is CIDirection.GroupRcv && item.chatDir.groupMember.memberId != (prev.chatDir as CIDirection.GroupRcv).groupMember.memberId) { recent.showAvatar.add(item.id) } recent.items.add(item) - recent.itemPositions[item.id] = index + existingSection.itemPositions[item.id] = positionInList } else { + positionInList++ val newSectionItems = SectionItems( mergeCategory = item.mergeCategory, items = mutableListOf().also { it.add(item) }, @@ -165,8 +175,8 @@ fun List.putIntoSections(revealedItems: Set): List it.add(item.id) } }, - itemPositions = mutableMapOf(item.id to index), ) + existingSection.itemPositions[item.id] = positionInList existingSection.items.add(newSectionItems) } existingSection.boundary.maxIndex = index @@ -178,6 +188,17 @@ fun List.putIntoSections(revealedItems: Set): List return sections } +fun List.chatItemPosition(chatItemId: Long): Int? { + for (section in this) { + val position = section.itemPositions[chatItemId] + if (position != null) { + return position + } + } + + return null +} + fun List.dropTemporarySections() { val bottomSection = this.find { it.area == ChatSectionArea.Bottom } if (bottomSection != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index e5fd4b5df1..c38df7704f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1020,10 +1020,13 @@ fun BoxWithConstraintsScope.ChatItemsList( .collect { revealedItems.value = setOf() preloadItemsEnabled.value = true - val firstUnreadItemIndex = reversedChatItems.indexOfLast { it.isRcvNew } - if (firstUnreadItemIndex != -1) { - listState.scrollToItem(scrollPosition(firstUnreadItemIndex), -maxHeightRounded) - val firstUnreadItem = chatModel.chatItems[firstUnreadItemIndex] + val firstUnreadItem = reversedChatItems.findLast { it.isRcvNew } + if (firstUnreadItem != null) { + val firstUnreadItemIndexIdx = sections.find { it.area == ChatSectionArea.Current }?.itemPositions?.get(firstUnreadItem.id) + if (firstUnreadItemIndexIdx != null) { + listState.scrollToItem(scrollPosition(firstUnreadItemIndexIdx), -maxHeightRounded) + } + if (chatModel.chatItemsSectionArea[firstUnreadItem.id] != ChatSectionArea.Bottom) { withBGApi { val chat = chatController.apiGetChat(rh = remoteHostId, type = chatInfo.chatType, id = chatInfo.apiId) @@ -1052,10 +1055,10 @@ fun BoxWithConstraintsScope.ChatItemsList( } val scrollToItem: (Long) -> Unit = { itemId: Long -> - val index = reversedChatItems.indexOfFirst { it.id == itemId } + val index = sections.chatItemPosition(itemId) preloadItemsEnabled.value = false - if (index != -1) { + if (index != null) { scope.launch { listState.animateScrollToItem(scrollPosition(index), -maxHeightRounded) preloadItemsEnabled.value = true @@ -1072,18 +1075,20 @@ fun BoxWithConstraintsScope.ChatItemsList( } val chatSectionLoad = ChatSectionLoad(0, ChatSectionArea.Destination) apiLoadMessagesAroundItem(rhId = remoteHostId, chatModel = chatModel, chatInfo = chatInfo, aroundItemId = itemId, chatSectionLoad = chatSectionLoad) - val idx = reversedChatItems.indexOfFirst { it.id == itemId } scope.launch { - listState.animateScrollToItem(scrollPosition(idx), -maxHeightRounded) - withContext(Dispatchers.Main) { - if (!itemsToDrop.isNullOrEmpty()) { - itemsToDrop.forEach { - chatModel.chatItemsSectionArea.remove(it.id) + val idx = sections.chatItemPosition(itemId) + if (idx != null) { + listState.animateScrollToItem(scrollPosition(idx), -maxHeightRounded) + withContext(Dispatchers.Main) { + if (!itemsToDrop.isNullOrEmpty()) { + itemsToDrop.forEach { + chatModel.chatItemsSectionArea.remove(it.id) + } + chatModel.chatItems.value.removeIf { chatModel.chatItemsSectionArea[it.id] == null } + val newIdx = reversedChatItems.indexOfFirst { it.id == itemId } + listState.scrollToItem(scrollPosition(newIdx), -maxHeightRounded) } - chatModel.chatItems.value.removeIf { chatModel.chatItemsSectionArea[it.id] == null } - val newIdx = reversedChatItems.indexOfFirst { it.id == itemId } - listState.scrollToItem(scrollPosition(newIdx), -maxHeightRounded) } } preloadItemsEnabled.value = true @@ -1364,7 +1369,7 @@ fun BoxWithConstraintsScope.ChatItemsList( // index here is just temporary, should be removed at all or put in the section items val prevItem = area.getPreviousShownItem(sIdx, i) val nextItem = area.getNextShownItem(sIdx, i) - ChatViewListItem(section.itemPositions[cItem.id] ?: -1, section, cItem, prevItem, nextItem) + ChatViewListItem(area.itemPositions[cItem.id] ?: -1, section, cItem, prevItem, nextItem) } } else { val item = section.items.first() @@ -1372,7 +1377,7 @@ fun BoxWithConstraintsScope.ChatItemsList( // here you make one collapsed item from multiple items (should be already in section items) val prevItem = area.getPreviousShownItem(sIdx, section.items.lastIndex) val nextItem = area.getNextShownItem(sIdx, section.items.lastIndex) - ChatViewListItem(section.itemPositions[item.id] ?: -1, section, item, prevItem, nextItem) + ChatViewListItem(area.itemPositions[item.id] ?: -1, section, item, prevItem, nextItem) } } }