refactor(nests): collapse 4 per-room subs into one NestRoomFilterAssembler
The room screen used to fan out four parallel relay subscriptions
through separate ComposeSubscriptionManagers — RoomChat, RoomPresence,
RoomReactions, RoomAdminCommands — each opening its own REQ per outbox
relay. They all keyed on the same dimensions (roomATag, account) and
ran in the same place (NestActivityContent), so the multi-sub
fan-out was wire waste.
Replace with a single NestRoomFilterAssembler that issues two Filters
inside one REQ per relay:
- {kinds: [1311, 10312, 7], "#a": [roomATag]}
→ chat, presence, reactions on the shared a-tag gate
- {kinds: [4312], "#a": [roomATag], "#p": [localPubkey]}
→ admin commands; #p restriction stays on a separate Filter so
it doesn't over-restrict the chat / presence / reaction kinds
Net wire change: 4 REQs per relay → 1 REQ per relay with 2 Filters,
same event coverage. Same kinds, same #a/#p semantics, same outbox
relay set.
Files removed:
RoomChatFilterAssembler.kt
RoomPresenceFilterAssembler.kt
RoomPresenceFilterAssemblerSubscription.kt
RoomReactionsFilterAssembler.kt
RoomAdminCommandsFilterAssembler.kt
Files updated:
NestActivityContent.kt — one NestRoomFilterAssemblerSubscription
call replaces four. The per-kind LocalCache.observeEvents
LaunchedEffects stay; only the wire side got collapsed.
RelaySubscriptionsCoordinator.kt — register `nestRoom` instead of
the four old per-room assemblers.
NestsFilterAssembler (the rooms-list level subscription) is unchanged
— different keying (per-user, per-follow-list), different lifecycle.
https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
This commit is contained in:
+3
-8
@@ -45,11 +45,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource.HashtagF
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestRoomFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomAdminCommandsFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomChatFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomPresenceFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomReactionsFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssembler
|
||||||
@@ -111,10 +108,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
val publicChats = PublicChatsFilterAssembler(client)
|
val publicChats = PublicChatsFilterAssembler(client)
|
||||||
val liveStreams = LiveStreamsFilterAssembler(client)
|
val liveStreams = LiveStreamsFilterAssembler(client)
|
||||||
val nests = NestsFilterAssembler(client)
|
val nests = NestsFilterAssembler(client)
|
||||||
val roomPresence = RoomPresenceFilterAssembler(client)
|
val nestRoom = NestRoomFilterAssembler(client)
|
||||||
val roomChat = RoomChatFilterAssembler(client)
|
|
||||||
val roomReactions = RoomReactionsFilterAssembler(client)
|
|
||||||
val roomAdminCommands = RoomAdminCommandsFilterAssembler(client)
|
|
||||||
val longs = LongsFilterAssembler(client)
|
val longs = LongsFilterAssembler(client)
|
||||||
val articles = ArticlesFilterAssembler(client)
|
val articles = ArticlesFilterAssembler(client)
|
||||||
val badges = BadgesFilterAssembler(client)
|
val badges = BadgesFilterAssembler(client)
|
||||||
@@ -140,6 +134,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
followPacksList,
|
followPacksList,
|
||||||
liveStreams,
|
liveStreams,
|
||||||
nests,
|
nests,
|
||||||
|
nestRoom,
|
||||||
longs,
|
longs,
|
||||||
articles,
|
articles,
|
||||||
badges,
|
badges,
|
||||||
|
|||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
|
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
|
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||||
|
import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-room state for every wire subscription scoped to a single
|
||||||
|
* Nest. Combines what used to be four parallel `*QueryState`s
|
||||||
|
* (chat, presence, reactions, admin commands) into one — the four
|
||||||
|
* relay REQs collapsed into a single subscription with two
|
||||||
|
* [Filter]s per relay (see [NestRoomFilterSubAssembler.updateFilter]).
|
||||||
|
*/
|
||||||
|
@Stable
|
||||||
|
class NestRoomQueryState(
|
||||||
|
val roomATag: String,
|
||||||
|
val localPubkey: String,
|
||||||
|
val account: Account,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single per-room sub-assembler. Issues two [Filter]s per outbox
|
||||||
|
* relay on every key update:
|
||||||
|
*
|
||||||
|
* 1. `kinds=[1311, 10312, 7]`, `#a=[roomATag]` — the chat, presence
|
||||||
|
* and reaction streams the room consumes. Bundling them into
|
||||||
|
* one Filter is safe: a single Filter requires (kind ∈ kinds)
|
||||||
|
* AND (every tag dimension matches), and these three streams
|
||||||
|
* share the same `#a` gate.
|
||||||
|
*
|
||||||
|
* 2. `kinds=[4312]`, `#a=[roomATag]`, `#p=[localPubkey]` — admin
|
||||||
|
* commands. Stays separate from (1) because the additional
|
||||||
|
* `#p` constraint must NOT apply to the chat/presence/reaction
|
||||||
|
* streams (they don't tag the local user). A second Filter on
|
||||||
|
* the same REQ keeps them as one wire subscription.
|
||||||
|
*
|
||||||
|
* Replaces the previous fan-out of four sibling assemblers — chat,
|
||||||
|
* presence, reactions, admin commands — that each opened their own
|
||||||
|
* REQ. Net wire change: 4 REQs per relay → 1 REQ per relay with 2
|
||||||
|
* Filters, same event coverage.
|
||||||
|
*/
|
||||||
|
class NestRoomFilterSubAssembler(
|
||||||
|
client: INostrClient,
|
||||||
|
allKeys: () -> Set<NestRoomQueryState>,
|
||||||
|
) : PerUniqueIdEoseManager<NestRoomQueryState, String>(client, allKeys) {
|
||||||
|
override fun updateFilter(
|
||||||
|
key: NestRoomQueryState,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
val relays = key.account.outboxRelays.flow.value
|
||||||
|
return relays.flatMap { relay ->
|
||||||
|
listOf(
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = relay,
|
||||||
|
filter =
|
||||||
|
Filter(
|
||||||
|
kinds =
|
||||||
|
listOf(
|
||||||
|
LiveActivitiesChatMessageEvent.KIND,
|
||||||
|
MeetingRoomPresenceEvent.KIND,
|
||||||
|
ReactionEvent.KIND,
|
||||||
|
),
|
||||||
|
tags = mapOf("a" to listOf(key.roomATag)),
|
||||||
|
since = since?.get(relay)?.time,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = relay,
|
||||||
|
filter =
|
||||||
|
Filter(
|
||||||
|
kinds = listOf(AdminCommandEvent.KIND),
|
||||||
|
tags = mapOf("a" to listOf(key.roomATag), "p" to listOf(key.localPubkey)),
|
||||||
|
since = since?.get(relay)?.time,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun id(key: NestRoomQueryState): String = "${key.roomATag}|${key.localPubkey}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class NestRoomFilterAssembler(
|
||||||
|
client: INostrClient,
|
||||||
|
) : ComposeSubscriptionManager<NestRoomQueryState>() {
|
||||||
|
private val sub = NestRoomFilterSubAssembler(client, ::allKeys)
|
||||||
|
|
||||||
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
|
override fun invalidateFilters() = sub.invalidateFilters()
|
||||||
|
|
||||||
|
override fun destroy() = sub.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle-aware subscription. Stays open while the room screen is
|
||||||
|
* in the tree; closes on dispose. One call replaces the previous
|
||||||
|
* four (`RoomChatFilterAssemblerSubscription`,
|
||||||
|
* `RoomPresenceFilterAssemblerSubscription`,
|
||||||
|
* `RoomReactionsFilterAssemblerSubscription`,
|
||||||
|
* `RoomAdminCommandsFilterAssemblerSubscription`).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NestRoomFilterAssemblerSubscription(
|
||||||
|
roomATag: String,
|
||||||
|
localPubkey: String,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) = NestRoomFilterAssemblerSubscription(
|
||||||
|
roomATag,
|
||||||
|
localPubkey,
|
||||||
|
accountViewModel.account,
|
||||||
|
accountViewModel.dataSources().nestRoom,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NestRoomFilterAssemblerSubscription(
|
||||||
|
roomATag: String,
|
||||||
|
localPubkey: String,
|
||||||
|
account: Account,
|
||||||
|
filterAssembler: NestRoomFilterAssembler,
|
||||||
|
) {
|
||||||
|
val state =
|
||||||
|
remember(roomATag, localPubkey) {
|
||||||
|
NestRoomQueryState(roomATag, localPubkey, account)
|
||||||
|
}
|
||||||
|
KeyDataSourceSubscription(state, filterAssembler)
|
||||||
|
}
|
||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-screen state for the per-room kind-4312 admin command sub.
|
|
||||||
* Filtered by both `#a` (the room) and `#p` (the local pubkey) so
|
|
||||||
* the relay only sends commands actually targeting the local user.
|
|
||||||
* Authority enforcement happens client-side in the room screen.
|
|
||||||
*/
|
|
||||||
@Stable
|
|
||||||
class RoomAdminCommandsQueryState(
|
|
||||||
val roomATag: String,
|
|
||||||
val localPubkey: String,
|
|
||||||
val account: Account,
|
|
||||||
)
|
|
||||||
|
|
||||||
class RoomAdminCommandsFilterSubAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
allKeys: () -> Set<RoomAdminCommandsQueryState>,
|
|
||||||
) : PerUniqueIdEoseManager<RoomAdminCommandsQueryState, String>(client, allKeys) {
|
|
||||||
override fun updateFilter(
|
|
||||||
key: RoomAdminCommandsQueryState,
|
|
||||||
since: SincePerRelayMap?,
|
|
||||||
): List<RelayBasedFilter> {
|
|
||||||
val relays = key.account.outboxRelays.flow.value
|
|
||||||
return relays.map { relay ->
|
|
||||||
RelayBasedFilter(
|
|
||||||
relay = relay,
|
|
||||||
filter =
|
|
||||||
Filter(
|
|
||||||
kinds = listOf(AdminCommandEvent.KIND),
|
|
||||||
tags = mapOf("a" to listOf(key.roomATag), "p" to listOf(key.localPubkey)),
|
|
||||||
since = since?.get(relay)?.time,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun id(key: RoomAdminCommandsQueryState): String = "${key.roomATag}|${key.localPubkey}"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class RoomAdminCommandsFilterAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
) : ComposeSubscriptionManager<RoomAdminCommandsQueryState>() {
|
|
||||||
private val sub = RoomAdminCommandsFilterSubAssembler(client, ::allKeys)
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
|
||||||
|
|
||||||
override fun invalidateFilters() = sub.invalidateFilters()
|
|
||||||
|
|
||||||
override fun destroy() = sub.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RoomAdminCommandsFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
localPubkey: String,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
) = RoomAdminCommandsFilterAssemblerSubscription(
|
|
||||||
roomATag,
|
|
||||||
localPubkey,
|
|
||||||
accountViewModel.account,
|
|
||||||
accountViewModel.dataSources().roomAdminCommands,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RoomAdminCommandsFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
localPubkey: String,
|
|
||||||
account: Account,
|
|
||||||
filterAssembler: RoomAdminCommandsFilterAssembler,
|
|
||||||
) {
|
|
||||||
val state = remember(roomATag, localPubkey) { RoomAdminCommandsQueryState(roomATag, localPubkey, account) }
|
|
||||||
KeyDataSourceSubscription(state, filterAssembler)
|
|
||||||
}
|
|
||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-screen state for the per-room kind-1311 live-chat
|
|
||||||
* subscription. Mirror of [RoomPresenceQueryState] — the room's
|
|
||||||
* `a`-tag is the address-pointer string (`30312:<host>:<roomId>`).
|
|
||||||
*/
|
|
||||||
@Stable
|
|
||||||
class RoomChatQueryState(
|
|
||||||
val roomATag: String,
|
|
||||||
val account: Account,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* REQs `kinds=[1311], #a=[roomATag]` against the user's outbox
|
|
||||||
* relays so the chat panel sees every message tagged for this
|
|
||||||
* room. Events arrive in [com.vitorpamplona.amethyst.model.LocalCache];
|
|
||||||
* the room screen pipes them into `NestViewModel.onChatEvent`.
|
|
||||||
*/
|
|
||||||
class RoomChatFilterSubAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
allKeys: () -> Set<RoomChatQueryState>,
|
|
||||||
) : PerUniqueIdEoseManager<RoomChatQueryState, String>(client, allKeys) {
|
|
||||||
override fun updateFilter(
|
|
||||||
key: RoomChatQueryState,
|
|
||||||
since: SincePerRelayMap?,
|
|
||||||
): List<RelayBasedFilter> {
|
|
||||||
val relays = key.account.outboxRelays.flow.value
|
|
||||||
return relays.map { relay ->
|
|
||||||
RelayBasedFilter(
|
|
||||||
relay = relay,
|
|
||||||
filter =
|
|
||||||
Filter(
|
|
||||||
kinds = listOf(LiveActivitiesChatMessageEvent.KIND),
|
|
||||||
tags = mapOf("a" to listOf(key.roomATag)),
|
|
||||||
since = since?.get(relay)?.time,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun id(key: RoomChatQueryState): String = key.roomATag
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class RoomChatFilterAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
) : ComposeSubscriptionManager<RoomChatQueryState>() {
|
|
||||||
private val sub = RoomChatFilterSubAssembler(client, ::allKeys)
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
|
||||||
|
|
||||||
override fun invalidateFilters() = sub.invalidateFilters()
|
|
||||||
|
|
||||||
override fun destroy() = sub.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lifecycle-aware Composable subscription. Sub stays open while the
|
|
||||||
* Composable is in the tree, then closes on dispose.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun RoomChatFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
) = RoomChatFilterAssemblerSubscription(
|
|
||||||
roomATag,
|
|
||||||
accountViewModel.account,
|
|
||||||
accountViewModel.dataSources().roomChat,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RoomChatFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
account: Account,
|
|
||||||
filterAssembler: RoomChatFilterAssembler,
|
|
||||||
) {
|
|
||||||
val state = remember(roomATag) { RoomChatQueryState(roomATag, account) }
|
|
||||||
KeyDataSourceSubscription(state, filterAssembler)
|
|
||||||
}
|
|
||||||
-95
@@ -1,95 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-screen state for the room-presence subscription. The room
|
|
||||||
* `a`-tag is the address-pointer string (e.g.
|
|
||||||
* `30312:<host-pubkey>:<roomId>`); the relay subscription returns
|
|
||||||
* every kind-10312 event tagged with that `#a`.
|
|
||||||
*/
|
|
||||||
@Stable
|
|
||||||
class RoomPresenceQueryState(
|
|
||||||
val roomATag: String,
|
|
||||||
val account: Account,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* REQs `kinds=[10312], #a=[roomATag]` against every relay the user
|
|
||||||
* already talks to. The events arrive in [com.vitorpamplona.amethyst.model.LocalCache]
|
|
||||||
* via the normal client pipeline; the room screen observes them
|
|
||||||
* with `LocalCache.observeEvents(...)` and feeds each event into
|
|
||||||
* `NestViewModel.onPresenceEvent`.
|
|
||||||
*
|
|
||||||
* One assembler instance services every audio-room screen — the
|
|
||||||
* `key` (= room a-tag) keeps overlapping screens (in unlikely
|
|
||||||
* scenarios such as PiP + opened-from-deep-link) cleanly separated.
|
|
||||||
*/
|
|
||||||
class RoomPresenceFilterSubAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
allKeys: () -> Set<RoomPresenceQueryState>,
|
|
||||||
) : PerUniqueIdEoseManager<RoomPresenceQueryState, String>(client, allKeys) {
|
|
||||||
override fun updateFilter(
|
|
||||||
key: RoomPresenceQueryState,
|
|
||||||
since: SincePerRelayMap?,
|
|
||||||
): List<RelayBasedFilter> {
|
|
||||||
// Use the user's outbox relay set; that's the same fan-out the
|
|
||||||
// global rooms-list subscription rides on, so the presence
|
|
||||||
// events arrive on the same connections as the room itself.
|
|
||||||
val relays = key.account.outboxRelays.flow.value
|
|
||||||
return relays.map { relay ->
|
|
||||||
RelayBasedFilter(
|
|
||||||
relay = relay,
|
|
||||||
filter =
|
|
||||||
Filter(
|
|
||||||
kinds = listOf(MeetingRoomPresenceEvent.KIND),
|
|
||||||
tags = mapOf("a" to listOf(key.roomATag)),
|
|
||||||
since = since?.get(relay)?.time,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun id(key: RoomPresenceQueryState): String = key.roomATag
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class RoomPresenceFilterAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
) : ComposeSubscriptionManager<RoomPresenceQueryState>() {
|
|
||||||
private val sub = RoomPresenceFilterSubAssembler(client, ::allKeys)
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
|
||||||
|
|
||||||
override fun invalidateFilters() = sub.invalidateFilters()
|
|
||||||
|
|
||||||
override fun destroy() = sub.destroy()
|
|
||||||
}
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lifecycle-aware subscription for the per-room kind-10312 presence
|
|
||||||
* stream. Mirror of [ThreadFilterAssemblerSubscription] — call this
|
|
||||||
* from the room screen and the relay sub stays open while the
|
|
||||||
* Composable is in the tree, then closes on dispose.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun RoomPresenceFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
) = RoomPresenceFilterAssemblerSubscription(
|
|
||||||
roomATag,
|
|
||||||
accountViewModel.account,
|
|
||||||
accountViewModel.dataSources().roomPresence,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RoomPresenceFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
account: Account,
|
|
||||||
filterAssembler: RoomPresenceFilterAssembler,
|
|
||||||
) {
|
|
||||||
val state = remember(roomATag) { RoomPresenceQueryState(roomATag, account) }
|
|
||||||
KeyDataSourceSubscription(state, filterAssembler)
|
|
||||||
}
|
|
||||||
-113
@@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
|
||||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-screen state for the per-room kind-7 reaction subscription.
|
|
||||||
* Mirror of [RoomChatQueryState] / [RoomPresenceQueryState].
|
|
||||||
*/
|
|
||||||
@Stable
|
|
||||||
class RoomReactionsQueryState(
|
|
||||||
val roomATag: String,
|
|
||||||
val account: Account,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* REQs `kinds=[7], #a=[roomATag]` — the room's reaction stream the
|
|
||||||
* floating-overlay UI consumes. Kind 9735 (zap receipts) is not yet
|
|
||||||
* in the filter; the v1 overlay only renders kind-7 reactions.
|
|
||||||
* Adding zaps later is a one-line filter change.
|
|
||||||
*/
|
|
||||||
class RoomReactionsFilterSubAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
allKeys: () -> Set<RoomReactionsQueryState>,
|
|
||||||
) : PerUniqueIdEoseManager<RoomReactionsQueryState, String>(client, allKeys) {
|
|
||||||
override fun updateFilter(
|
|
||||||
key: RoomReactionsQueryState,
|
|
||||||
since: SincePerRelayMap?,
|
|
||||||
): List<RelayBasedFilter> {
|
|
||||||
val relays = key.account.outboxRelays.flow.value
|
|
||||||
return relays.map { relay ->
|
|
||||||
RelayBasedFilter(
|
|
||||||
relay = relay,
|
|
||||||
filter =
|
|
||||||
Filter(
|
|
||||||
kinds = listOf(ReactionEvent.KIND),
|
|
||||||
tags = mapOf("a" to listOf(key.roomATag)),
|
|
||||||
since = since?.get(relay)?.time,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun id(key: RoomReactionsQueryState): String = key.roomATag
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class RoomReactionsFilterAssembler(
|
|
||||||
client: INostrClient,
|
|
||||||
) : ComposeSubscriptionManager<RoomReactionsQueryState>() {
|
|
||||||
private val sub = RoomReactionsFilterSubAssembler(client, ::allKeys)
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
|
||||||
|
|
||||||
override fun invalidateFilters() = sub.invalidateFilters()
|
|
||||||
|
|
||||||
override fun destroy() = sub.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lifecycle-aware Composable subscription. Sub stays open while the
|
|
||||||
* Composable is in the tree, then closes on dispose.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun RoomReactionsFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
) = RoomReactionsFilterAssemblerSubscription(
|
|
||||||
roomATag,
|
|
||||||
accountViewModel.account,
|
|
||||||
accountViewModel.dataSources().roomReactions,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RoomReactionsFilterAssemblerSubscription(
|
|
||||||
roomATag: String,
|
|
||||||
account: Account,
|
|
||||||
filterAssembler: RoomReactionsFilterAssembler,
|
|
||||||
) {
|
|
||||||
val state = remember(roomATag) { RoomReactionsQueryState(roomATag, account) }
|
|
||||||
KeyDataSourceSubscription(state, filterAssembler)
|
|
||||||
}
|
|
||||||
+18
-20
@@ -39,10 +39,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.service.nests.NestForegroundService
|
import com.vitorpamplona.amethyst.service.nests.NestForegroundService
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomAdminCommandsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestRoomFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomChatFilterAssemblerSubscription
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomPresenceFilterAssemblerSubscription
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomReactionsFilterAssemblerSubscription
|
|
||||||
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
@@ -145,12 +142,19 @@ private fun NestActivityBody(
|
|||||||
|
|
||||||
LaunchedEffect(viewModel, onStageKeys) { viewModel.updateSpeakers(onStageKeys) }
|
LaunchedEffect(viewModel, onStageKeys) { viewModel.updateSpeakers(onStageKeys) }
|
||||||
|
|
||||||
// Per-room kind-10312 presence: open the wire sub for the duration
|
// Single per-room wire subscription. NestRoomFilterAssembler bundles
|
||||||
// of this Composable, observe matching events from LocalCache, feed
|
// every #a-tagged kind we need (chat 1311, presence 10312,
|
||||||
// them into the VM's aggregator. Drives the listener counter +
|
// reactions 7, admin commands 4312) into one REQ per relay — see
|
||||||
// (Tier 2) participant grid + (T1 #5) hand-raise queue.
|
// its docstring. Replaces the four parallel subscriptions this
|
||||||
|
// composable used to open. The LaunchedEffect collectors below
|
||||||
|
// still fan-out per-kind because each one feeds a different VM
|
||||||
|
// hook; the wire side is what got collapsed.
|
||||||
val roomATag = remember(event) { event.address().toValue() }
|
val roomATag = remember(event) { event.address().toValue() }
|
||||||
RoomPresenceFilterAssemblerSubscription(roomATag, accountViewModel)
|
val localPubkey = accountViewModel.account.signer.pubKey
|
||||||
|
NestRoomFilterAssemblerSubscription(roomATag, localPubkey, accountViewModel)
|
||||||
|
|
||||||
|
// Per-room kind-10312 presence — drives the listener counter,
|
||||||
|
// participant grid, and hand-raise queue.
|
||||||
LaunchedEffect(viewModel, roomATag) {
|
LaunchedEffect(viewModel, roomATag) {
|
||||||
val filter =
|
val filter =
|
||||||
Filter(
|
Filter(
|
||||||
@@ -171,10 +175,7 @@ private fun NestActivityBody(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-room kind-1311 live chat: open the wire sub for the duration
|
// Per-room kind-1311 live chat — feeds the VM's chat ledger.
|
||||||
// of this Composable, observe matching events from LocalCache, feed
|
|
||||||
// them into the VM's chat ledger.
|
|
||||||
RoomChatFilterAssemblerSubscription(roomATag, accountViewModel)
|
|
||||||
LaunchedEffect(viewModel, roomATag) {
|
LaunchedEffect(viewModel, roomATag) {
|
||||||
val filter =
|
val filter =
|
||||||
Filter(
|
Filter(
|
||||||
@@ -186,11 +187,10 @@ private fun NestActivityBody(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-room kind-7 reactions: same shape as chat. The VM's
|
// Per-room kind-7 reactions. The VM's sliding-window aggregator
|
||||||
// sliding-window aggregator drops entries older than 30 s; the
|
// drops entries older than 30 s; the 1-s tick below drives the
|
||||||
// 1-s tick below drives the eviction so the floating-up overlay
|
// eviction so the floating-up overlay animation timing is one-
|
||||||
// animation timing is one-place rather than per-component.
|
// place rather than per-component.
|
||||||
RoomReactionsFilterAssemblerSubscription(roomATag, accountViewModel)
|
|
||||||
LaunchedEffect(viewModel, roomATag) {
|
LaunchedEffect(viewModel, roomATag) {
|
||||||
val filter =
|
val filter =
|
||||||
Filter(
|
Filter(
|
||||||
@@ -214,8 +214,6 @@ private fun NestActivityBody(
|
|||||||
// (not in the relay) — only honour kicks where the signer's
|
// (not in the relay) — only honour kicks where the signer's
|
||||||
// ParticipantTag.canSpeak() returns true on the active
|
// ParticipantTag.canSpeak() returns true on the active
|
||||||
// kind-30312. nostrnests' UI does the same gating.
|
// kind-30312. nostrnests' UI does the same gating.
|
||||||
val localPubkey = accountViewModel.account.signer.pubKey
|
|
||||||
RoomAdminCommandsFilterAssemblerSubscription(roomATag, localPubkey, accountViewModel)
|
|
||||||
LaunchedEffect(viewModel, roomATag, localPubkey) {
|
LaunchedEffect(viewModel, roomATag, localPubkey) {
|
||||||
val filter =
|
val filter =
|
||||||
Filter(
|
Filter(
|
||||||
|
|||||||
Reference in New Issue
Block a user