From cc83c244278868f71fe71faa0c2adbc3a3c4d19b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 01:43:08 +0000 Subject: [PATCH] fix: dedupe public-channel rows in chatroom list updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the initial chatroom list picked a ChannelMetadataEvent or ChannelCreateEvent as the representative note for a public channel (because no ChannelMessageEvent had been seen yet), the arrival of a later ChannelMessageEvent caused updateListWith to append a second entry for the same channel — its match check only recognized old notes whose event was a ChannelMessageEvent. Two notes for the same channelId then produced the same PublicChannelLazyKey in the LazyColumn and crashed with IllegalArgumentException: Key was already used. Extract the channel id from any of the three public-chat event types when matching the existing entry so the new message replaces the placeholder instead of duplicating it. --- .../rooms/dal/ChatroomListKnownFeedFilter.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt index cba0f06476..c11283c657 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/dal/ChatroomListKnownFeedFilter.kt @@ -30,6 +30,8 @@ import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId 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 import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent class ChatroomListKnownFeedFilter( @@ -111,7 +113,7 @@ class ChatroomListKnownFeedFilter( newRelevantPublicMessages.forEach { newNotePair -> var hasUpdated = false oldList.forEach { oldNote -> - val channelId = (oldNote.event as? ChannelMessageEvent)?.channelId() + val channelId = publicChannelIdOf(oldNote) if (newNotePair.key == channelId) { hasUpdated = true if ((newNotePair.value.createdAt() ?: 0L) > (oldNote.createdAt() ?: 0L)) { @@ -260,4 +262,18 @@ class ChatroomListKnownFeedFilter( } override fun sort(items: Set): List = items.sortedWith(DefaultFeedOrder) + + // Maps a note that represents a public chat row to its channel id. The + // representative note for a channel may be the channel's create event + // (id == channelId), a metadata update, or a message — match all three so + // an arriving ChannelMessageEvent replaces an existing placeholder + // metadata/create note for the same channel instead of duplicating it + // (which would yield the same LazyColumn key twice). + private fun publicChannelIdOf(note: Note): String? = + when (val event = note.event) { + is ChannelMessageEvent -> event.channelId() + is ChannelMetadataEvent -> event.channelId() + is ChannelCreateEvent -> event.id + else -> null + } }