fix(chats): make ChatroomLazyKey Bundle-storable

LazyColumn keys are persisted in a SaveableStateHolder, which on Android
must be Bundle-storable (primitives, Parcelable, or Serializable). The
recent perf rework wrapped keys in plain data classes, which Kotlin does
not auto-mark Serializable, so opening a public chat crashed with:

  java.lang.IllegalArgumentException: Type of the key
    PublicChannelLazyKey(channelId=...) is not supported.

Mark the sealed interface Serializable, and decompose RoomId/ChatroomKey
fields (which live in quartz commonMain and can't depend on the JVM-only
Serializable interface) into String primitives. Each variant still wraps
a stable per-chatroom identity, so reorders move rows instead of
recreating them.
This commit is contained in:
Claude
2026-04-27 15:17:51 +00:00
parent 54ef9ead2d
commit d9ba7187ca
@@ -48,13 +48,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderC
import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent 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.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import java.io.Serializable
@Composable @Composable
fun ChatroomListFeedView( fun ChatroomListFeedView(
@@ -139,10 +138,11 @@ private fun FeedLoaded(
} }
// Stable per-chatroom key — derived from chatroom identity, not the latest // Stable per-chatroom key — derived from chatroom identity, not the latest
// message id, so reorders move the row instead of recreating it. Uses a // message id, so reorders move the row instead of recreating it. Compose
// sealed wrapper around an existing String/RoomId/ChatroomKey to avoid the // stores LazyColumn item keys in a SaveableStateHolder, which on Android
// StringBuilder + concatenation allocations of a typed-prefix string key. // requires Bundle-storable types, so each variant is Serializable and only
private sealed interface ChatroomLazyKey // holds primitives.
private sealed interface ChatroomLazyKey : Serializable
private data class MarmotChatroomLazyKey( private data class MarmotChatroomLazyKey(
val groupId: HexKey, val groupId: HexKey,
@@ -153,11 +153,12 @@ private data class PublicChannelLazyKey(
) : ChatroomLazyKey ) : ChatroomLazyKey
private data class EphemeralChannelLazyKey( private data class EphemeralChannelLazyKey(
val roomId: RoomId, val id: String,
val relayUrl: String,
) : ChatroomLazyKey ) : ChatroomLazyKey
private data class PrivateChatLazyKey( private data class PrivateChatLazyKey(
val key: ChatroomKey, val key: String,
) : ChatroomLazyKey ) : ChatroomLazyKey
private data class FallbackChatroomLazyKey( private data class FallbackChatroomLazyKey(
@@ -186,12 +187,12 @@ private fun chatroomLazyKey(
} }
is EphemeralChatEvent -> { is EphemeralChatEvent -> {
event.roomId()?.let { EphemeralChannelLazyKey(it) } event.roomId()?.let { EphemeralChannelLazyKey(it.id, it.relayUrl.url) }
?: FallbackChatroomLazyKey(item.idHex) ?: FallbackChatroomLazyKey(item.idHex)
} }
is ChatroomKeyable -> { is ChatroomKeyable -> {
PrivateChatLazyKey(event.chatroomKey(myPubKey)) PrivateChatLazyKey(event.chatroomKey(myPubKey).users.sorted().joinToString(","))
} }
else -> { else -> {