mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
fix(concord): populate channel community metadata account-wide so the Messages chip shows
Each Concord channel row in the Messages tab has a chip naming its parent community (tap → opens the community), mirroring the NIP-29 relay chip — but it only renders when ConcordChannel.communityName is set, and that was populated solely by refreshConcordChannelIndex() inside the Concord hub screen's subscription composable. On the Messages tab that screen isn't mounted, so the channel objects there never got their community name/icon and the chip was absent. Moves the channel-index refresh to an account-scoped collector on the ConcordSessionManager revision, so community metadata (name/icon, channel flags, membership) and per-community ban pruning apply across the whole app the moment a Control Plane folds — not only while the hub screen is open. The hub subscription now just re-derives its filters; the shared refresh lives in Account. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzJ2Cwo8tg4oZq43oRa3ig
This commit is contained in:
@@ -137,6 +137,7 @@ import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.EventProcessor
|
||||
import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEntry
|
||||
import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEvent
|
||||
import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.ConcordPermissions
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.MetadataEntity
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.RoleEntity
|
||||
@@ -447,6 +448,35 @@ class Account(
|
||||
cache.consumeConcordRumor(communityId, channelIdHex, rumor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies each folded community's metadata (name/icon, channel flags, this account's
|
||||
* membership) onto its [ConcordChannel] objects in the cache, and drops messages from
|
||||
* authors banned since they loaded. Runs account-wide on every
|
||||
* [com.vitorpamplona.amethyst.commons.model.concord.ConcordSessionManager] revision —
|
||||
* NOT gated behind the Concord hub screen — so every surface (the Messages-tab
|
||||
* community chip, the chat screen title) reflects the current fold, and bans apply,
|
||||
* even when the hub was never opened.
|
||||
*/
|
||||
fun refreshConcordChannelIndex() {
|
||||
val myPubKey = signer.pubKey
|
||||
val relaysByCommunity =
|
||||
concordChannelList.liveCommunities.value.associate { entry ->
|
||||
entry.id to entry.relays.mapNotNullTo(mutableSetOf()) { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
}
|
||||
for (session in concordSessions.sessions()) {
|
||||
val state = session.state.value ?: continue
|
||||
val communityId = session.entry.id
|
||||
val relays = relaysByCommunity[communityId] ?: emptySet()
|
||||
for (channelIdHex in state.channels.keys) {
|
||||
val channel = cache.getOrCreateConcordChannel(ConcordChannelId(communityId, channelIdHex))
|
||||
channel.updateFrom(state, relays, myPubKey)
|
||||
channel.notes
|
||||
.filter { _, note -> note.event?.pubKey?.let { state.authority.isBanned(it) } == true }
|
||||
.forEach { channel.removeNote(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val publicChatListDecryptionCache = PublicChatListDecryptionCache(signer)
|
||||
val publicChatList = PublicChatListState(signer, cache, publicChatListDecryptionCache, scope, settings)
|
||||
|
||||
@@ -4713,6 +4743,13 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
// Keep Concord channel metadata (community name/icon, membership) live across the whole
|
||||
// app — not just the hub screen — so the Messages tab renders each channel's community
|
||||
// chip, and per-community bans apply, as soon as a Control Plane folds.
|
||||
scope.launch {
|
||||
concordSessions.revision.collect { refreshConcordChannelIndex() }
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
cache.antiSpam.flowSpam.collect {
|
||||
it.cache.spamMessages.snapshot().values.forEach { spammer ->
|
||||
|
||||
+5
-36
@@ -26,16 +26,12 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
|
||||
/**
|
||||
* Mount on any screen that lists the user's joined Concord Channels (the Messages
|
||||
* tab, the Concord home) to keep their planes live and their folded metadata in
|
||||
* the [LocalCache] channel index.
|
||||
* the LocalCache channel index.
|
||||
*
|
||||
* The query state is keyed on the account (stable), so the assembler wouldn't
|
||||
* re-run its filter derivation on its own when a community folds or the joined set
|
||||
@@ -57,39 +53,12 @@ fun ConcordChannelSubscription(
|
||||
|
||||
val revision by account.concordSessions.revision.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(revision) {
|
||||
refreshConcordChannelIndex(account)
|
||||
// The channel-index refresh (community name/icon, membership, ban pruning) runs
|
||||
// account-wide from Account on this same revision, so the Messages tab has chips even
|
||||
// when this screen was never opened. Here we only need to re-derive the subscription
|
||||
// filters, since a newly-folded channel plane must now be subscribed.
|
||||
dataSource.invalidateFilters()
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects each folded community session into the shared LocalCache channel index
|
||||
* so the Messages list and chat screens render an up-to-date [ConcordChannel]
|
||||
* (name, voice/private flags, community name/relays, this account's membership).
|
||||
*/
|
||||
private fun refreshConcordChannelIndex(account: Account) {
|
||||
val myPubKey = account.signer.pubKey
|
||||
val relaysByCommunity =
|
||||
account.concordChannelList.liveCommunities.value
|
||||
.associate { entry ->
|
||||
entry.id to entry.relays.mapNotNullTo(mutableSetOf()) { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
}
|
||||
|
||||
for (session in account.concordSessions.sessions()) {
|
||||
val state = session.state.value ?: continue
|
||||
val communityId = session.entry.id
|
||||
val relays = relaysByCommunity[communityId] ?: emptySet()
|
||||
for (channelIdHex in state.channels.keys) {
|
||||
val channel = LocalCache.getOrCreateConcordChannel(ConcordChannelId(communityId, channelIdHex))
|
||||
channel.updateFrom(state, relays, myPubKey)
|
||||
// A member banned since these notes loaded: drop their messages now (the
|
||||
// ingest gate stops future ones). removeNote invalidates the feed, so the
|
||||
// ban is reflected live rather than only on the next feed pass.
|
||||
channel.notes
|
||||
.filter { _, note -> note.event?.pubKey?.let { state.authority.isBanned(it) } == true }
|
||||
.forEach { channel.removeNote(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user