From 16edc24c1692c26c57f38760ad61708ca3848632 Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:16:39 +0700 Subject: [PATCH] android, desktop: testing grouping of chat items --- .../common/views/chat/ChatItemGroups.kt | 60 ++++++++++++++++++ .../simplex/common/views/chat/ChatView.kt | 63 +++++++++---------- 2 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemGroups.kt diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemGroups.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemGroups.kt new file mode 100644 index 0000000000..e93d19e53a --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemGroups.kt @@ -0,0 +1,60 @@ +package chat.simplex.common.views.chat + +import androidx.compose.runtime.snapshots.SnapshotStateList +import chat.simplex.common.model.* + +data class SectionItems ( + val mergeCategory: CIMergeCategory?, + val items: SnapshotStateList, + val revealed: Boolean, + val showAvatar: MutableSet, +) + +fun List.putIntoGroups(revealedItems: Set): List { + println("LALAL LENGTH ${size}") + val start = System.currentTimeMillis() + val groups = ArrayList() + var recent: SectionItems = if (isNotEmpty()) { + val first = this[0] + SectionItems( + mergeCategory = first.mergeCategory, + items = SnapshotStateList().also { it.add(first) }, + revealed = first.mergeCategory == null || revealedItems.contains(first.id), + showAvatar = mutableSetOf().also { if (first.chatDir is CIDirection.GroupRcv) it.add(first.id) }) + } else { + return emptyList() + } + groups.add(recent) + var prev = this[0] + var index = 0 + while (index < size) { + if (index == 0) { + index++ + continue + } + val item = this[index] + val category = item.mergeCategory + if (recent.mergeCategory == category) { + recent.items.add(item) + if (item.chatDir is CIDirection.GroupRcv && prev.chatDir is CIDirection.GroupRcv && item.chatDir.groupMember == (prev.chatDir as CIDirection.GroupRcv).groupMember) { + recent.showAvatar.add(item.id) + } + } else { + recent = SectionItems( + mergeCategory = item.mergeCategory, + items = SnapshotStateList().also { it.add(item) }, + revealed = item.mergeCategory == null || revealedItems.contains(item.id), + showAvatar = mutableSetOf().also { + if (item.chatDir is CIDirection.GroupRcv && (prev.chatDir !is CIDirection.GroupRcv || (prev.chatDir as CIDirection.GroupRcv).groupMember != item.chatDir.groupMember)) { + it.add(item.id) + } + } + ) + groups.add(recent) + } + prev = item + index++ + } + println("LALAL RES ${System.currentTimeMillis() - start}, groups: ${groups.size}") + return groups +} 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 d89782148a..8be2aceb6b 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 @@ -11,6 +11,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshots.AutoboxingStateValueProperty import androidx.compose.ui.* import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache @@ -988,6 +989,8 @@ fun BoxWithConstraintsScope.ChatItemsList( Spacer(Modifier.size(8.dp)) val reversedChatItems by remember { derivedStateOf { chatModel.chatItems.asReversed() } } + val revealedItems = rememberSaveable { mutableStateOf(setOf()) } + val groups by remember { derivedStateOf { (reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems + reversedChatItems).putIntoGroups(revealedItems.value) } } val maxHeightRounded = with(LocalDensity.current) { maxHeight.roundToPx() } val scrollToItem: (Long) -> Unit = { itemId: Long -> val index = reversedChatItems.indexOfFirst { it.id == itemId } @@ -1011,8 +1014,8 @@ fun BoxWithConstraintsScope.ChatItemsList( VideoPlayerHolder.releaseAll() } ) - LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) { - itemsIndexed(reversedChatItems, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem -> + @Composable + fun ChatViewListItem(i: Int, showAvatar: Boolean, cItem: ChatItem) { CompositionLocalProvider( // Makes horizontal and vertical scrolling to coexist nicely. // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view @@ -1104,7 +1107,7 @@ fun BoxWithConstraintsScope.ChatItemsList( } else { null to 1 } - if (prevItem == null || showMemberImage(member, prevItem) || prevMember != null) { + if (showMemberImage(member, prevItem) || showAvatar) { Column( Modifier .padding(top = 8.dp) @@ -1225,37 +1228,6 @@ fun BoxWithConstraintsScope.ChatItemsList( } } } - - val (currIndex, nextItem) = chatModel.getNextChatItem(cItem) - val ciCategory = cItem.mergeCategory - if (ciCategory != null && ciCategory == nextItem?.mergeCategory) { - // memberConnected events and deleted items are aggregated at the last chat item in a row, see ChatItemView - } else { - val (prevHidden, prevItem) = chatModel.getPrevShownChatItem(currIndex, ciCategory) - - val itemSeparation = getItemSeparation(cItem, nextItem) - val previousItemSeparation = if (prevItem != null) getItemSeparation(prevItem, cItem) else null - - if (itemSeparation.date != null) { - DateSeparator(itemSeparation.date) - } - - val range = chatViewItemsRange(currIndex, prevHidden) - if (revealed.value && range != null) { - reversedChatItems.subList(range.first, range.last + 1).forEachIndexed { index, ci -> - val prev = if (index + range.first == prevHidden) prevItem else reversedChatItems[index + range.first + 1] - ChatItemView(ci, null, prev, itemSeparation, previousItemSeparation) - } - } else { - ChatItemView(cItem, range, prevItem, itemSeparation, previousItemSeparation) - } - - if (i == reversedChatItems.lastIndex) { - DateSeparator(cItem.meta.itemTs) - } - } - - if (cItem.isRcvNew && chatInfo.id == ChatModel.chatId.value) { LaunchedEffect(cItem.id) { scope.launch { @@ -1264,6 +1236,29 @@ fun BoxWithConstraintsScope.ChatItemsList( } } } + val itemSeparation = getItemSeparation(cItem, null) + ChatItemView(cItem, null, null, itemSeparation, null) + } + } + LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) { + for (group in groups) { + if (group.revealed) { + itemsIndexed(group.items, key = { _, item -> (item.id to item.meta.createdAt.toEpochMilliseconds()).toString() }) { i, cItem -> + // index here is just temporary, should be removed at all or put in the section items + ChatViewListItem(reversedChatItems.indexOf(cItem), showAvatar = group.showAvatar.contains(cItem.id), cItem) + } + } else { + val item = group.items.last() + item(key = { (item.id to item.meta.createdAt.toEpochMilliseconds()).toString() }) { + // here you make one collapsed item from multiple items (should be already in section items) + ChatViewListItem(reversedChatItems.indexOf(item), showAvatar = group.showAvatar.contains(item.id), item) + } + } + } + + if (reversedChatItems.isNotEmpty()) { + item { + DateSeparator(reversedChatItems.last().meta.itemTs) } } }