fix(chats): stable per-chatroom LazyColumn key for messages list
The chatroom list keyed each row by `if (index == 0) index else item.idHex`. Two problems: 1. Position 0 was hardcoded to key `0`, so when a new chatroom moved to the top, the existing composition slot was reused with state from the previous chatroom — observed as "the row updated and reordered but still shows the old result" right at the top. 2. For other positions, the key was the latest message's `idHex`. When a new message arrived in any chatroom the chatroom's representative Note got replaced (different idHex), so Compose threw away the row and rebuilt it from scratch — wasted work. Fix: derive a stable key from chatroom identity instead of message id — nostr group id for marmot rooms, channel id for public/ephemeral channels, sorted user set for DMs. Falls back to `item.idHex` for unrecognized event types (drafts etc.). Reorders now move the row; new-message updates re-use the slot.
This commit is contained in:
+51
-4
@@ -25,14 +25,16 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
@@ -45,6 +47,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderCompose
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderCompose
|
||||||
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.nip01Core.core.HexKey
|
||||||
|
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
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomListFeedView(
|
fun ChatroomListFeedView(
|
||||||
@@ -103,14 +111,16 @@ private fun FeedLoaded(
|
|||||||
) {
|
) {
|
||||||
val items by loaded.feed.collectAsStateWithLifecycle()
|
val items by loaded.feed.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val myPubKey = accountViewModel.userProfile().pubkeyHex
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = rememberFeedContentPadding(FeedPadding),
|
contentPadding = rememberFeedContentPadding(FeedPadding),
|
||||||
state = listState,
|
state = listState,
|
||||||
) {
|
) {
|
||||||
itemsIndexed(
|
items(
|
||||||
items.list,
|
items.list,
|
||||||
key = { index, item -> if (index == 0) index else item.idHex },
|
key = { item -> chatroomLazyKey(item, myPubKey) },
|
||||||
) { _, item ->
|
) { item ->
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth()) {
|
||||||
ChatroomHeaderCompose(
|
ChatroomHeaderCompose(
|
||||||
item,
|
item,
|
||||||
@@ -125,3 +135,40 @@ 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.
|
||||||
|
private fun chatroomLazyKey(
|
||||||
|
item: Note,
|
||||||
|
myPubKey: HexKey,
|
||||||
|
): String {
|
||||||
|
item.inGatherers
|
||||||
|
?.firstNotNullOfOrNull { it as? MarmotGroupChatroom }
|
||||||
|
?.let { return "marmot:${it.nostrGroupId}" }
|
||||||
|
|
||||||
|
return when (val event = item.event) {
|
||||||
|
is ChannelMessageEvent -> {
|
||||||
|
"ch:${event.channelId() ?: item.idHex}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is ChannelMetadataEvent -> {
|
||||||
|
"ch:${event.channelId() ?: item.idHex}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is ChannelCreateEvent -> {
|
||||||
|
"ch:${event.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is EphemeralChatEvent -> {
|
||||||
|
"eph:${event.roomId()?.toKey() ?: item.idHex}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is ChatroomKeyable -> {
|
||||||
|
"dm:${event.chatroomKey(myPubKey).users.sorted().joinToString(",")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
item.idHex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user