From 46f305fe1a36454ada6221ad9961c1c85ad9971e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Apr 2026 15:51:15 +0000 Subject: [PATCH] perf(chats): typed sealed key instead of concatenated string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix used `"ch:${id}"`, `"dm:${users.sorted().joinToString}"` etc., which allocates a StringBuilder + char[] + new String per call — worst case for the DM branch which also allocates a sorted List on top. Replace with a sealed `ChatroomLazyKey` and per-type data classes that just wrap the existing String / RoomId / ChatroomKey. Equality and hashCode are auto-generated, so Compose still moves rows correctly on reorder, and we drop most of the per-key allocations: ch:abc -> PublicChannelLazyKey(abc) # 1 wrapper, reused String dm:userA,userB -> PrivateChatLazyKey(chatroomKey) # 1 wrapper, reused ChatroomKey eph:roomId -> EphemeralChannelLazyKey(roomId) # 1 wrapper, reused RoomId --- .../chats/rooms/feed/ChatroomListFeedView.kt | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt index 2845170f8..b852165f1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt @@ -48,7 +48,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderC import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent +import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent @@ -137,38 +139,63 @@ private fun FeedLoaded( } // Stable per-chatroom key — derived from chatroom identity, not the latest -// message id, so reorders move the row instead of recreating it. +// message id, so reorders move the row instead of recreating it. Uses a +// sealed wrapper around an existing String/RoomId/ChatroomKey to avoid the +// StringBuilder + concatenation allocations of a typed-prefix string key. +private sealed interface ChatroomLazyKey + +private data class MarmotChatroomLazyKey( + val groupId: HexKey, +) : ChatroomLazyKey + +private data class PublicChannelLazyKey( + val channelId: HexKey, +) : ChatroomLazyKey + +private data class EphemeralChannelLazyKey( + val roomId: RoomId, +) : ChatroomLazyKey + +private data class PrivateChatLazyKey( + val key: ChatroomKey, +) : ChatroomLazyKey + +private data class FallbackChatroomLazyKey( + val noteIdHex: HexKey, +) : ChatroomLazyKey + private fun chatroomLazyKey( item: Note, myPubKey: HexKey, -): String { +): ChatroomLazyKey { item.inGatherers ?.firstNotNullOfOrNull { it as? MarmotGroupChatroom } - ?.let { return "marmot:${it.nostrGroupId}" } + ?.let { return MarmotChatroomLazyKey(it.nostrGroupId) } return when (val event = item.event) { is ChannelMessageEvent -> { - "ch:${event.channelId() ?: item.idHex}" + PublicChannelLazyKey(event.channelId() ?: item.idHex) } is ChannelMetadataEvent -> { - "ch:${event.channelId() ?: item.idHex}" + PublicChannelLazyKey(event.channelId() ?: item.idHex) } is ChannelCreateEvent -> { - "ch:${event.id}" + PublicChannelLazyKey(event.id) } is EphemeralChatEvent -> { - "eph:${event.roomId()?.toKey() ?: item.idHex}" + event.roomId()?.let { EphemeralChannelLazyKey(it) } + ?: FallbackChatroomLazyKey(item.idHex) } is ChatroomKeyable -> { - "dm:${event.chatroomKey(myPubKey).users.sorted().joinToString(",")}" + PrivateChatLazyKey(event.chatroomKey(myPubKey)) } else -> { - item.idHex + FallbackChatroomLazyKey(item.idHex) } } }