fix(nests): keep presence out of channel chat + render kind:10312
Two-part fix for kind-10312 [MeetingRoomPresenceEvent] showing up as
empty cards in the channel chat panel.
(1) Filter at the chat-feed level. ChannelFeedFilter now rejects
MeetingRoomPresenceEvent — these only land in `channel.notes` so the
home live-bubble (HomeLiveFilter) can detect a follow broadcasting
in a Nest by walking channel.notes. They aren't chat content; the
chat panel rendering them as empty rows was a leak from that
home-bubble plumbing.
The filter is in ChannelFeedFilter rather than upstream in
LocalCache.consume because removing presence from channel.notes
would silently break the home-bubble's broadcasting-now detection.
This way the channel keeps the events for non-chat consumers and
the chat just refuses to surface them.
(2) Renderer fallback. NoteCompose now dispatches kind-10312 to
RenderMeetingRoomPresence — a one-line italic status row ("@user ·
raised their hand" / "stepped on stage" / "is speaking" / "joined
as audience" / "left the nest"). After (1) this only shows up on
non-chat surfaces (search results, thread view of a quoted
presence, profile timeline, …), but renders informatively instead
of as the empty card it was before.
Five new strings under nest_presence_*.
https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
This commit is contained in:
@@ -1039,6 +1039,11 @@ private fun RenderNoteRow(
|
|||||||
RenderMeetingRoomEvent(baseNote, accountViewModel, nav)
|
RenderMeetingRoomEvent(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent -> {
|
||||||
|
com.vitorpamplona.amethyst.ui.note.types
|
||||||
|
.RenderMeetingRoomPresence(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
is GitRepositoryEvent -> {
|
is GitRepositoryEvent -> {
|
||||||
RenderGitRepositoryEvent(baseNote, accountViewModel, nav)
|
RenderGitRepositoryEvent(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,72 @@ fun RenderMeetingRoomEventInner(
|
|||||||
RenderParticipants(participants, accountViewModel, nav)
|
RenderParticipants(participants, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inline renderer for kind-10312 [MeetingRoomPresenceEvent] events.
|
||||||
|
* The event's `content` is empty; the interesting bits are the
|
||||||
|
* presence flags. Render a one-line italic status — "@user · raised
|
||||||
|
* their hand" — so a presence event that leaks into a feed surface
|
||||||
|
* outside the room (search results, thread view of a quoted
|
||||||
|
* presence, …) renders as a status note instead of an empty card.
|
||||||
|
*
|
||||||
|
* The active room's chat panel does NOT render presence events at
|
||||||
|
* all — `ChannelFeedFilter` filters them out so the chat stays
|
||||||
|
* just chat. This composable is the fallback for everything else.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun RenderMeetingRoomPresence(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val event = baseNote.event as? com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent ?: return
|
||||||
|
|
||||||
|
val handRaised = remember(event) { event.handRaised() == true }
|
||||||
|
val publishing = remember(event) { event.publishing() == true }
|
||||||
|
val onstage = remember(event) { event.onstage() == true }
|
||||||
|
|
||||||
|
val stateRes =
|
||||||
|
when {
|
||||||
|
!publishing && !onstage && !handRaised -> R.string.nest_presence_left
|
||||||
|
handRaised -> R.string.nest_presence_raised_hand
|
||||||
|
onstage && publishing -> R.string.nest_presence_speaking
|
||||||
|
onstage -> R.string.nest_presence_on_stage
|
||||||
|
else -> R.string.nest_presence_listening
|
||||||
|
}
|
||||||
|
|
||||||
|
val user =
|
||||||
|
remember(event.pubKey) {
|
||||||
|
com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
.getOrCreateUser(event.pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.clickable { nav.nav(routeFor(user)) },
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
ClickableUserPicture(user, 20.dp, accountViewModel)
|
||||||
|
Spacer(modifier = Modifier.padding(horizontal = 4.dp))
|
||||||
|
UsernameDisplay(
|
||||||
|
baseUser = user,
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.Normal,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(horizontal = 4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringRes(stateRes),
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic,
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderParticipants(
|
private fun RenderParticipants(
|
||||||
participants: List<ParticipantTag>,
|
participants: List<ParticipantTag>,
|
||||||
|
|||||||
+16
-2
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ChangesFlowFilter
|
import com.vitorpamplona.amethyst.ui.dal.ChangesFlowFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||||
|
import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent
|
||||||
|
|
||||||
class ChannelFeedFilter(
|
class ChannelFeedFilter(
|
||||||
val channel: Channel,
|
val channel: Channel,
|
||||||
@@ -37,12 +38,25 @@ class ChannelFeedFilter(
|
|||||||
override fun changesFlow() = channel.changesFlow()
|
override fun changesFlow() = channel.changesFlow()
|
||||||
|
|
||||||
// returns the last Note of each user.
|
// returns the last Note of each user.
|
||||||
override fun feed(): List<Note> = sort(channel.notes.filterIntoSet { key, it -> account.isAcceptable(it) })
|
override fun feed(): List<Note> = sort(channel.notes.filterIntoSet { key, it -> isChatEvent(it) && account.isAcceptable(it) })
|
||||||
|
|
||||||
override fun applyFilter(newItems: Set<Note>): Set<Note> =
|
override fun applyFilter(newItems: Set<Note>): Set<Note> =
|
||||||
newItems
|
newItems
|
||||||
.filter { channel.notes.containsKey(it.idHex) && account.isAcceptable(it) }
|
.filter { channel.notes.containsKey(it.idHex) && isChatEvent(it) && account.isAcceptable(it) }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
|
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject non-chat events that get attached to a [Channel] for other
|
||||||
|
* surfaces. The current case: kind-10312
|
||||||
|
* [MeetingRoomPresenceEvent]s land in `channel.notes` so the home
|
||||||
|
* live-bubble can detect a follow broadcasting in a Nest
|
||||||
|
* (HomeLiveFilter scans channel.notes); they have no chat content
|
||||||
|
* and would otherwise render as an empty card in the chat panel.
|
||||||
|
*
|
||||||
|
* Anything else is passed through — chat messages, zaps, raids,
|
||||||
|
* clips, channel-create / metadata events all belong here.
|
||||||
|
*/
|
||||||
|
private fun isChatEvent(note: Note): Boolean = note.event !is MeetingRoomPresenceEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -530,6 +530,11 @@
|
|||||||
<string name="nest_notification_stop">Stop</string>
|
<string name="nest_notification_stop">Stop</string>
|
||||||
<string name="nest_join">Join nest</string>
|
<string name="nest_join">Join nest</string>
|
||||||
<string name="nest_lobby_host_label">Host</string>
|
<string name="nest_lobby_host_label">Host</string>
|
||||||
|
<string name="nest_presence_raised_hand">raised their hand</string>
|
||||||
|
<string name="nest_presence_on_stage">stepped on stage</string>
|
||||||
|
<string name="nest_presence_speaking">is speaking</string>
|
||||||
|
<string name="nest_presence_listening">joined as audience</string>
|
||||||
|
<string name="nest_presence_left">left the nest</string>
|
||||||
<string name="nest_leave">Leave</string>
|
<string name="nest_leave">Leave</string>
|
||||||
<string name="nest_leave_host_title">End this nest?</string>
|
<string name="nest_leave_host_title">End this nest?</string>
|
||||||
<string name="nest_leave_host_body">You\'re the host. Closing the room will disconnect everyone. Choose \"Just leave\" if you want to come back later — the room will auto-close after 8 hours of inactivity.</string>
|
<string name="nest_leave_host_body">You\'re the host. Closing the room will disconnect everyone. Choose \"Just leave\" if you want to come back later — the room will auto-close after 8 hours of inactivity.</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user