refactor: rename relay-group REQ assemblers by when they run

The assembler names didn't say when each is active — most confusingly, a
"Threads" assembler with no matching "chat" one, because group chat (kind-9) is
served by the shared `channel` assembler, not a group-specific one. Rename the
four group-specific families for their surface, and document that chat has no
dedicated assembler:

  relayGroupDirectory  -> relayGroupsOnRelay        (browsing one relay's channels)
  relayGroupRoster     -> relayGroupMyJoinedGroups  (metadata+rosters of joined groups)
  relayGroupThreads    -> relayGroupThreadFeed      (a group's forum-threads tab)
  relayGroupPreview    -> relayGroupWarmup          (prefetch before a group opens)

Each family's FilterAssembler / QueryState / SubAssembler / Subscription + file
renamed to match. relayGroupsDiscovery is left as-is: it already names the
Discover feed and shares its token namespace with the screen/DAL/settings, so a
rename would either collide with RelayGroupDiscoveryFeedFilter or corrupt those.
Pure rename; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5MLY4hq5LXJ2D5WeLRyXj
This commit is contained in:
Claude
2026-07-09 20:05:43 +00:00
parent 23c93e43f9
commit 2ffd64c170
19 changed files with 94 additions and 89 deletions
@@ -37,11 +37,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile.datasource.P
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource.ChatroomFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupDirectoryFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupPreviewFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupRosterFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupThreadsFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupMyJoinedGroupsFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupThreadFeedFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupWarmupFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupsDiscoveryFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupsOnRelayFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource.ChessFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.datasource.CommunityFilterAssembler
@@ -118,11 +118,16 @@ class RelaySubscriptionsCoordinator(
// active depending on the screen.
val channel = ChannelFilterAssembler(client)
val relayGroupDirectory = RelayGroupDirectoryFilterAssembler(client)
val relayGroupRoster = RelayGroupRosterFilterAssembler(client)
val relayGroupThreads = RelayGroupThreadsFilterAssembler(client)
val relayGroupPreview = RelayGroupPreviewFilterAssembler(client)
val relayGroupsDiscovery = RelayGroupsDiscoveryFilterAssembler(client)
// NIP-29 relay-group REQ assemblers, each named for WHEN it runs. There is deliberately no
// "group chat" assembler: a group's kind-9 chat timeline is served by the shared [channel]
// assembler above (same as NIP-28 public chats), so only the group-specific surfaces get their
// own here.
val relayGroupsOnRelay = RelayGroupsOnRelayFilterAssembler(client) // browsing one relay's channel list
val relayGroupMyJoinedGroups = RelayGroupMyJoinedGroupsFilterAssembler(client) // metadata+rosters of groups I've joined
val relayGroupThreadFeed = RelayGroupThreadFeedFilterAssembler(client) // a group's forum-threads tab
val relayGroupWarmup = RelayGroupWarmupFilterAssembler(client) // prefetching a group before it's opened
val relayGroupsDiscovery = RelayGroupsDiscoveryFilterAssembler(client) // the cross-relay Discover feed
val chatroom = ChatroomFilterAssembler(client)
val community = CommunityFilterAssembler(client)
val gitRepository = RepositoryFilterAssembler(client)
@@ -184,10 +189,10 @@ class RelaySubscriptionsCoordinator(
val all =
listOf(
relayGroupDirectory,
relayGroupRoster,
relayGroupThreads,
relayGroupPreview,
relayGroupsOnRelay,
relayGroupMyJoinedGroups,
relayGroupThreadFeed,
relayGroupWarmup,
relayGroupsDiscovery,
account,
accountForeground,
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.LoadRelayGroupChannel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupPreviewSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupWarmupSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nip29RelayGroups.GroupInviteLink
@@ -93,7 +93,7 @@ private fun RelayGroupCardContent(
accountViewModel: AccountViewModel,
nav: INav,
) {
RelayGroupPreviewSubscription(baseChannel, accountViewModel.dataSources().relayGroupPreview, accountViewModel)
RelayGroupWarmupSubscription(baseChannel, accountViewModel.dataSources().relayGroupWarmup, accountViewModel)
// Recompose in place when the relay-signed metadata / roster changes.
val channelState by baseChannel
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarItem
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupRosterSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupMyJoinedGroupsSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.datasource.CommunitiesListFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription
@@ -132,7 +132,7 @@ private fun PreloadFor(
NavBarItem.PUBLIC_CHATS -> PublicChatsFilterAssemblerSubscription(accountViewModel)
NavBarItem.RELAY_GROUPS -> RelayGroupRosterSubscription(accountViewModel.dataSources().relayGroupRoster, accountViewModel)
NavBarItem.RELAY_GROUPS -> RelayGroupMyJoinedGroupsSubscription(accountViewModel.dataSources().relayGroupMyJoinedGroups, accountViewModel)
NavBarItem.FOLLOW_PACKS -> FollowPacksFilterAssemblerSubscription(accountViewModel)
@@ -60,7 +60,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupDirectorySubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupsOnRelaySubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
@@ -70,7 +70,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
/**
* Lists every channel a relay hosts (its kind 39000-39003 directory), so the user
* can browse and open channels on that relay. The relay's directory is streamed by
* [RelayGroupDirectorySubscription] and consumed into per-group channels; this
* [RelayGroupsOnRelaySubscription] and consumed into per-group channels; this
* screen reads them back for the relay and renders them.
*/
@Composable
@@ -81,7 +81,7 @@ fun RelayGroupChannelListScreen(
) {
val relay = remember(relayUrl) { RelayUrlNormalizer.normalizeOrNull(relayUrl) } ?: return
RelayGroupDirectorySubscription(relay, accountViewModel.dataSources().relayGroupDirectory, accountViewModel)
RelayGroupsOnRelaySubscription(relay, accountViewModel.dataSources().relayGroupsOnRelay, accountViewModel)
// Re-read the relay's channels whenever a group-metadata (kind 39000) event lands in
// the cache — driven by LocalCache.observeEvents rather than a timer, so the list
@@ -75,8 +75,8 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.dal.relayGroupDiscoveryChannelFor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupPreviewSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupRosterSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupMyJoinedGroupsSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupWarmupSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupsDiscoveryFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@@ -116,7 +116,7 @@ fun RelayGroupDiscoveryScreen(
RelayGroupsDiscoveryFilterAssemblerSubscription(accountViewModel)
// Keep the joined groups' metadata + rosters live so the "My Groups" filter can list them
// (their host relays aren't fetched by the discovery filter set).
RelayGroupRosterSubscription(accountViewModel.dataSources().relayGroupRoster, accountViewModel)
RelayGroupMyJoinedGroupsSubscription(accountViewModel.dataSources().relayGroupMyJoinedGroups, accountViewModel)
DisappearingScaffold(
isInvertedLayout = false,
@@ -242,7 +242,7 @@ private fun RelayGroupDiscoveryRow(
// Prefetch the group's recent content so opening the card lands on a populated screen. The
// metadata/rosters are already streaming from the directory subscription, so only ask for
// content here (contentOnly) instead of re-requesting 39000-39003 per visible row.
RelayGroupPreviewSubscription(baseChannel, accountViewModel.dataSources().relayGroupPreview, accountViewModel, contentOnly = true)
RelayGroupWarmupSubscription(baseChannel, accountViewModel.dataSources().relayGroupWarmup, accountViewModel, contentOnly = true)
val channelState by baseChannel
.flow()
@@ -535,7 +535,7 @@ private fun FollowsInGroupRow(
/**
* Display cap for the loaded-message counter — kept in step with the discovery preview's fetch
* limit (RELAY_GROUP_PREVIEW_LIMIT), so a chat that returns the full page reads as "50+ messages".
* limit (RELAY_GROUP_WARMUP_LIMIT), so a chat that returns the full page reads as "50+ messages".
*/
private const val DISCOVERY_MESSAGE_CAP = 50
@@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBack
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupPreviewSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupWarmupSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -107,7 +107,7 @@ private fun RelayGroupMembers(
// standalone screen is open. observeChannel alone does NOT do this for a relay group
// (its finder only handles public-chat / live-activity channels), so without this the
// screen would only show whatever the chat screen happened to cache.
RelayGroupPreviewSubscription(baseChannel, accountViewModel.dataSources().relayGroupPreview, accountViewModel)
RelayGroupWarmupSubscription(baseChannel, accountViewModel.dataSources().relayGroupWarmup, accountViewModel)
// Recompose when the relay-signed roster (39001/39002) changes.
val channelState by observeChannel(baseChannel, accountViewModel)
@@ -69,7 +69,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.CreatingTopBar
import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupPreviewSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupWarmupSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
@@ -115,7 +115,7 @@ fun RelayGroupEditScreen(
LoadRelayGroupChannel(groupId, accountViewModel) { channel ->
// Keep the relay-signed metadata fresh while editing so a late load prefills.
RelayGroupPreviewSubscription(channel, accountViewModel.dataSources().relayGroupPreview, accountViewModel)
RelayGroupWarmupSubscription(channel, accountViewModel.dataSources().relayGroupWarmup, accountViewModel)
val channelState by channel
.flow()
@@ -58,7 +58,7 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBack
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupThreadsSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupThreadFeedSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -69,7 +69,7 @@ import com.vitorpamplona.quartz.nip7DThreads.ThreadEvent
/**
* A group's forum-style threads (kind 11) — the secondary content type kept out of
* the kind-9 chat feed. Streams the group's threads + their comments via
* [RelayGroupThreadsSubscription]; tapping a thread opens the generic thread view
* [RelayGroupThreadFeedSubscription]; tapping a thread opens the generic thread view
* ([Route.Note]) with its comment tree. Members can start a new thread.
*/
@Composable
@@ -93,7 +93,7 @@ private fun RelayGroupThreads(
accountViewModel: AccountViewModel,
nav: INav,
) {
RelayGroupThreadsSubscription(channel, accountViewModel.dataSources().relayGroupThreads, accountViewModel)
RelayGroupThreadFeedSubscription(channel, accountViewModel.dataSources().relayGroupThreadFeed, accountViewModel)
val threads by channel.threads.collectAsStateWithLifecycle()
@@ -88,7 +88,7 @@ class RelayGroupDiscoveryFeedFilter(
*
* These live on their host relays (from kind 10009), not my outbox, so the standard
* [TopFilter.Mine] relay-set resolution wouldn't reach them. The joined groups' metadata +
* rosters are kept in cache by RelayGroupRosterSubscription mounted on the screen.
* rosters are kept in cache by RelayGroupMyJoinedGroupsSubscription mounted on the screen.
*/
private fun isMine(): Boolean = followList() is TopFilter.Mine
@@ -33,7 +33,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMembersEvent
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
/** One screen's request to keep the roster of the user's joined groups fresh. */
class RelayGroupRosterQueryState(
class RelayGroupMyJoinedGroupsQueryState(
val account: Account,
)
@@ -59,12 +59,12 @@ private val RELAY_GROUP_ROSTER_KINDS =
* One subscription per host relay, scoped by `#d` to just that relay's joined
* group ids so we don't pull a relay's whole directory, only what we're in.
*/
class RelayGroupRosterFilterAssembler(
class RelayGroupMyJoinedGroupsFilterAssembler(
client: INostrClient,
) : ComposeSubscriptionManager<RelayGroupRosterQueryState>() {
) : ComposeSubscriptionManager<RelayGroupMyJoinedGroupsQueryState>() {
val group =
listOf(
RelayGroupRosterSubAssembler(client, ::allKeys),
RelayGroupMyJoinedGroupsSubAssembler(client, ::allKeys),
)
override fun invalidateKeys() = invalidateFilters()
@@ -74,12 +74,12 @@ class RelayGroupRosterFilterAssembler(
override fun destroy() = group.forEach { it.destroy() }
}
class RelayGroupRosterSubAssembler(
class RelayGroupMyJoinedGroupsSubAssembler(
client: INostrClient,
allKeys: () -> Set<RelayGroupRosterQueryState>,
) : PerUniqueIdEoseManager<RelayGroupRosterQueryState, Account>(client, allKeys) {
allKeys: () -> Set<RelayGroupMyJoinedGroupsQueryState>,
) : PerUniqueIdEoseManager<RelayGroupMyJoinedGroupsQueryState, Account>(client, allKeys) {
override fun updateFilter(
key: RelayGroupRosterQueryState,
key: RelayGroupMyJoinedGroupsQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter>? {
val joined = key.account.relayGroupList.liveRelayGroupList.value
@@ -102,5 +102,5 @@ class RelayGroupRosterSubAssembler(
}
}
override fun id(key: RelayGroupRosterQueryState) = key.account
override fun id(key: RelayGroupMyJoinedGroupsQueryState) = key.account
}
@@ -39,13 +39,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
* the new group's roster (critical for confirming admission to a closed group).
*/
@Composable
fun RelayGroupRosterSubscription(
dataSource: RelayGroupRosterFilterAssembler,
fun RelayGroupMyJoinedGroupsSubscription(
dataSource: RelayGroupMyJoinedGroupsFilterAssembler,
accountViewModel: AccountViewModel,
) {
val state =
remember(accountViewModel.account) {
RelayGroupRosterQueryState(accountViewModel.account)
RelayGroupMyJoinedGroupsQueryState(accountViewModel.account)
}
val joined by accountViewModel.account.relayGroupList.liveRelayGroupList
@@ -32,7 +32,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
import com.vitorpamplona.quartz.nip7DThreads.ThreadEvent
/** One threads-screen's request for a single group's kind-11 threads. */
class RelayGroupThreadsQueryState(
class RelayGroupThreadFeedQueryState(
val channel: RelayGroupChannel,
)
@@ -43,12 +43,12 @@ class RelayGroupThreadsQueryState(
* kind-9 chat, so we don't pay for them until asked). Fetching the comments here
* too means opening a thread from the list has its replies already cached.
*/
class RelayGroupThreadsFilterAssembler(
class RelayGroupThreadFeedFilterAssembler(
client: INostrClient,
) : ComposeSubscriptionManager<RelayGroupThreadsQueryState>() {
) : ComposeSubscriptionManager<RelayGroupThreadFeedQueryState>() {
val group =
listOf(
RelayGroupThreadsSubAssembler(client, ::allKeys),
RelayGroupThreadFeedSubAssembler(client, ::allKeys),
)
override fun invalidateKeys() = invalidateFilters()
@@ -58,12 +58,12 @@ class RelayGroupThreadsFilterAssembler(
override fun destroy() = group.forEach { it.destroy() }
}
class RelayGroupThreadsSubAssembler(
class RelayGroupThreadFeedSubAssembler(
client: INostrClient,
allKeys: () -> Set<RelayGroupThreadsQueryState>,
) : PerUniqueIdEoseManager<RelayGroupThreadsQueryState, GroupId>(client, allKeys) {
allKeys: () -> Set<RelayGroupThreadFeedQueryState>,
) : PerUniqueIdEoseManager<RelayGroupThreadFeedQueryState, GroupId>(client, allKeys) {
override fun updateFilter(
key: RelayGroupThreadsQueryState,
key: RelayGroupThreadFeedQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> {
val groupId = key.channel.groupId
@@ -80,5 +80,5 @@ class RelayGroupThreadsSubAssembler(
)
}
override fun id(key: RelayGroupThreadsQueryState) = key.channel.groupId
override fun id(key: RelayGroupThreadFeedQueryState) = key.channel.groupId
}
@@ -28,14 +28,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
/** Mount on a group's Threads screen to stream its kind-11 threads + 1111 comments. */
@Composable
fun RelayGroupThreadsSubscription(
fun RelayGroupThreadFeedSubscription(
channel: RelayGroupChannel,
dataSource: RelayGroupThreadsFilterAssembler,
dataSource: RelayGroupThreadFeedFilterAssembler,
accountViewModel: AccountViewModel,
) {
val state =
remember(channel.groupId) {
RelayGroupThreadsQueryState(channel)
RelayGroupThreadFeedQueryState(channel)
}
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
@@ -36,14 +36,14 @@ import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
/** One on-screen group card's request to warm a single group. */
class RelayGroupPreviewQueryState(
class RelayGroupWarmupQueryState(
val channel: RelayGroupChannel,
/** When true, prefetch only recent content — the caller's screen already streams metadata. */
val contentOnly: Boolean = false,
)
/** Newest content kinds we prefetch so opening the card lands on populated screens. */
private val RELAY_GROUP_PREVIEW_CONTENT_KINDS =
private val RELAY_GROUP_WARMUP_CONTENT_KINDS =
listOf(ChatEvent.KIND, PollEvent.KIND, ThreadEvent.KIND, CommentEvent.KIND)
/**
@@ -51,7 +51,7 @@ private val RELAY_GROUP_PREVIEW_CONTENT_KINDS =
* discovery card's "50+ messages" activity signal (a chat that returns the full page reads as
* "50+"; fewer shows the exact loaded count).
*/
private const val RELAY_GROUP_PREVIEW_LIMIT = 50
private const val RELAY_GROUP_WARMUP_LIMIT = 50
/**
* Warms a NIP-29 group referenced inline (a group-link card) without opening it:
@@ -60,12 +60,12 @@ private const val RELAY_GROUP_PREVIEW_LIMIT = 50
* messages and threads so tapping the card lands on already-cached content. Both are
* pinned to the group's single host relay. Active only while the card is on-screen.
*/
class RelayGroupPreviewFilterAssembler(
class RelayGroupWarmupFilterAssembler(
client: INostrClient,
) : ComposeSubscriptionManager<RelayGroupPreviewQueryState>() {
) : ComposeSubscriptionManager<RelayGroupWarmupQueryState>() {
val group =
listOf(
RelayGroupPreviewSubAssembler(client, ::allKeys),
RelayGroupWarmupSubAssembler(client, ::allKeys),
)
override fun invalidateKeys() = invalidateFilters()
@@ -75,12 +75,12 @@ class RelayGroupPreviewFilterAssembler(
override fun destroy() = group.forEach { it.destroy() }
}
class RelayGroupPreviewSubAssembler(
class RelayGroupWarmupSubAssembler(
client: INostrClient,
allKeys: () -> Set<RelayGroupPreviewQueryState>,
) : PerUniqueIdEoseManager<RelayGroupPreviewQueryState, GroupId>(client, allKeys) {
allKeys: () -> Set<RelayGroupWarmupQueryState>,
) : PerUniqueIdEoseManager<RelayGroupWarmupQueryState, GroupId>(client, allKeys) {
override fun updateFilter(
key: RelayGroupPreviewQueryState,
key: RelayGroupWarmupQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> {
val groupId = key.channel.groupId
@@ -90,13 +90,13 @@ class RelayGroupPreviewSubAssembler(
relay = groupId.relayUrl,
filter =
Filter(
kinds = RELAY_GROUP_PREVIEW_CONTENT_KINDS,
kinds = RELAY_GROUP_WARMUP_CONTENT_KINDS,
tags = mapOf(GroupIdTag.TAG_NAME to listOf(groupId.id)),
limit = RELAY_GROUP_PREVIEW_LIMIT,
limit = RELAY_GROUP_WARMUP_LIMIT,
since = since?.get(groupId.relayUrl)?.time,
),
)
}
override fun id(key: RelayGroupPreviewQueryState) = key.channel.groupId
override fun id(key: RelayGroupWarmupQueryState) = key.channel.groupId
}
@@ -32,15 +32,15 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
* subscription already streams that for the whole relay, so each visible row asks for content only.
*/
@Composable
fun RelayGroupPreviewSubscription(
fun RelayGroupWarmupSubscription(
channel: RelayGroupChannel,
dataSource: RelayGroupPreviewFilterAssembler,
dataSource: RelayGroupWarmupFilterAssembler,
accountViewModel: AccountViewModel,
contentOnly: Boolean = false,
) {
val state =
remember(channel.groupId, contentOnly) {
RelayGroupPreviewQueryState(channel, contentOnly)
RelayGroupWarmupQueryState(channel, contentOnly)
}
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
@@ -34,7 +34,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.SupportedRolesEvent
/** One screen's request for the full channel directory of a single relay. */
class RelayGroupDirectoryQueryState(
class RelayGroupsOnRelayQueryState(
val relay: NormalizedRelayUrl,
val account: Account,
)
@@ -54,12 +54,12 @@ private val RELAY_GROUP_DIRECTORY_KINDS =
* the whole directory. Consumed into per-group [com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel]s
* keyed by (relay + group id).
*/
class RelayGroupDirectoryFilterAssembler(
class RelayGroupsOnRelayFilterAssembler(
client: INostrClient,
) : ComposeSubscriptionManager<RelayGroupDirectoryQueryState>() {
) : ComposeSubscriptionManager<RelayGroupsOnRelayQueryState>() {
val group =
listOf(
RelayGroupDirectorySubAssembler(client, ::allKeys),
RelayGroupsOnRelaySubAssembler(client, ::allKeys),
)
override fun invalidateKeys() = invalidateFilters()
@@ -69,12 +69,12 @@ class RelayGroupDirectoryFilterAssembler(
override fun destroy() = group.forEach { it.destroy() }
}
class RelayGroupDirectorySubAssembler(
class RelayGroupsOnRelaySubAssembler(
client: INostrClient,
allKeys: () -> Set<RelayGroupDirectoryQueryState>,
) : PerUniqueIdEoseManager<RelayGroupDirectoryQueryState, NormalizedRelayUrl>(client, allKeys) {
allKeys: () -> Set<RelayGroupsOnRelayQueryState>,
) : PerUniqueIdEoseManager<RelayGroupsOnRelayQueryState, NormalizedRelayUrl>(client, allKeys) {
override fun updateFilter(
key: RelayGroupDirectoryQueryState,
key: RelayGroupsOnRelayQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> =
listOf(
@@ -89,5 +89,5 @@ class RelayGroupDirectorySubAssembler(
),
)
override fun id(key: RelayGroupDirectoryQueryState) = key.relay
override fun id(key: RelayGroupsOnRelayQueryState) = key.relay
}
@@ -27,14 +27,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@Composable
fun RelayGroupDirectorySubscription(
fun RelayGroupsOnRelaySubscription(
relay: NormalizedRelayUrl,
dataSource: RelayGroupDirectoryFilterAssembler,
dataSource: RelayGroupsOnRelayFilterAssembler,
accountViewModel: AccountViewModel,
) {
val state =
remember(accountViewModel.account, relay) {
RelayGroupDirectoryQueryState(relay, accountViewModel.account)
RelayGroupsOnRelayQueryState(relay, accountViewModel.account)
}
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
@@ -43,7 +43,7 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupServerList
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupViewModeToggle
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupRosterSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupMyJoinedGroupsSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelFabColumn
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesPager
@@ -109,7 +109,7 @@ fun MessagesSinglePane(
// Keep joined groups' rosters live while the messages list is on top, so
// membership/pending state is accurate inline without opening each chat.
RelayGroupRosterSubscription(accountViewModel.dataSources().relayGroupRoster, accountViewModel)
RelayGroupMyJoinedGroupsSubscription(accountViewModel.dataSources().relayGroupMyJoinedGroups, accountViewModel)
Column {
RelayGroupViewModeToggle(accountViewModel)
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomVie
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.PublicChatChannelView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupServerList
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupViewModeToggle
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupRosterSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupMyJoinedGroupsSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelFabColumn
import com.vitorpamplona.amethyst.ui.theme.Size20dp
@@ -103,7 +103,7 @@ fun MessagesTwoPane(
val viewMode by accountViewModel.account.settings.relayGroupViewMode
.collectAsStateWithLifecycle()
RelayGroupRosterSubscription(accountViewModel.dataSources().relayGroupRoster, accountViewModel)
RelayGroupMyJoinedGroupsSubscription(accountViewModel.dataSources().relayGroupMyJoinedGroups, accountViewModel)
Column(Modifier.fillMaxSize()) {
RelayGroupViewModeToggle(accountViewModel)