perf(communities): stop rebuilding the card on every reaction/zap
RenderCommunitiesThumb was observing the full NoteState, which
recomposes on every reaction, zap, boost or metadata tick. Each
recomposition rebuilt the CommunityCard, re-parsing the definition
tags and calling moderatorKeys().toImmutableList() — producing a
brand-new ImmutableList instance. That new list identity then
invalidated LaunchedEffect(key = moderators) in LoadModerators,
re-running the expensive ParticipantListBuilder traversal (author +
all replies + boosts + zaps + reactions + public-chat notes) per
tick.
Fixes:
- Switch to observeNoteEvent<CommunityDefinitionEvent> so the
thumb only recomposes when the definition event itself changes
(including edits). Reactions/zaps are handled separately inside
LikeReaction/ZapReaction.
- remember(event.id) { CommunityCard(...) } so the card and its
moderator list identity are allocated once per event version.
- Stabilize LoadModerators' LaunchedEffect with (baseNote.idHex,
moderators). Convert the hosts list to a Set for O(1) filtering,
reuse a single ParticipantListBuilder instance, and skip the
redundant .minus(hosts) call on the new set.
This commit is contained in:
+30
-15
@@ -54,7 +54,7 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByPr
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
|
||||
@@ -91,16 +91,26 @@ fun RenderCommunitiesThumb(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteState by observeNote(baseNote, accountViewModel)
|
||||
val noteEvent = noteState.note.event as? CommunityDefinitionEvent ?: return
|
||||
// Narrow observation: we only care about the CommunityDefinitionEvent itself,
|
||||
// not every reaction/zap/boost that would otherwise recompose the whole card.
|
||||
val definition by observeNoteEvent<CommunityDefinitionEvent>(baseNote, accountViewModel)
|
||||
val event = definition ?: return
|
||||
|
||||
// Memoize card + moderator list identity so LaunchedEffect(moderators) in
|
||||
// LoadModerators doesn't retrigger the ParticipantListBuilder traversal on
|
||||
// every recomposition.
|
||||
val card =
|
||||
remember(event.id) {
|
||||
CommunityCard(
|
||||
name = event.name()?.ifBlank { null } ?: event.dTag(),
|
||||
description = event.description(),
|
||||
cover = event.image()?.imageUrl,
|
||||
moderators = event.moderatorKeys().toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
RenderCommunitiesThumb(
|
||||
CommunityCard(
|
||||
name = noteEvent.name()?.ifBlank { null } ?: noteEvent.dTag(),
|
||||
description = noteEvent.description(),
|
||||
cover = noteEvent.image()?.imageUrl,
|
||||
moderators = noteEvent.moderatorKeys().toImmutableList(),
|
||||
),
|
||||
card,
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
nav,
|
||||
@@ -193,16 +203,21 @@ fun LoadModerators(
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = moderators) {
|
||||
// Keying by the note id + moderator identity keeps this effect from
|
||||
// restarting on every recomposition (reaction/zap updates would otherwise
|
||||
// reallocate `moderators` and rerun the expensive traversal below).
|
||||
LaunchedEffect(key1 = baseNote.idHex, key2 = moderators) {
|
||||
launch(Dispatchers.IO) {
|
||||
val authorHex = baseNote.author?.pubkeyHex
|
||||
val hosts =
|
||||
moderators.mapNotNull { part ->
|
||||
if (part != baseNote.author?.pubkeyHex) {
|
||||
if (part != authorHex) {
|
||||
LocalCache.checkGetOrCreateUser(part)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val hostSet = hosts.toSet()
|
||||
|
||||
val topFilter = accountViewModel.account.liveDiscoveryFollowLists.value
|
||||
val discoveryTopFilterAuthors =
|
||||
@@ -217,15 +232,15 @@ fun LoadModerators(
|
||||
else -> null
|
||||
}
|
||||
|
||||
val followingKeySet = discoveryTopFilterAuthors
|
||||
val builder = ParticipantListBuilder()
|
||||
val allParticipants =
|
||||
ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts)
|
||||
builder.followsThatParticipateOn(baseNote, discoveryTopFilterAuthors) - hostSet
|
||||
|
||||
val newParticipantUsers =
|
||||
if (followingKeySet == null) {
|
||||
if (discoveryTopFilterAuthors == null) {
|
||||
val allFollows = accountViewModel.account.kind3FollowList.flow.value.authors
|
||||
val followingParticipants =
|
||||
ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts)
|
||||
builder.followsThatParticipateOn(baseNote, allFollows) - hostSet
|
||||
|
||||
(hosts + followingParticipants + (allParticipants - followingParticipants))
|
||||
.toImmutableList()
|
||||
|
||||
Reference in New Issue
Block a user