diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 68df7e7ed0..19699bc448 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -3191,9 +3191,14 @@ class Account( val filters = entries.flatMap { entry -> val state = concordSessions.sessionFor(entry.id)?.state?.value ?: return@flatMap emptyList() - ConcordSubscriptionPlanner.channelPreviewFilters(entry, state, lastReadFor = { channelIdHex -> - loadLastRead(concordChannelLastReadRoute(entry.id, channelIdHex)) - }) + ConcordSubscriptionPlanner.channelPreviewFilters( + entry, + state, + lastReadFor = { channelIdHex -> + loadLastRead(concordChannelLastReadRoute(entry.id, channelIdHex)) + }, + accountPubKey = userProfile().pubkeyHex, + ) } if (filters.isEmpty()) return val byRelay = filters.groupBy { it.relay }.mapValues { (_, group) -> group.map { it.filter } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/AccountScopedQuery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/AccountScopedQuery.kt new file mode 100644 index 0000000000..3d6bc3fc0c --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/AccountScopedQuery.kt @@ -0,0 +1,39 @@ +/* + * 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.service.relayClient + +import com.vitorpamplona.amethyst.model.Account + +/** + * A subscription query state that belongs to one logged-in account. + * + * Around 66 query-state classes already carry an `account`, but nothing tied them together, so a + * subscription manager could not ask "whose subscription is this?" without knowing the concrete + * type. That is why the Active Relay Subscriptions screen filed the home feed under "not attributed" + * despite it being built from one specific person's follow list: the base manager checked for + * `AccountQueryState` and the home feed uses `HomeQueryState`. + * + * Implement this on any query state whose subscriptions belong to a single account, and the base + * managers attribute them automatically. + */ +interface AccountScopedQuery { + val account: Account +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUniqueIdEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUniqueIdEoseManager.kt index 2817e2a39d..0427380423 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUniqueIdEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUniqueIdEoseManager.kt @@ -21,6 +21,8 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager +import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.attributedTo +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relays.EOSEByKey import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.core.Event @@ -114,7 +116,13 @@ abstract class PerUniqueIdEoseManager( uniqueSubscribedAccounts.forEach { val mainKey = id(it) - val newFilters = updateFilter(it, since(it))?.ifEmpty { null } + val newFilters = + updateFilter(it, since(it)) + ?.ifEmpty { null } + // Attribute to the account that owns this subscription, once, here — rather than + // threading a pubkey through every filter builder underneath. Builders that already + // know their account keep what they set. + ?.let { f -> accountPubKeyOf(it)?.let { pk -> f.attributedTo(pk) } ?: f } findOrCreateSubFor(it).updateFilters(newFilters?.groupByRelay()) updated.add(mainKey) @@ -131,4 +139,13 @@ abstract class PerUniqueIdEoseManager( ): List? abstract fun id(key: T): U + + /** + * The account behind [key], when the key is account-scoped. Null for keys about other users. + * + * Keyed on [AccountScopedQuery] rather than a concrete query-state type: the home feed uses + * HomeQueryState, notifications use AccountQueryState, and checking one concrete class filed the + * other under "not attributed" despite both being built from a single account's data. + */ + private fun accountPubKeyOf(key: Any?): String? = (key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserAndFollowListEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserAndFollowListEoseManager.kt index 80bce3f6fa..cc55f02d7a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserAndFollowListEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserAndFollowListEoseManager.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager +import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.attributedTo import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relays.EOSEAccountKey import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.core.Event @@ -127,7 +129,13 @@ abstract class PerUserAndFollowListEoseManager( uniqueSubscribedAccounts.forEach { val user = user(it) val sub = findOrCreateSubFor(it) - val newFilters = updateFilter(it, since(it))?.ifEmpty { null } + val newFilters = + updateFilter(it, since(it)) + ?.ifEmpty { null } + // Attribute to the account that owns this subscription, once, here — rather than + // threading a pubkey through every filter builder underneath. Builders that already + // know their account keep what they set. + ?.let { f -> accountPubKeyOf(it)?.let { pk -> f.attributedTo(pk) } ?: f } sub.updateFilters(newFilters?.groupByRelay()) updated.add(user) } @@ -145,4 +153,13 @@ abstract class PerUserAndFollowListEoseManager( abstract fun user(key: T): User abstract fun list(key: T): U + + /** + * The account behind [key], when the key is account-scoped. Null for keys about other users. + * + * Keyed on [AccountScopedQuery] rather than a concrete query-state type: the home feed uses + * HomeQueryState, notifications use AccountQueryState, and checking one concrete class filed the + * other under "not attributed" despite both being built from a single account's data. + */ + private fun accountPubKeyOf(key: Any?): String? = (key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserEoseManager.kt index ad899cd2c1..b904bf4ab9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/PerUserEoseManager.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager +import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.attributedTo import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.core.Event @@ -113,7 +115,13 @@ abstract class PerUserEoseManager( uniqueSubscribedAccounts.forEach { val user = user(it) - val newFilters = updateFilter(it, since(it))?.ifEmpty { null } + val newFilters = + updateFilter(it, since(it)) + ?.ifEmpty { null } + // Attribute to the account that owns this subscription, once, here — rather than + // threading a pubkey through every filter builder underneath. Builders that already + // know their account keep what they set. + ?.let { f -> accountPubKeyOf(it)?.let { pk -> f.attributedTo(pk) } ?: f } findOrCreateSubFor(it).updateFilters(newFilters?.groupByRelay()) @@ -131,4 +139,13 @@ abstract class PerUserEoseManager( ): List? abstract fun user(key: T): User + + /** + * The account behind [key], when the key is account-scoped. Null for keys about other users. + * + * Keyed on [AccountScopedQuery] rather than a concrete query-state type: the home feed uses + * HomeQueryState, notifications use AccountQueryState, and checking one concrete class filed the + * other under "not attributed" despite both being built from a single account's data. + */ + private fun accountPubKeyOf(key: Any?): String? = (key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt index 57fb10f46a..d4df1e5deb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt @@ -21,6 +21,8 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager +import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.attributedTo +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter @@ -66,11 +68,26 @@ abstract class SingleSubNoEoseCacheEoseManager( override fun updateSubscriptions(keys: Set) { val uniqueSubscribedAccounts = keys.distinctBy { distinct(it) } - val newFilters = updateFilter(uniqueSubscribedAccounts)?.ifEmpty { null } + val newFilters = + updateFilter(uniqueSubscribedAccounts) + ?.ifEmpty { null } + // Attribute to the account that owns this subscription, once, here — rather than + // threading a pubkey through every filter builder underneath. Builders that already + // know their account keep what they set. + ?.let { f -> accountPubKeyOf(uniqueSubscribedAccounts.firstOrNull())?.let { pk -> f.attributedTo(pk) } ?: f } sub.updateFilters(newFilters?.groupByRelay()) } abstract fun updateFilter(keys: List): List? abstract fun distinct(key: T): Any + + /** + * The account behind [key], when the key is account-scoped. Null for keys about other users. + * + * Keyed on [AccountScopedQuery] rather than a concrete query-state type: the home feed uses + * HomeQueryState, notifications use AccountQueryState, and checking one concrete class filed the + * other under "not attributed" despite both being built from a single account's data. + */ + private fun accountPubKeyOf(key: Any?): String? = (key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt index f049140194..fe57efc735 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account 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.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.drafts.AccountDraftsEoseManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.marmot.MarmotGroupEventsEoseManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metadata.AccountMetadataEoseManager @@ -38,10 +39,10 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient // This allows multiple screen to be listening to logged-in accounts. @Stable class AccountQueryState( - val account: Account, + override val account: Account, val feedContentStates: AccountFeedContentStates, val otherAccounts: Set, -) +) : AccountScopedQuery /** * Always-on account loaders: metadata, gift wraps, drafts, inbox-relay diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/follows/AccountFollowsLoaderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/follows/AccountFollowsLoaderSubAssembler.kt index 6a3e4ca2d6..4318526e11 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/follows/AccountFollowsLoaderSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/follows/AccountFollowsLoaderSubAssembler.kt @@ -159,6 +159,15 @@ class AccountFollowsLoaderSubAssembler( val connectedRelays = client.connectedRelaysFlow().value + // Attributed only when one account is asking. The follow lists above are unioned across every + // logged-in account and the relays are then picked from that union, so with several accounts + // active no single one owns a given filter. Deduped by pubkey because `Account` uses identity + // equality. + val soleAccountPubKey = + accounts + .mapTo(mutableSetOf()) { it.userProfile().pubkeyHex } + .singleOrNull() + val perRelay = pickRelaysToLoadUsers(users, accounts, connectedRelays, failureTracker.cannotConnectRelays, hasTried) hasTried.removeEveryoneBut(users) @@ -167,7 +176,13 @@ class AccountFollowsLoaderSubAssembler( if (users.isNotEmpty()) { RelayBasedFilter( relay = relay, - filter = ExplainedFilter(purpose = SubPurpose.FOLLOW_LISTS, kinds = listOf(AdvertisedRelayListEvent.KIND), authors = users.sorted()), + filter = + ExplainedFilter( + purpose = SubPurpose.RELAY_LISTS, + kinds = listOf(AdvertisedRelayListEvent.KIND), + authors = users.sorted(), + accountPubKey = soleAccountPubKey, + ), ) } else { null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/marmot/MarmotGroupEventsEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/marmot/MarmotGroupEventsEoseManager.kt index fe34cca31b..f81c301100 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/marmot/MarmotGroupEventsEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/marmot/MarmotGroupEventsEoseManager.kt @@ -91,7 +91,7 @@ class MarmotGroupEventsEoseManager( "(metadataRelays=${groupRelays?.size ?: 0}, usingFallback=${groupRelays.isNullOrEmpty()}): ${relaysForGroup.map { it.url }}" } for (relay in relaysForGroup) { - result.add(RelayBasedFilter(relay = relay, filter = ExplainedFilter.of(filter, SubPurpose.ENCRYPTED_GROUPS, "MLS group messages"))) + result.add(RelayBasedFilter(relay = relay, filter = ExplainedFilter.of(filter, SubPurpose.ENCRYPTED_GROUPS, "MLS group messages", entityIds = listOf(groupId)))) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip47WalletConnect/NwcNotificationsEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip47WalletConnect/NwcNotificationsEoseManager.kt index 62594bacc4..384c89df06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip47WalletConnect/NwcNotificationsEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip47WalletConnect/NwcNotificationsEoseManager.kt @@ -90,7 +90,7 @@ class NwcNotificationsEoseManager( relay = wallet.uri.relayUri, filter = ExplainedFilter( - purpose = SubPurpose.WALLET, + purpose = SubPurpose.NWC, kinds = listOf(NwcNotificationEvent.KIND, NwcNotificationEvent.LEGACY_KIND), authors = listOf(wallet.uri.pubKeyHex), tags = mapOf("p" to listOf(signer.pubKey)), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblyGroup.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblyGroup.kt index 3f12d0bd05..2560fcc2cb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblyGroup.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblyGroup.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.model.Channel import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive.ChannelMetadataAndLiveActivityWatcherSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.ChannelLoaderSubAssembler import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -32,8 +33,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @Stable class ChannelFinderQueryState( val channel: Channel, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class ChannelFinderFilterAssemblyGroup( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt index 3e00e65548..f1f320ae50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManager import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.AddressableAuthorRelayLoaderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers.EventWatcherSubAssembler @@ -35,8 +36,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @Stable class EventFinderQueryState( val note: Note, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class EventFinderFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/EventWatcherSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/EventWatcherSubAssembler.kt index 91358cdfd0..e58b596d2d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/EventWatcherSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/EventWatcherSubAssembler.kt @@ -60,6 +60,16 @@ class EventWatcherSubAssembler( lastNotesOnFilter = keys.map { it.note } + // Attributed only when one account is watching. The notes here are whatever is rendered, not + // anything an account owns, so with several accounts active no single one is the honest owner — + // and splitting would re-request the same replies/zaps once per account. + // Deduped by pubkey, not by `Account`: that class uses identity equality, so two objects for + // the same logged-in user would look like two accounts and suppress attribution entirely. + val soleAccountPubKey = + keys + .mapTo(mutableSetOf()) { it.account.userProfile().pubkeyHex } + .singleOrNull() + return groupByRelayPresence(lastNotesOnFilter, latestEOSEs) .map { group -> if (group.isNotEmpty()) { @@ -67,8 +77,8 @@ class EventWatcherSubAssembler( val events = group.mapNotNull { if (it !is AddressableNote) it else null } listOfNotNull( - filterRepliesAndReactionsToNotes(events, findMinimumEOSEs(events, latestEOSEs)), - filterRepliesAndReactionsToAddresses(addressables, findMinimumEOSEs(addressables, latestEOSEs)), + filterRepliesAndReactionsToNotes(events, findMinimumEOSEs(events, latestEOSEs), soleAccountPubKey), + filterRepliesAndReactionsToAddresses(addressables, findMinimumEOSEs(addressables, latestEOSEs), soleAccountPubKey), ).flatten() } else { emptyList() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt index 7a172477d8..87f42c8d1c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt @@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent @@ -72,6 +73,7 @@ val TextNoteKindList = listOf(TextNoteEvent.KIND) fun filterRepliesAndReactionsToAddresses( keys: List, since: SincePerRelayMap?, + accountPubKey: HexKey? = null, ): List? { if (keys.isEmpty()) return null @@ -94,6 +96,7 @@ fun filterRepliesAndReactionsToAddresses( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = RepliesAndReactionsToAddressesKinds1, tags = mapOf("a" to sortedList), since = since, @@ -106,6 +109,7 @@ fun filterRepliesAndReactionsToAddresses( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = PostsAndChatMessagesToAddresses, tags = mapOf("a" to sortedList), since = since, @@ -118,6 +122,7 @@ fun filterRepliesAndReactionsToAddresses( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = DeletionKindList, tags = mapOf("a" to sortedList), since = since, @@ -130,6 +135,7 @@ fun filterRepliesAndReactionsToAddresses( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = TextNoteKindList, tags = mapOf("q" to sortedList), since = since, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt index 494ee84209..3763929013 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent @@ -78,6 +79,7 @@ val RepliesAndReactionsKinds2 = fun filterRepliesAndReactionsToNotes( events: List, since: SincePerRelayMap?, + accountPubKey: HexKey? = null, ): List? { if (events.isEmpty()) return null @@ -101,6 +103,7 @@ fun filterRepliesAndReactionsToNotes( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = RepliesAndReactionsKinds, tags = mapOf("e" to sortedList), since = since, @@ -113,6 +116,7 @@ fun filterRepliesAndReactionsToNotes( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = RepliesAndReactionsKinds2, tags = mapOf("e" to sortedList), since = since, @@ -124,6 +128,7 @@ fun filterRepliesAndReactionsToNotes( filter = ExplainedFilter( purpose = SubPurpose.ENGAGEMENT, + accountPubKey = accountPubKey, kinds = listOf(TextNoteEvent.KIND, CommentEvent.KIND), tags = mapOf("q" to sortedList), since = since, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssembler.kt index 8e72efe9ef..b0383beda5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssembler.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManager import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders.UserOutboxFinderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserCardsSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserReportsSubAssembler @@ -36,8 +37,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineT @Stable class UserFinderQueryState( val user: User, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class UserFinderFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserOutboxFinderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserOutboxFinderSubAssembler.kt index 1710572657..1e535e8e97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserOutboxFinderSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserOutboxFinderSubAssembler.kt @@ -103,6 +103,18 @@ class UserOutboxFinderSubAssembler( val accounts = keys.mapTo(mutableSetOf()) { it.account } val connectedRelays = client.connectedRelaysFlow().value + // Attributed only when one account is asking. Unlike the reports sweep, the relay choice here + // is made from the *union* of every logged-in account's tiers (`pickRelaysToLoadUsers` below), + // and the users being resolved are whoever is on screen rather than anyone's follow list — so + // with several accounts active there is no single honest owner for a given filter, and + // splitting the sweep per account would re-issue the same lookups once per account. + // Deduped by pubkey, not by `Account`: that class uses identity equality, so two objects for + // the same logged-in user would look like two accounts and suppress attribution entirely. + val soleAccountPubKey = + accounts + .mapTo(mutableSetOf()) { it.userProfile().pubkeyHex } + .singleOrNull() + val perRelayKeysBoth = pickRelaysToLoadUsers( noOutboxList, @@ -118,7 +130,13 @@ class UserOutboxFinderSubAssembler( if (sortedUsers.isNotEmpty()) { RelayBasedFilter( relay = it.key, - filter = ExplainedFilter(kinds = relayListKinds, authors = sortedUsers, purpose = SubPurpose.RELAY_LISTS), + filter = + ExplainedFilter( + kinds = relayListKinds, + authors = sortedUsers, + purpose = SubPurpose.RELAY_LISTS, + accountPubKey = soleAccountPubKey, + ), ) } else { null @@ -148,7 +166,14 @@ class UserOutboxFinderSubAssembler( fallbackRelays.map { relay -> RelayBasedFilter( relay = relay, - filter = ExplainedFilter(kinds = relayListKinds, authors = sortedAbandoned, purpose = SubPurpose.RELAY_LISTS, purposeDetail = "outbox discovery for users whose relay list we lost"), + filter = + ExplainedFilter( + kinds = relayListKinds, + authors = sortedAbandoned, + purpose = SubPurpose.RELAY_LISTS, + purposeDetail = "outbox discovery for users whose relay list we lost", + accountPubKey = soleAccountPubKey, + ), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterReportsToKey.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterReportsToKey.kt index 0e5d73a549..ba3022af91 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterReportsToKey.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterReportsToKey.kt @@ -34,6 +34,7 @@ fun filterReportsToKeysFromTrusted( trustedAccounts: List, relay: NormalizedRelayUrl, since: Long?, + accountPubKey: HexKey? = null, ): RelayBasedFilter? { if (targets.isEmpty() || trustedAccounts.isEmpty()) return null return RelayBasedFilter( @@ -43,6 +44,7 @@ fun filterReportsToKeysFromTrusted( purpose = SubPurpose.MODERATION, kinds = ReportKindList, authors = trustedAccounts, + accountPubKey = accountPubKey, tags = mapOf("p" to targets.sorted()), since = since, ), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterUserMetadataForKey.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterUserMetadataForKey.kt index 14ec5e3d96..488b4b1297 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterUserMetadataForKey.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/FilterUserMetadataForKey.kt @@ -63,6 +63,7 @@ fun filterUserMetadataForKey( indexRelays: Set, cannotConnectRelays: Set, since: EOSEAccountFast, + accountPubKey: HexKey? = null, ): List { val perRelayUsers = mapOfSet { @@ -114,6 +115,7 @@ fun filterUserMetadataForKey( filter = ExplainedFilter( purpose = SubPurpose.PROFILE_METADATA, + accountPubKey = accountPubKey, kinds = UserMetadataForKeyKinds, authors = firstTimers.sorted(), ), @@ -127,6 +129,7 @@ fun filterUserMetadataForKey( filter = ExplainedFilter( purpose = SubPurpose.PROFILE_METADATA, + accountPubKey = accountPubKey, kinds = UserMetadataForKeyKinds, authors = updates.sorted(), since = minimumTime, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserCardsSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserCardsSubAssembler.kt index 5ab831c9c7..a833114d6b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserCardsSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserCardsSubAssembler.kt @@ -70,6 +70,13 @@ class UserCardsSubAssembler( val accounts = keys.mapTo(mutableSetOf()) { it.account } + // Attributed only when one account is asking: the trusted-author sets below are pooled across + // accounts, so with several active none of them owns a given filter. + val soleAccountPubKey = + accounts + .mapTo(mutableSetOf()) { it.userProfile().pubkeyHex } + .singleOrNull() + val trustedAccounts: Map> = mapOfSet { accounts.forEach { account -> @@ -92,12 +99,14 @@ class UserCardsSubAssembler( val trustedAccounts = trustedUsersInThisRelay.sorted() listOfNotNull( filterContactCardsToTargetKeysFromTrustedAccountsInTheRelay( + accountPubKey = soleAccountPubKey, targets = groups.usersWithoutEose.toHexSet(), trustedAccounts = trustedAccounts, relay = relay, since = null, ), filterContactCardsToTargetKeysFromTrustedAccountsInTheRelay( + accountPubKey = soleAccountPubKey, targets = groups.usersWithEose.toHexSet(), trustedAccounts = trustedAccounts, relay = relay, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserReportsSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserReportsSubAssembler.kt index fd2e68532d..4371a4f822 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserReportsSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserReportsSubAssembler.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers import com.vitorpamplona.amethyst.commons.model.toHexSet import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubEoseManager +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState @@ -31,7 +32,6 @@ 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.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.utils.mapOfSet class UserReportsSubAssembler( client: INostrClient, @@ -65,14 +65,19 @@ class UserReportsSubAssembler( if (lastUsersOnFilter.isEmpty()) return null - val trustedAccountsPerRelay = - mapOfSet { - accounts.map { it.declaredFollowsPerOutboxRelay.value }.forEach { - add(it) - } - } + // One pass per account, never a union. "Whose follows wrote this report" is the whole point of + // the filter, so merging every logged-in account's follow list into one per-relay map both made + // the result unattributable and asked one account's follows of another account's outbox relays. + return accounts.flatMap { account -> filtersFor(account, lastUsersOnFilter) }.ifEmpty { null } + } - return trustedAccountsPerRelay + private fun filtersFor( + account: Account, + lastUsersOnFilter: Set, + ): List { + val accountPubKey = account.userProfile().pubkeyHex + + return account.declaredFollowsPerOutboxRelay.value .flatMap { (relay, trustedUsersInThisRelay) -> // this relay + accounts are where we could find reports. // we might have already loaded them, so let's separate new targets that were checked before from the others @@ -84,12 +89,14 @@ class UserReportsSubAssembler( trustedAccounts = trustedAccounts, relay = relay, since = null, + accountPubKey = accountPubKey, ), filterReportsToKeysFromTrusted( targets = groups.usersWithEose.toHexSet(), trustedAccounts = trustedAccounts, relay = relay, since = findMinimumEOSEsForUsers(groups.usersWithEose, relay), + accountPubKey = accountPubKey, ), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserWatcherSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserWatcherSubAssembler.kt index 6d8f7efb23..d695cd8e7e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserWatcherSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/watchers/UserWatcherSubAssembler.kt @@ -96,6 +96,13 @@ class UserWatcherSubAssembler( } // assembles all index relays from all accounts + // Attributed only when one account is looking: the index relays below are pooled across every + // account and the users are whoever is on screen, so with several askers none of them owns it. + val soleAccountPubKey = + keys + .mapTo(mutableSetOf()) { it.account.userProfile().pubkeyHex } + .singleOrNull() + val indexRelays = mutableSetOf() keys.mapTo(mutableSetOf()) { it.account }.forEach { indexRelays.addAll( @@ -110,6 +117,7 @@ class UserWatcherSubAssembler( indexRelays, failureTracker.cannotConnectRelays, latestEOSEs, + soleAccountPubKey, ).ifEmpty { null } sub.updateFilters(newFilters?.groupByRelay()) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/SearchFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/SearchFilterAssembler.kt index ebbd64c4aa..abde8f87a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/SearchFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/SearchFilterAssembler.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManager import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.MutableQueryState import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies.SearchPostWatcherSubAssembler import com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies.SearchUserWatcherSubAssembler import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -35,8 +36,9 @@ import kotlinx.coroutines.flow.MutableStateFlow @Stable class SearchQueryState( val searchQuery: MutableStateFlow, - val account: Account, -) : MutableQueryState { + override val account: Account, +) : MutableQueryState, + AccountScopedQuery { override fun flow(): Flow = searchQuery } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/datasource/ProfileAppRecommendationsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/datasource/ProfileAppRecommendationsFilterAssembler.kt index bbce9cc824..d4e687213f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/datasource/ProfileAppRecommendationsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/datasource/ProfileAppRecommendationsFilterAssembler.kt @@ -23,11 +23,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.apps.recommendations.datas 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.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient class ProfileAppRecommendationsQueryState( - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class ProfileAppRecommendationsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssembler.kt index 5ee186f653..23b2195f26 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class ArticlesQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class ArticlesFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssembler.kt index 74d95bc09e..40eb063052 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class BadgesQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class BadgesFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/datasource/ProfileBadgesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/datasource/ProfileBadgesFilterAssembler.kt index 02892b5a27..065964fd39 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/datasource/ProfileBadgesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/datasource/ProfileBadgesFilterAssembler.kt @@ -23,11 +23,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile.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.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient class ProfileBadgesQueryState( - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class ProfileBadgesFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/datasource/CalendarsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/datasource/CalendarsFilterAssembler.kt index 79c1cddd0e..51d04db05e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/datasource/CalendarsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/datasource/CalendarsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class CalendarsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class CalendarsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/datasource/ChatroomFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/datasource/ChatroomFilterAssembler.kt index 9366082074..4bd284de17 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/datasource/ChatroomFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/datasource/ChatroomFilterAssembler.kt @@ -22,14 +22,15 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey // This allows multiple screen to be listening to tags, even the same tag class ChatroomQueryState( val room: ChatroomKey, - val account: Account, -) { + override val account: Account, +) : AccountScopedQuery { val listId = room.hashCode().toString() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelFilterAssembler.kt index cd9e4280c4..9d8f1b00b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelFilterAssembler.kt @@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.commons.actions.ConcordSubscriptionPlanner import com.vitorpamplona.amethyst.commons.model.chats.ChatFeedType import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.launchChatFeedToggleObserver import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap @@ -34,8 +35,8 @@ import kotlinx.coroutines.Job /** One screen's request to keep the user's joined Concord Channels live. */ class ConcordChannelQueryState( - val account: Account, -) + override val account: Account, +) : AccountScopedQuery /** * Keeps every joined Concord community's planes live while a Concord-bearing @@ -81,7 +82,11 @@ class ConcordChannelSubAssembler( // per-filter result cap (the "Soapbox shows 1 of 12 channels" bug). See // [ConcordSubscriptionPlanner.controlIsolatedFilters]. val all = - ConcordSubscriptionPlanner.controlIsolatedFilters(entries, since = since) { entry -> + ConcordSubscriptionPlanner.controlIsolatedFilters( + entries, + since = since, + accountPubKey = account.userProfile().pubkeyHex, + ) { entry -> account.concordSessions .sessionFor(entry.id) ?.state diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelHistoryFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelHistoryFilterAssembler.kt index 2ada002093..2720cb1d77 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelHistoryFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/datasource/ConcordChannelHistoryFilterAssembler.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFil import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId @@ -44,10 +45,10 @@ import kotlinx.coroutines.flow.StateFlow /** One open Concord Channel whose older history the screen wants paged in. */ class ConcordChannelHistoryQueryState( - val account: Account, + override val account: Account, val communityId: String, val channelId: String, -) +) : AccountScopedQuery /** * Mounts the on-demand **history** pager for whichever Concord Channel screen is open. The live @@ -133,6 +134,8 @@ class ConcordChannelHistorySubAssembler( filter = ExplainedFilter( purpose = SubPurpose.COMMUNITY_CHATS, + purposeDetail = "concord channel history", + entityIds = listOf(key.communityId), kinds = listOf(ConcordStreamEnvelope.KIND_WRAP), // All epoch planes at once: the relay serves them interleaved by created_at, so // one backward cursor walks across the Refounding boundaries; "exhausted" then diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/BuzzDmJoinedChatTailFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/BuzzDmJoinedChatTailFilterAssembler.kt index 0ebe1be439..d3ca4ea0ee 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/BuzzDmJoinedChatTailFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/BuzzDmJoinedChatTailFilterAssembler.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManager import com.vitorpamplona.amethyst.commons.relayClient.paging.WindowLoadTracker import com.vitorpamplona.amethyst.commons.relayClient.paging.trackingListener import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip01Notifications.filterGroupNotificationsToPubkey import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap @@ -40,9 +41,9 @@ import kotlinx.coroutines.flow.StateFlow /** One Buzz DM channel whose recent chat is kept live. */ class BuzzDmJoinedChatTailQueryState( - val account: Account, + override val account: Account, val groupId: GroupId, -) +) : AccountScopedQuery /** * Always-on **live tail** for the recent chat of every Buzz DM channel the viewer belongs to — the DM diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedChatTailFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedChatTailFilterAssembler.kt index 66cd9bdad0..9685069cb5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedChatTailFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedChatTailFilterAssembler.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManager import com.vitorpamplona.amethyst.commons.relayClient.paging.WindowLoadTracker import com.vitorpamplona.amethyst.commons.relayClient.paging.trackingListener import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip01Notifications.filterGroupNotificationsToPubkey import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap @@ -41,9 +42,9 @@ import kotlinx.coroutines.flow.StateFlow /** One joined group whose recent chat is kept live for the Messages-list preview. */ class RelayGroupJoinedChatTailQueryState( - val account: Account, + override val account: Account, val groupId: GroupId, -) +) : AccountScopedQuery /** * Always-on **live tail** for the recent chat of every NIP-29 group the user has joined — the group diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedStateFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedStateFilterAssembler.kt index 3f55679e1b..7a2dc48cf1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedStateFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupJoinedStateFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relay import com.vitorpamplona.amethyst.commons.model.chats.ChatFeedType import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.launchChatFeedToggleObserver import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap @@ -33,8 +34,8 @@ import kotlinx.coroutines.Job /** One request to keep the relay-signed state of the user's joined groups live. */ class RelayGroupJoinedStateQueryState( - val account: Account, -) + override val account: Account, +) : AccountScopedQuery /** * Keeps the relay-signed **state** of every group the user has joined live and current — name, picture, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatHistoryFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatHistoryFilterAssembler.kt index 3cad4be707..7b6f7bbcac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatHistoryFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatHistoryFilterAssembler.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.paging.BackwardRelayPager import com.vitorpamplona.amethyst.commons.relayClient.paging.PagingStatus import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.core.Event @@ -40,9 +41,9 @@ import kotlinx.coroutines.flow.StateFlow /** One open NIP-29 group whose older history the chat screen wants paged in. */ class RelayGroupOpenChatHistoryQueryState( - val account: Account, + override val account: Account, val groupId: GroupId, -) +) : AccountScopedQuery /** * Mounts the on-demand **history** pager for whichever NIP-29 group chat screen is open. The live diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatTailFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatTailFilterAssembler.kt index af33acea2e..e151b15ebf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatTailFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenChatTailFilterAssembler.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManager import com.vitorpamplona.amethyst.commons.relayClient.paging.WindowLoadTracker import com.vitorpamplona.amethyst.commons.relayClient.paging.trackingListener import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -37,9 +38,9 @@ import kotlinx.coroutines.flow.StateFlow /** One open NIP-29 group whose recent chat the screen wants live. */ class RelayGroupOpenChatTailQueryState( - val account: Account, + override val account: Account, val groupId: GroupId, -) +) : AccountScopedQuery /** * Per-open-group **live tail**: the recent chat window of the *currently open* group, `#h`-scoped on diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenThreadsHistoryFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenThreadsHistoryFilterAssembler.kt index 6eb149347f..ddbef10a9a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenThreadsHistoryFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupOpenThreadsHistoryFilterAssembler.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.paging.BackwardRelayPager import com.vitorpamplona.amethyst.commons.relayClient.paging.PagingStatus import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.core.Event @@ -40,9 +41,9 @@ import kotlinx.coroutines.flow.StateFlow /** One open NIP-29 group whose older forum threads the Threads tab wants paged in. */ class RelayGroupOpenThreadsHistoryQueryState( - val account: Account, + override val account: Account, val groupId: GroupId, -) +) : AccountScopedQuery /** * Mounts the on-demand **history** pager for whichever NIP-29 group's Threads tab is open. The Threads diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsDiscoveryFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsDiscoveryFilterAssembler.kt index 5dde33785b..cd99f96616 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsDiscoveryFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsDiscoveryFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relay 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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class RelayGroupsDiscoveryQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class RelayGroupsDiscoveryFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsOnRelayFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsOnRelayFilterAssembler.kt index 3fdbb97692..65d8cf66fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsOnRelayFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/datasource/RelayGroupsOnRelayFilterAssembler.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relay import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -31,8 +32,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl /** One screen's request for the full channel directory of a single relay. */ class RelayGroupsOnRelayQueryState( val relay: NormalizedRelayUrl, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery /** * Subscribes to the relay-signed directory (kinds 39000-39003) of a single relay, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/ChatroomListFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/ChatroomListFilterAssembler.kt index 286b506641..649ab893af 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/ChatroomListFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/ChatroomListFilterAssembler.kt @@ -23,13 +23,14 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.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.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient // This allows multiple screen to be listening to tags, even the same tag @Stable class ChatroomListState( - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class ChatroomListFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssembler.kt index 1ac72e047c..b5f19f4752 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.datasourc 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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class CommunitiesListQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class CommunitiesListFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt index dc49d49b5e..01ccad2648 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt @@ -22,16 +22,17 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope // This allows multiple screen to be listening to tags, even the same tag class DiscoveryQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery class DiscoveryFilterAssembler( client: INostrClient, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/browse/datasource/BrowseEmojiSetsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/browse/datasource/BrowseEmojiSetsFilterAssembler.kt index f33b19c848..e1fe9f9c7a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/browse/datasource/BrowseEmojiSetsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/browse/datasource/BrowseEmojiSetsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.browse.datasour 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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class BrowseEmojiSetsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class BrowseEmojiSetsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssembler.kt index 02c80a798a..83114ea4a9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.datasourc 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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class FollowPacksQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class FollowPacksFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepositories/datasource/GitRepositoriesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepositories/datasource/GitRepositoriesFilterAssembler.kt index 2514e313b2..56626774d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepositories/datasource/GitRepositoriesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepositories/datasource/GitRepositoriesFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepositories.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class GitRepositoriesQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class GitRepositoriesFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssembler.kt index d394b588d1..1750ec7fd2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssembler.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -29,8 +30,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl class HashtagQueryState( val hashtag: String, val relays: Set, - val account: Account, -) { + override val account: Account, +) : AccountScopedQuery { val lowercaseHashtag = hashtag.lowercase() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/highlights/datasource/HighlightsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/highlights/datasource/HighlightsFilterAssembler.kt index 61e50a3b27..7006c1e79d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/highlights/datasource/HighlightsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/highlights/datasource/HighlightsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.highlights.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class HighlightsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class HighlightsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/HomeFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/HomeFilterAssembler.kt index e76959df4b..0f3a3bd769 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/HomeFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/HomeFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip65Follows.HomeOutboxEventsEoseManager import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -32,10 +33,10 @@ import kotlinx.coroutines.CoroutineScope @Stable class HomeQueryState( - val account: Account, + override val account: Account, val feedState: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class HomeFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssembler.kt index 5252bf8c3b..306666585e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class LiveStreamsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class LiveStreamsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/datasource/LongsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/datasource/LongsFilterAssembler.kt index 173b0a2ed4..b57c0ef105 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/datasource/LongsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/datasource/LongsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class LongsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class LongsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicPlaylistsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicPlaylistsFilterAssembler.kt index 90a17ec475..3918a83811 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicPlaylistsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicPlaylistsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.music.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class MusicPlaylistsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class MusicPlaylistsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicTracksFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicTracksFilterAssembler.kt index 7751934347..693ee0286f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicTracksFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/datasource/MusicTracksFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.music.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class MusicTracksQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class MusicTracksFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssembler.kt index 331a494a85..09a94d7c30 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.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.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -32,9 +33,9 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient * been granted permissions in the user's ledger. */ class ConnectedAppsQueryState( - val account: Account, + override val account: Account, val authors: Set, -) +) : AccountScopedQuery /** * Live subscription for NIP-5D napplet manifests (kinds 15129/35129) while diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssembler.kt index 3ac9f54b57..f31a00821e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.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.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope @@ -33,9 +34,9 @@ import kotlinx.coroutines.CoroutineScope * selection) and a scope. */ class NappletsQueryState( - val account: Account, + override val account: Account, val scope: CoroutineScope, -) +) : AccountScopedQuery /** * Subscribes to NIP-5D napplet manifests (kinds 15129/35129) while a napplet screen is open, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomFilterAssembler.kt index f3dd500d6c..c5d33cb800 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomFilterAssembler.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwa import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -53,8 +54,8 @@ import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent @Stable class NestRoomQueryState( val note: AddressableNote, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery /** * Single per-room sub-assembler. Issues two [Filter]s per outbox diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomLivenessProbeAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomLivenessProbeAssembler.kt index 3ae8d18843..59842afb75 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomLivenessProbeAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestRoomLivenessProbeAssembler.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwa import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -51,8 +52,8 @@ import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresence @Stable class NestRoomLivenessQueryState( val note: AddressableNote, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery /** * Single per-room sub-assembler for the feed thumbnail liveness diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestsFilterAssembler.kt index a05c0d0703..01e3884616 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/datasource/NestsFilterAssembler.kt @@ -23,15 +23,16 @@ 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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class NestsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class NestsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nsites/datasource/NsitesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nsites/datasource/NsitesFilterAssembler.kt index b57fcb7523..a3661e8f9f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nsites/datasource/NsitesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nsites/datasource/NsitesFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.nsites.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.AccountScopedQuery import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope @@ -33,9 +34,9 @@ import kotlinx.coroutines.CoroutineScope * scope. */ class NsitesQueryState( - val account: Account, + override val account: Account, val scope: CoroutineScope, -) +) : AccountScopedQuery /** * Subscribes to NIP-5A static-site manifests (kinds 15128/35128) while an nSite screen is open, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/datasource/PicturesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/datasource/PicturesFilterAssembler.kt index 11afee9e15..0e6cafdcda 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/datasource/PicturesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/datasource/PicturesFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class PicturesQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class PicturesFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastEpisodesFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastEpisodesFilterAssembler.kt index efb9bdb167..f407de6b67 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastEpisodesFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastEpisodesFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class PodcastEpisodesQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class PodcastEpisodesFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastsFilterAssembler.kt index 85d6e8c995..b1df728a8d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/datasource/PodcastsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class PodcastsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class PodcastsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/polls/datasource/PollsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/polls/datasource/PollsFilterAssembler.kt index a518072e43..51ef565d14 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/polls/datasource/PollsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/polls/datasource/PollsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class PollsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class PollsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssembler.kt index 8d4354d8e1..95998e4ef0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.products.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class ProductsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class ProductsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssembler.kt index 0a12bd9320..fa36618731 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.publicChats.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class PublicChatsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class PublicChatsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/datasource/ShortsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/datasource/ShortsFilterAssembler.kt index 28ddef8aff..81107e925c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/datasource/ShortsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/datasource/ShortsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class ShortsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class ShortsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsFilterAssembler.kt index ee51bbe8d2..235c34d412 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class SoftwareAppsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class SoftwareAppsFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt index 9cb7560137..4a25956659 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources 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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies.ThreadEventLoaderSubAssembler import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies.ThreadFilterSubAssembler import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -32,8 +33,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @Stable class ThreadQueryState( val eventId: HexKey, - val account: Account, -) + override val account: Account, +) : AccountScopedQuery @Stable class ThreadFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/VideoFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/VideoFilterAssembler.kt index ca5fa42e7d..bf4ab512fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/VideoFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/VideoFilterAssembler.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassemblies.VideoOutboxEventsFilterSubAssembler import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -29,10 +30,10 @@ import kotlinx.coroutines.CoroutineScope // This allows multiple screen to be listening to tags, even the same tag class VideoQueryState( - val account: Account, + override val account: Account, val feedState: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery class VideoFilterAssembler( client: INostrClient, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/datasource/WorkoutsFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/datasource/WorkoutsFilterAssembler.kt index baab033a30..9ddb312de1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/datasource/WorkoutsFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/datasource/WorkoutsFilterAssembler.kt @@ -23,15 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.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.AccountScopedQuery import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import kotlinx.coroutines.CoroutineScope class WorkoutsQueryState( - val account: Account, + override val account: Account, val feedStates: AccountFeedContentStates, val scope: CoroutineScope, -) +) : AccountScopedQuery @Stable class WorkoutsFilterAssembler( diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/ConcordSubscriptionPlanner.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/ConcordSubscriptionPlanner.kt index 6e8dff7700..74ee3ca92b 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/ConcordSubscriptionPlanner.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/ConcordSubscriptionPlanner.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEntr import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityState import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId import com.vitorpamplona.quartz.concord.envelope.ConcordStreamEnvelope +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter @@ -165,6 +166,7 @@ object ConcordSubscriptionPlanner { lastReadFor: (channelIdHex: String) -> Long, catchUpLimit: Int = 50, previewLimit: Int = 10, + accountPubKey: HexKey? = null, ): List { val authorsByChannel = LinkedHashMap>() val relaysByChannel = HashMap>() @@ -180,6 +182,8 @@ object ConcordSubscriptionPlanner { ExplainedFilter( purpose = SubPurpose.COMMUNITY_CHATS, purposeDetail = "concord community planes", + entityIds = listOf(entry.id), + accountPubKey = accountPubKey, kinds = listOf(ConcordStreamEnvelope.KIND_WRAP), authors = authors.toList(), // -1 so the last-read message (created_at == lastRead) is itself returned: @@ -191,6 +195,8 @@ object ConcordSubscriptionPlanner { ExplainedFilter( purpose = SubPurpose.COMMUNITY_CHATS, purposeDetail = "concord community planes", + entityIds = listOf(entry.id), + accountPubKey = accountPubKey, kinds = listOf(ConcordStreamEnvelope.KIND_WRAP), authors = authors.toList(), limit = previewLimit, @@ -232,6 +238,7 @@ object ConcordSubscriptionPlanner { fun controlIsolatedFilters( entries: List, since: SincePerRelayMap?, + accountPubKey: HexKey? = null, stateOf: (ConcordCommunityListEntry) -> ConcordCommunityState?, ): List { val controlSubs = controlPlaneSubs(entries) @@ -243,7 +250,7 @@ object ConcordSubscriptionPlanner { otherSubs += channelPlaneSubs(entry, state) } - return relayBasedFilters(controlSubs, since).orEmpty() + relayBasedFilters(otherSubs, since).orEmpty() + return relayBasedFilters(controlSubs, since, accountPubKey).orEmpty() + relayBasedFilters(otherSubs, since, accountPubKey).orEmpty() } /** @@ -258,10 +265,18 @@ object ConcordSubscriptionPlanner { fun relayBasedFilters( subs: List, since: SincePerRelayMap?, + accountPubKey: HexKey? = null, ): List? { val authorsByRelay = HashMap>() + // Which communities each relay is being asked about. Collapsing planes into one filter per + // relay is what makes the subscription screen unable to name them otherwise — the plane + // pubkeys are stream keys, not something a user can recognise. + val communitiesByRelay = HashMap>() for (sub in subs) { - for (relay in sub.relays) authorsByRelay.getOrPut(relay) { HashSet() }.add(sub.pubKeyHex) + for (relay in sub.relays) { + authorsByRelay.getOrPut(relay) { HashSet() }.add(sub.pubKeyHex) + sub.channelId?.let { communitiesByRelay.getOrPut(relay) { HashSet() }.add(it.communityId) } + } } if (authorsByRelay.isEmpty()) return null @@ -271,6 +286,9 @@ object ConcordSubscriptionPlanner { filter = ExplainedFilter( purpose = SubPurpose.COMMUNITY_CHATS, + purposeDetail = "concord live planes", + entityIds = communitiesByRelay[relay]?.sorted(), + accountPubKey = accountPubKey, // Stored plane wraps (1059) plus ephemeral ones (21059) — the latter carry the // live-only typing heartbeats a relay broadcasts but never stores. kinds = listOf(ConcordStreamEnvelope.KIND_WRAP, ConcordStreamEnvelope.KIND_WRAP_EPHEMERAL), diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuMintDirectoryFilterAssembler.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuMintDirectoryFilterAssembler.kt index d40853b271..836f4ad0b9 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuMintDirectoryFilterAssembler.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuMintDirectoryFilterAssembler.kt @@ -85,7 +85,7 @@ private class CashuMintDirectorySubAssembler( val mintAnnouncements = ExplainedFilter( - purpose = SubPurpose.WALLET, + purpose = SubPurpose.MINT_DIRECTORY, purposeDetail = "mint directory", kinds = listOf(CashuMintEvent.KIND), ) @@ -95,7 +95,7 @@ private class CashuMintDirectorySubAssembler( // recommendations here. val cashuRecommendations = ExplainedFilter( - purpose = SubPurpose.WALLET, + purpose = SubPurpose.MINT_DIRECTORY, purposeDetail = "mint directory", kinds = listOf(MintRecommendationEvent.KIND), tags = mapOf("k" to listOf(CashuMintEvent.KIND.toString())), diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuWalletFilterAssembler.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuWalletFilterAssembler.kt index f86a8b9fe0..1831b0353e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuWalletFilterAssembler.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/CashuWalletFilterAssembler.kt @@ -95,16 +95,31 @@ private class CashuWalletSubAssembler( ) : SingleSubEoseManager(client, allKeys, invalidateAfterEose = true) { override fun distinct(key: CashuWalletQueryState): Any = key.pubkey + /** + * One set of filters **per account**, never a merged one. + * + * [SingleSubEoseManager] hands over every distinct key, so with two wallets logged in this used + * to take `keys.first().pubkey` while pooling *both* accounts' relays — the second account's + * wallet was never subscribed, and its inbox relays were queried for the first account's + * nutzaps. Keeping each account's pubkey with its own relay sets is also what lets the + * subscription screen attribute these filters instead of piling them under "not attributed". + */ override fun updateFilter( keys: List, since: SincePerRelayMap?, ): List? { if (keys.isEmpty()) return null + return keys.flatMap { filtersFor(it, since) }.ifEmpty { null } + } - val pubkey = keys.first().pubkey - val ownEventRelays = keys.flatMap { it.ownEventRelays }.toSet() - val inboxRelays = keys.flatMap { it.inboxRelays }.toSet() - if (ownEventRelays.isEmpty() && inboxRelays.isEmpty()) return null + private fun filtersFor( + key: CashuWalletQueryState, + since: SincePerRelayMap?, + ): List { + val pubkey = key.pubkey + val ownEventRelays = key.ownEventRelays + val inboxRelays = key.inboxRelays + if (ownEventRelays.isEmpty() && inboxRelays.isEmpty()) return emptyList() val ownedFilter = ExplainedFilter( @@ -123,13 +138,15 @@ private class CashuWalletSubAssembler( MintRecommendationEvent.KIND, ), authors = listOf(pubkey), + accountPubKey = pubkey, ) val inboundNutzapsFilter = ExplainedFilter( - purpose = SubPurpose.WALLET, + purpose = SubPurpose.NUTZAP_INBOX, kinds = listOf(NutzapEvent.KIND), tags = mapOf("p" to listOf(pubkey)), + accountPubKey = pubkey, ) // Own NIP-60 events are read from the user's outbox; inbound nutzaps diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/ContactCardFilters.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/ContactCardFilters.kt index 4bdfb7623e..3c654bf55b 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/ContactCardFilters.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/ContactCardFilters.kt @@ -40,6 +40,7 @@ fun filterContactCardsToTargetKeysFromTrustedAccountsInTheRelay( trustedAccounts: List, relay: NormalizedRelayUrl, since: Long?, + accountPubKey: HexKey? = null, ): RelayBasedFilter? { if (targets.isEmpty() || trustedAccounts.isEmpty()) return null return RelayBasedFilter( @@ -47,6 +48,7 @@ fun filterContactCardsToTargetKeysFromTrustedAccountsInTheRelay( filter = ExplainedFilter( purpose = SubPurpose.PROFILE_METADATA, + accountPubKey = accountPubKey, kinds = ContactCardKindList, authors = trustedAccounts, // kind:30382 addresses the target user in the d-tag @@ -72,6 +74,8 @@ fun filterContactCardsByAuthorInTheRelay( filter = ExplainedFilter( purpose = SubPurpose.PROFILE_METADATA, + // This variant fetches an account's OWN contact card, so the author is the owner. + accountPubKey = author, kinds = ContactCardKindList, authors = listOf(author), limit = limit, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/MetadataFilterAssembler.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/MetadataFilterAssembler.kt index c81db0984c..5ba35efcdc 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/MetadataFilterAssembler.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/assemblers/MetadataFilterAssembler.kt @@ -37,6 +37,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl data class MetadataQueryState( val pubkeys: Set, val indexRelays: Set, + /** Which account is looking. Null when the caller has no account context (kept out of attribution). */ + val accountPubKey: HexKey? = null, ) /** @@ -69,6 +71,11 @@ class MetadataFilterAssembler( if (allPubkeys.isEmpty() || allRelays.isEmpty()) return null + // Attributed only when one account is looking. These pubkeys are whoever is rendered rather + // than anything an account owns, and several query states are batched into one filter, so with + // more than one asker there is no single honest owner. + val soleAccountPubKey = keys.mapNotNullTo(mutableSetOf()) { it.accountPubKey }.singleOrNull() + val pubkeyList = allPubkeys.toList() // Create filter for metadata (Kind 0) @@ -78,6 +85,7 @@ class MetadataFilterAssembler( authors = pubkeyList, limit = pubkeyList.size, purpose = SubPurpose.PROFILE_METADATA, + accountPubKey = soleAccountPubKey, ) // Apply since times per relay diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/subscriptions/ExplainedFilter.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/subscriptions/ExplainedFilter.kt index 436fac0fd2..65d9821165 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/subscriptions/ExplainedFilter.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/subscriptions/ExplainedFilter.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.relayClient.subscriptions import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Kind +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter /** @@ -66,12 +67,18 @@ class ExplainedFilter( /** Free-form extra context for [SubPurpose.OTHER] or for narrowing a bucket while debugging. */ val purposeDetail: String? = null, /** - * The thing this filter serves — a community, group, channel or mint id. Deliberately an **id, - * not a name**: names change, are not always loaded when the filter is built, and would pin a - * stale copy into a long-lived subscription. The UI resolves it against `LocalCache` at render - * time, so it always shows the current name and shows nothing gracefully when unknown. + * The things this filter serves — community, group, channel or mint ids. + * + * A **list**, because filters are routinely batched: relay-group state is fetched with one `#d` + * filter per host relay carrying every joined group on it, so a single filter can legitimately + * serve a dozen chats. Modelling one id would have forced either a wrong answer ("All") or a + * filter-per-chat, which is far more REQs than the relays want. + * + * Ids, not names: names change, are often not loaded when the filter is built, and would pin a + * stale copy into a long-lived subscription. The UI resolves them against `LocalCache` at render + * time, so it shows the current name and degrades to a short id when unknown. */ - val entityId: HexKey? = null, + val entityIds: List? = null, /** * Which logged-in account asked for this. Several accounts are commonly active at once and they * do not share relay sets, so "why is this relay connected" is only answerable per account — @@ -93,7 +100,7 @@ class ExplainedFilter( until: Long?, limit: Int?, search: String?, - ) = ExplainedFilter(ids, authors, kinds, tags, tagsAll, since, until, limit, search, purpose, purposeDetail, entityId, accountPubKey) + ) = ExplainedFilter(ids, authors, kinds, tags, tagsAll, since, until, limit, search, purpose, purposeDetail, entityIds, accountPubKey) companion object { /** Tags [filter] with a [purpose], preserving every protocol field. */ @@ -101,7 +108,7 @@ class ExplainedFilter( filter: Filter, purpose: SubPurpose, detail: String? = null, - entityId: HexKey? = null, + entityIds: List? = null, accountPubKey: HexKey? = null, ) = ExplainedFilter( filter.ids, @@ -115,7 +122,7 @@ class ExplainedFilter( filter.search, purpose, detail, - entityId, + entityIds, accountPubKey, ) } @@ -135,8 +142,14 @@ fun Collection.purposes(): Set = mapNotNullTo(mutableSetOf() * whose. Entries with no entity collapse to a single row for that purpose. */ fun Collection.purposeEntities(): Set = - mapNotNullTo(mutableSetOf()) { filter -> - (filter as? ExplainedFilter)?.let { PurposeEntity(it.purpose, it.entityId, it.accountPubKey, it.purposeDetail) } + flatMapTo(mutableSetOf()) { filter -> + val explained = filter as? ExplainedFilter ?: return@flatMapTo emptyList() + val ids = explained.entityIds + if (ids.isNullOrEmpty()) { + listOf(PurposeEntity(explained.purpose, null, explained.accountPubKey, explained.purposeDetail)) + } else { + ids.map { PurposeEntity(explained.purpose, it, explained.accountPubKey, explained.purposeDetail) } + } } /** A single "this relay is doing X, for Y, on behalf of account Z" fact. Ids only; names resolve in the UI. */ @@ -146,3 +159,23 @@ data class PurposeEntity( val accountPubKey: HexKey? = null, val detail: String? = null, ) + +/** + * Stamps [accountPubKey] onto every tagged filter that does not already name one. + * + * Applied once where a subscription manager knows its account, rather than threading a pubkey + * parameter through the ~200 filter builders below it. Filters that already name an account are left + * alone, so a builder with better knowledge always wins. + */ +fun List.attributedTo(accountPubKey: HexKey): List = + map { relayFilter -> + val filter = relayFilter.filter + if (filter is ExplainedFilter && filter.accountPubKey == null) { + RelayBasedFilter( + relay = relayFilter.relay, + filter = ExplainedFilter.of(filter, filter.purpose, filter.purposeDetail, filter.entityIds, accountPubKey), + ) + } else { + relayFilter + } + }