Adds Proxy relay list and adds support for fixed list of relays.

This commit is contained in:
Vitor Pamplona
2025-07-23 19:09:04 -04:00
parent 1084d2a063
commit a494257a27
86 changed files with 1184 additions and 293 deletions
@@ -34,7 +34,7 @@ import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountOutboxRelayState
import com.vitorpamplona.amethyst.model.nip01UserMetadata.NotificationInboxRelayState
import com.vitorpamplona.amethyst.model.nip01UserMetadata.UserMetadataState
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxRelays
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxOrProxyRelays
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowsPerOutboxRelay
import com.vitorpamplona.amethyst.model.nip03Timestamp.OtsState
@@ -64,6 +64,8 @@ import com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays.IndexerRelayLis
import com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays.IndexerRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.muteList.MuteListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.muteList.MuteListState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListDecryptionCache
@@ -246,6 +248,9 @@ class Account(
val trustedRelayListDecryptionCache = TrustedRelayListDecryptionCache(signer)
val trustedRelayList = TrustedRelayListState(signer, cache, trustedRelayListDecryptionCache, scope, settings)
val proxyRelayListDecryptionCache = ProxyRelayListDecryptionCache(signer)
val proxyRelayList = ProxyRelayListState(signer, cache, proxyRelayListDecryptionCache, scope, settings)
val broadcastRelayListDecryptionCache = BroadcastRelayListDecryptionCache(signer)
val broadcastRelayList = BroadcastRelayListState(signer, cache, broadcastRelayListDecryptionCache, scope, settings)
@@ -297,11 +302,11 @@ class Account(
val trustedRelays = TrustedRelayListsState(nip65RelayList, privateStorageRelayList, localRelayList, dmRelayList, searchRelayList, trustedRelayList, broadcastRelayList, scope)
// Follows Relays
val followOutboxes = FollowListOutboxRelays(kind3FollowList, blockedRelayList, cache, scope)
val followPlusAllMine = MergedFollowPlusMineRelayListsState(followOutboxes, nip65RelayList, privateStorageRelayList, localRelayList, trustedRelayList, broadcastRelayList, indexerRelayList, scope)
val followOutboxesOrProxy = FollowListOutboxOrProxyRelays(kind3FollowList, blockedRelayList, proxyRelayList, cache, scope)
val followPlusAllMine = MergedFollowPlusMineRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, broadcastRelayList, indexerRelayList, scope)
// keeps a cache of the outbox relays for each author
val followsPerRelay = FollowsPerOutboxRelay(kind3FollowList, blockedRelayList, cache, scope).flow
val followsPerRelay = FollowsPerOutboxRelay(kind3FollowList, blockedRelayList, proxyRelayList, cache, scope).flow
// Merges all follow lists to create a single All Follows feed.
val allFollows = MergedFollowListsState(kind3FollowList, hashtagList, geohashList, communityList, scope)
@@ -341,6 +346,7 @@ class Account(
locationFlow = geolocationFlow,
followsRelays = followPlusAllMine.flow,
blockedRelays = blockedRelayList.flow,
proxyRelays = proxyRelayList.flow,
caches = feedDecryptionCaches,
signer = signer,
scope = scope,
@@ -355,6 +361,7 @@ class Account(
locationFlow = geolocationFlow,
followsRelays = followPlusAllMine.flow,
blockedRelays = blockedRelayList.flow,
proxyRelays = proxyRelayList.flow,
caches = feedDecryptionCaches,
signer = signer,
scope = scope,
@@ -369,6 +376,7 @@ class Account(
locationFlow = geolocationFlow,
followsRelays = followPlusAllMine.flow,
blockedRelays = blockedRelayList.flow,
proxyRelays = proxyRelayList.flow,
caches = feedDecryptionCaches,
signer = signer,
scope = scope,
@@ -383,6 +391,7 @@ class Account(
locationFlow = geolocationFlow,
followsRelays = followPlusAllMine.flow,
blockedRelays = blockedRelayList.flow,
proxyRelays = proxyRelayList.flow,
caches = feedDecryptionCaches,
signer = signer,
scope = scope,
@@ -1728,6 +1737,8 @@ class Account(
suspend fun saveBroadcastRelayList(trustedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(broadcastRelayList.saveRelayList(trustedRelays))
suspend fun saveProxyRelayList(trustedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(proxyRelayList.saveRelayList(trustedRelays))
suspend fun saveTrustedRelayList(trustedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(trustedRelayList.saveRelayList(trustedRelays))
suspend fun saveBlockedRelayList(blockedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(blockedRelayList.saveRelayList(blockedRelays))
@@ -140,6 +140,7 @@ import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
@@ -959,6 +960,12 @@ object LocalCache : ILocalCache {
wasVerified: Boolean,
) = consumeBaseReplaceable(event, relay, wasVerified)
private fun consume(
event: ProxyRelayListEvent,
relay: NormalizedRelayUrl?,
wasVerified: Boolean,
) = consumeBaseReplaceable(event, relay, wasVerified)
private fun consume(
event: IndexerRelayListEvent,
relay: NormalizedRelayUrl?,
@@ -2790,6 +2797,7 @@ object LocalCache : ILocalCache {
is PictureEvent -> consume(event, relay, wasVerified)
is PrivateDmEvent -> consume(event, relay, wasVerified)
is PrivateOutboxRelayListEvent -> consume(event, relay, wasVerified)
is ProxyRelayListEvent -> consume(event, relay, wasVerified)
is PinListEvent -> consume(event, relay, wasVerified)
is PeopleListEvent -> consume(event, relay, wasVerified)
is PollNoteEvent -> consume(event, relay, wasVerified)
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
@@ -31,19 +32,24 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlin.collections.flatten
class FollowListOutboxRelays(
class FollowListOutboxOrProxyRelays(
kind3Follows: FollowListState,
blockedRelayList: BlockedRelayListState,
proxyRelayList: ProxyRelayListState,
val cache: LocalCache,
scope: CoroutineScope,
) {
@@ -57,34 +63,63 @@ class FollowListOutboxRelays(
fun allRelayListFlows(followList: Set<HexKey>): List<StateFlow<NoteState>> = followList.map { getNIP65RelayListFlow(it) }
fun combineAllFlows(
flows: List<StateFlow<NoteState>>,
blockedRelays: Set<NormalizedRelayUrl>,
): Flow<Set<NormalizedRelayUrl>> =
fun combineAllFlows(flows: List<StateFlow<NoteState>>): Flow<Set<NormalizedRelayUrl>> =
combine(flows) { relayListNotes: Array<NoteState> ->
relayListNotes.mapNotNull {
relayListNotes
.mapNotNull {
(it.note.event as? AdvertisedRelayListEvent)?.writeRelaysNorm()
}
}.map {
it.flatten().toSet().minus(blockedRelays)
}.flatten()
.toSet()
}
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Set<NormalizedRelayUrl>> =
combineTransform(kind3Follows.flow, blockedRelayList.flow) { followList, blockedRelays ->
val flows: List<StateFlow<NoteState>> = allRelayListFlows(followList.authors)
val relayListFlows = combineAllFlows(flows, blockedRelays)
emitAll(relayListFlows)
val outboxRelayFlow: StateFlow<Set<NormalizedRelayUrl>> =
kind3Follows.flow
.transformLatest {
emitAll(combineAllFlows(allRelayListFlows(it.authors)))
}.onStart {
val blocked = blockedRelayList.flow.value.toSet()
val authors = kind3Follows.flow.value.authors
val perRelay =
authors
emit(
kind3Follows.flow.value.authors
.mapNotNull {
getNIP65RelayList(it)?.writeRelaysNorm()?.minus(blocked)
getNIP65RelayList(it)?.writeRelaysNorm()
}.flatten()
.toSet()
emit(perRelay)
.toSet(),
)
}.distinctUntilChanged()
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
val outboxRelayMinusBlockedFlow: StateFlow<Set<NormalizedRelayUrl>> =
combine(outboxRelayFlow, blockedRelayList.flow) { followList, blockedRelays ->
followList.minus(blockedRelays)
}.onStart {
emit(outboxRelayFlow.value.minus(blockedRelayList.flow.value.toSet()))
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Set<NormalizedRelayUrl>> =
proxyRelayList.flow
.flatMapLatest { proxyRelays ->
if (proxyRelays.isEmpty()) {
outboxRelayMinusBlockedFlow
} else {
MutableStateFlow(proxyRelays)
}
}.onStart {
emit(
proxyRelayList.flow.value.ifEmpty {
outboxRelayMinusBlockedFlow.value
},
)
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
@@ -98,13 +133,7 @@ class FollowListOutboxRelays(
.map { relayList ->
relayList.map { it.url }.toSet()
}.onStart {
emit(
kind3Follows.flow.value.authors
.mapNotNull {
getNIP65RelayList(it)?.writeRelaysNorm()?.map { it.url }?.toSet()
}.flatten()
.toSet(),
)
emit(flow.value.map { it.url }.toSet())
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
@@ -24,6 +24,8 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.scope
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
@@ -35,15 +37,19 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
class FollowsPerOutboxRelay(
kind3Follows: FollowListState,
blockedRelayList: BlockedRelayListState,
proxyRelayList: ProxyRelayListState,
val cache: LocalCache,
scope: CoroutineScope,
) {
@@ -57,42 +63,71 @@ class FollowsPerOutboxRelay(
fun allRelayListFlows(followList: Set<HexKey>): List<StateFlow<NoteState>> = followList.map { getNIP65RelayListFlow(it) }
fun combineAllFlows(
flows: List<StateFlow<NoteState>>,
blockedList: Set<NormalizedRelayUrl>,
): Flow<Map<NormalizedRelayUrl, Set<HexKey>>> =
fun combineAllRelayListFlows(flows: List<StateFlow<NoteState>>): Flow<Map<NormalizedRelayUrl, Set<HexKey>>> =
combine(flows) { relayListNotes: Array<NoteState> ->
mapOfSet {
relayListNotes.forEach { noteState ->
noteState.note.author?.pubkeyHex?.let { authorHex ->
(noteState.note.event as? AdvertisedRelayListEvent)?.writeRelaysNorm()?.forEach { relay ->
if (relay !in blockedList) {
add(relay, authorHex)
}
}
}
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
combineTransform(kind3Follows.flow, blockedRelayList.flow) { followList, blockedList ->
val flows: List<StateFlow<NoteState>> = allRelayListFlows(followList.authors)
val relayListFlows = combineAllFlows(flows, blockedList)
emitAll(relayListFlows)
val outboxPerRelayFlow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
kind3Follows.flow
.transformLatest {
emitAll(combineAllRelayListFlows(allRelayListFlows(it.authors)))
}.onStart {
emit(
mapOfSet {
kind3Follows.flow.value.authors.map { authorHex ->
getNIP65RelayList(authorHex)?.writeRelaysNorm()?.forEach { relay ->
if (relay !in blockedRelayList.flow.value) {
add(relay, authorHex)
}
}
}
},
)
}.distinctUntilChanged()
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyMap(),
)
val outboxPerRelayMinusBlockedFlow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
combine(outboxPerRelayFlow, blockedRelayList.flow) { followList, blockedRelays ->
followList.minus(blockedRelays)
}.onStart {
emit(outboxPerRelayFlow.value.minus(blockedRelayList.flow.value.toSet()))
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyMap(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
proxyRelayList.flow
.flatMapLatest { proxyRelays ->
if (proxyRelays.isEmpty()) {
outboxPerRelayMinusBlockedFlow
} else {
kind3Follows.flow.map { follows ->
proxyRelays.associateWith { follows.authors }
}
}
}.onStart {
if (proxyRelayList.flow.value.isEmpty()) {
emit(outboxPerRelayMinusBlockedFlow.value)
} else {
emit(proxyRelayList.flow.value.associateWith { kind3Follows.flow.value.authors })
}
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
@@ -49,7 +49,7 @@ class BlockedRelayListState(
val scope: CoroutineScope,
val settings: AccountSettings,
) {
fun getBlockedRelayListAddress() = BlockedRelayListEvent.Companion.createAddress(signer.pubKey)
fun getBlockedRelayListAddress() = BlockedRelayListEvent.createAddress(signer.pubKey)
fun getBlockedRelayListNote(): AddressableNote = LocalCache.getOrCreateAddressableNote(getBlockedRelayListAddress())
@@ -64,12 +64,13 @@ class BlockedRelayListState(
val flow =
getBlockedRelayListFlow()
.map { normalizeBlockedRelayListWithBackup(it.note) }
.onStart { emit(normalizeBlockedRelayListWithBackup(getBlockedRelayListNote())) }
.map {
normalizeBlockedRelayListWithBackup(it.note)
}.onStart { emit(normalizeBlockedRelayListWithBackup(getBlockedRelayListNote())) }
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Companion.Eagerly,
SharingStarted.Eagerly,
emptySet(),
)
@@ -78,13 +79,13 @@ class BlockedRelayListState(
val relayListForBlocked = getBlockedRelayList()
return if (relayListForBlocked != null && relayListForBlocked.tags.isNotEmpty()) {
BlockedRelayListEvent.Companion.updateRelayList(
BlockedRelayListEvent.updateRelayList(
earlierVersion = relayListForBlocked,
relays = blockedRelays,
signer = signer,
)
} else {
BlockedRelayListEvent.Companion.create(
BlockedRelayListEvent.create(
relays = blockedRelays,
signer = signer,
)
@@ -48,7 +48,7 @@ class HashtagListState(
val scope: CoroutineScope,
val settings: AccountSettings,
) {
fun getHashtagListAddress() = HashtagListEvent.Companion.createAddress(signer.pubKey)
fun getHashtagListAddress() = HashtagListEvent.createAddress(signer.pubKey)
fun getHashtagListNote(): AddressableNote = cache.getOrCreateAddressableNote(getHashtagListAddress())
@@ -0,0 +1,29 @@
/**
* 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.model.nip51Lists.proxyRelays
import com.vitorpamplona.amethyst.model.nip51Lists.relayLists.GenericRelayListCache
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
class ProxyRelayListDecryptionCache(
signer: NostrSigner,
) : GenericRelayListCache<ProxyRelayListEvent>(signer)
@@ -0,0 +1,87 @@
/**
* 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.model.nip51Lists.proxyRelays
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
class ProxyRelayListState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: ProxyRelayListDecryptionCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
fun getProxyRelayListAddress() = ProxyRelayListEvent.createAddress(signer.pubKey)
fun getProxyRelayListNote(): AddressableNote = cache.getOrCreateAddressableNote(getProxyRelayListAddress())
fun getProxyRelayListFlow(): StateFlow<NoteState> = getProxyRelayListNote().flow().metadata.stateFlow
fun getProxyRelayList(): ProxyRelayListEvent? = getProxyRelayListNote().event as? ProxyRelayListEvent
suspend fun normalizeProxyRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> {
val event = note.event as? ProxyRelayListEvent
return event?.let { decryptionCache.relays(it) } ?: emptySet()
}
val flow =
getProxyRelayListFlow()
.map { normalizeProxyRelayListWithBackup(it.note) }
.onStart { emit(normalizeProxyRelayListWithBackup(getProxyRelayListNote())) }
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
suspend fun saveRelayList(proxyRelays: List<NormalizedRelayUrl>): ProxyRelayListEvent {
val relayListForProxy = getProxyRelayList()
return if (relayListForProxy != null && relayListForProxy.tags.isNotEmpty()) {
ProxyRelayListEvent.updateRelayList(
earlierVersion = relayListForProxy,
relays = proxyRelays,
signer = signer,
)
} else {
ProxyRelayListEvent.create(
relays = proxyRelays,
signer = signer,
)
}
}
}
@@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.stateIn
import kotlin.collections.map
@@ -44,14 +45,14 @@ class MergedFollowListsState(
) {
fun mergeLists(
kind3: FollowListState.Kind3Follows,
hashtages: Set<String>,
hashtags: Set<String>,
geohashes: Set<String>,
community: Set<CommunityTag>,
): FollowListState.Kind3Follows =
FollowListState.Kind3Follows(
kind3.authors,
kind3.authorsPlusMe,
kind3.hashtags + hashtages,
kind3.hashtags + hashtags,
kind3.geotags + geohashes,
kind3.communities + community.map { it.address.toValue() },
)
@@ -73,7 +74,8 @@ class MergedFollowListsState(
communityList.flow.value,
),
)
}.flowOn(Dispatchers.Default)
}.sample(200)
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
@@ -22,10 +22,9 @@ package com.vitorpamplona.amethyst.model.serverList
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxRelays
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxOrProxyRelays
import com.vitorpamplona.amethyst.model.nip51Lists.broadcastRelays.BroadcastRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays.IndexerRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListState
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
@@ -38,11 +37,10 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
class MergedFollowPlusMineRelayListsState(
val followsOutboxRelayList: FollowListOutboxRelays,
val followsOutboxOrProxyRelayList: FollowListOutboxOrProxyRelays,
val nip65RelayList: Nip65RelayListState,
val privateOutboxRelayList: PrivateStorageRelayListState,
val localRelayList: LocalRelayListState,
val trustedRelayList: TrustedRelayListState,
val broadcastRelayList: BroadcastRelayListState,
val indexerRelayList: IndexerRelayListState,
val scope: CoroutineScope,
@@ -52,12 +50,11 @@ class MergedFollowPlusMineRelayListsState(
val flow: StateFlow<Set<NormalizedRelayUrl>> =
combine(
listOf(
followsOutboxRelayList.flow,
followsOutboxOrProxyRelayList.flow,
nip65RelayList.outboxFlow,
nip65RelayList.inboxFlow,
privateOutboxRelayList.flow,
localRelayList.flow,
trustedRelayList.flow,
broadcastRelayList.flow,
indexerRelayList.flow,
),
@@ -66,12 +63,11 @@ class MergedFollowPlusMineRelayListsState(
emit(
mergeLists(
arrayOf(
followsOutboxRelayList.flow.value,
followsOutboxOrProxyRelayList.flow.value,
nip65RelayList.outboxFlow.value,
nip65RelayList.inboxFlow.value,
privateOutboxRelayList.flow.value,
localRelayList.flow.value,
trustedRelayList.flow.value,
broadcastRelayList.flow.value,
indexerRelayList.flow.value,
),
@@ -83,12 +79,11 @@ class MergedFollowPlusMineRelayListsState(
SharingStarted.Eagerly,
mergeLists(
arrayOf(
followsOutboxRelayList.flow.value,
followsOutboxOrProxyRelayList.flow.value,
nip65RelayList.outboxFlow.value,
nip65RelayList.inboxFlow.value,
privateOutboxRelayList.flow.value,
localRelayList.flow.value,
trustedRelayList.flow.value,
broadcastRelayList.flow.value,
indexerRelayList.flow.value,
),
@@ -51,19 +51,20 @@ class FeedTopNavFilterState(
val locationFlow: StateFlow<LocationState.LocationResult>,
val followsRelays: StateFlow<Set<NormalizedRelayUrl>>,
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
val caches: FeedDecryptionCaches,
val signer: NostrSigner,
val scope: CoroutineScope,
) {
fun loadFlowsFor(listName: String): IFeedFlowsType =
when (listName) {
GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays)
ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays)
AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays)
GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays, proxyRelays)
ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
else -> {
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
if (note != null) {
NoteFeedFlow(note.flow().metadata.stateFlow, signer, followsRelays, blockedRelays, caches)
NoteFeedFlow(note.flow().metadata.stateFlow, signer, followsRelays, blockedRelays, proxyRelays, caches)
} else {
UnknownFeedFlow(listName)
}
@@ -20,14 +20,14 @@
*/
package com.vitorpamplona.amethyst.model.topNavFeeds
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.utils.mapOfSet
@@ -50,12 +50,12 @@ class MergedTopFeedAuthorListsState(
fun authorList(navFilter: IFeedTopNavPerRelayFilterSet): Map<NormalizedRelayUrl, Set<HexKey>?> =
when (navFilter) {
is AllCommunitiesTopNavPerRelayFilterSet -> emptyMap()
is AllFollowsByOutboxTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
is AuthorsByOutboxTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
is AllFollowsTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
is AuthorsTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
is GlobalTopNavPerRelayFilterSet -> emptyMap()
is HashtagTopNavPerRelayFilterSet -> emptyMap()
is LocationTopNavPerRelayFilterSet -> emptyMap()
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
is MutedAuthorsTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
is SingleCommunityTopNavPerRelayFilterSet -> navFilter.set.mapValues { it.value.authors }
else -> emptyMap()
}
@@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlin.collections.associateWith
/**
* This is a big OR filter on all fields.
@@ -78,7 +79,7 @@ class AllFollowsByOutboxTopNavFilter(
(communities != null && noteEvent.isTaggedAddressableNotes(communities))
}
override fun toPerRelayFlow(cache: LocalCache): Flow<AllFollowsByOutboxTopNavPerRelayFilterSet> {
override fun toPerRelayFlow(cache: LocalCache): Flow<AllFollowsTopNavPerRelayFilterSet> {
val authorsPerRelay =
if (authors != null) {
OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it }
@@ -95,9 +96,9 @@ class AllFollowsByOutboxTopNavFilter(
return combine(authorsPerRelay, communitiesPerRelay, defaultRelays, blockedRelays) { perRelayAuthors, perRelayCommunities, default, blockedRelays ->
val allRelays = (perRelayAuthors.keys + perRelayCommunities.keys).filter { it !in blockedRelays }.ifEmpty { default }
AllFollowsByOutboxTopNavPerRelayFilterSet(
AllFollowsTopNavPerRelayFilterSet(
allRelays.associateWith {
AllFollowsByOutboxTopNavPerRelayFilter(
AllFollowsTopNavPerRelayFilter(
authors = perRelayAuthors[it],
hashtags = hashtags,
geotags = geotags,
@@ -108,7 +109,7 @@ class AllFollowsByOutboxTopNavFilter(
}
}
override fun startValue(cache: LocalCache): AllFollowsByOutboxTopNavPerRelayFilterSet {
override fun startValue(cache: LocalCache): AllFollowsTopNavPerRelayFilterSet {
val authorsPerRelay =
if (authors != null) {
OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it }
@@ -124,9 +125,9 @@ class AllFollowsByOutboxTopNavFilter(
val allRelays = (authorsPerRelay.keys + communitiesPerRelay.keys).filter { it !in blockedRelays.value }.ifEmpty { defaultRelays.value }
return AllFollowsByOutboxTopNavPerRelayFilterSet(
return AllFollowsTopNavPerRelayFilterSet(
allRelays.associateWith {
AllFollowsByOutboxTopNavPerRelayFilter(
AllFollowsTopNavPerRelayFilter(
authors = authorsPerRelay[it],
hashtags = hashtags,
geotags = geotags,
@@ -0,0 +1,105 @@
/**
* 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.model.topNavFeeds.allFollows
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableNotes
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHashes
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHashes
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.collections.associateWith
/**
* This is a big OR filter on all fields.
*/
@Immutable
class AllFollowsByProxyTopNavFilter(
val authors: Set<String>? = null,
val hashtags: Set<String>? = null,
val geotags: Set<String>? = null,
val communities: Set<String>? = null,
val proxyRelays: Set<NormalizedRelayUrl>,
) : IFeedTopNavFilter {
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf<String>()) { GeohashId.Companion.toScope(it) }
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf<String>()) { HashtagId.Companion.toScope(it) }
override fun matchAuthor(pubkey: HexKey): Boolean = authors == null || pubkey in authors
override fun match(noteEvent: Event): Boolean =
if (noteEvent is LiveActivitiesEvent) {
(authors != null && noteEvent.participantsIntersect(authors)) ||
(hashtags != null && noteEvent.isTaggedHashes(hashtags)) ||
(geotags != null && noteEvent.isTaggedGeoHashes(geotags)) ||
(communities != null && noteEvent.isTaggedAddressableNotes(communities))
} else if (noteEvent is CommentEvent) {
// ignore follows and checks only the root scope
(authors != null && noteEvent.pubKey in authors) ||
(hashtags != null && noteEvent.isTaggedHashes(hashtags)) ||
(hashtagScopes != null && noteEvent.isTaggedScopes(hashtagScopes)) ||
(geotags != null && noteEvent.isTaggedGeoHashes(geotags)) ||
(geotagScopes != null && noteEvent.isTaggedScopes(geotagScopes)) ||
(communities != null && noteEvent.isTaggedAddressableNotes(communities))
} else {
(authors != null && noteEvent.pubKey in authors) ||
(hashtags != null && noteEvent.isTaggedHashes(hashtags)) ||
(geotags != null && noteEvent.isTaggedGeoHashes(geotags)) ||
(communities != null && noteEvent.isTaggedAddressableNotes(communities))
}
// forces the use of the Proxy on all connections, replacing the outbox model.
override fun toPerRelayFlow(cache: LocalCache): Flow<AllFollowsTopNavPerRelayFilterSet> =
MutableStateFlow(
AllFollowsTopNavPerRelayFilterSet(
proxyRelays.associateWith {
AllFollowsTopNavPerRelayFilter(
authors = authors,
hashtags = hashtags,
geotags = geotags,
communities = communities,
)
},
),
)
override fun startValue(cache: LocalCache): AllFollowsTopNavPerRelayFilterSet {
// forces the use of the Proxy on all connections, replacing the outbox model.
return AllFollowsTopNavPerRelayFilterSet(
proxyRelays.associateWith {
AllFollowsTopNavPerRelayFilter(
authors = authors,
hashtags = hashtags,
geotags = geotags,
communities = communities,
)
},
)
}
}
@@ -26,15 +26,20 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
class AllFollowsFeedFlow(
val allFollows: StateFlow<FollowListState.Kind3Follows?>,
val followsRelays: StateFlow<Set<NormalizedRelayUrl>>,
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
) : IFeedFlowsType {
fun convert(kind3: FollowListState.Kind3Follows?): AllFollowsByOutboxTopNavFilter =
fun convert(
kind3: FollowListState.Kind3Follows?,
proxyRelays: Set<NormalizedRelayUrl>,
): IFeedTopNavFilter =
if (kind3 != null) {
if (proxyRelays.isEmpty()) {
AllFollowsByOutboxTopNavFilter(
authors = kind3.authors,
hashtags = kind3.hashtags,
@@ -43,6 +48,15 @@ class AllFollowsFeedFlow(
defaultRelays = followsRelays,
blockedRelays = blockedRelays,
)
} else {
AllFollowsByProxyTopNavFilter(
authors = kind3.authors,
hashtags = kind3.hashtags,
geotags = kind3.geotags,
communities = kind3.communities,
proxyRelays = proxyRelays,
)
}
} else {
AllFollowsByOutboxTopNavFilter(
authors = emptySet(),
@@ -51,9 +65,9 @@ class AllFollowsFeedFlow(
)
}
override fun flow() = allFollows.map(::convert)
override fun flow() = combine(allFollows, proxyRelays, ::convert)
override fun startValue(): AllFollowsByOutboxTopNavFilter = convert(allFollows.value)
override fun startValue(): IFeedTopNavFilter = convert(allFollows.value, proxyRelays.value)
override suspend fun startValue(collector: FlowCollector<IFeedTopNavFilter>) {
collector.emit(startValue())
@@ -29,7 +29,7 @@ import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId
* This is a big OR filter.
*/
@Immutable
class AllFollowsByOutboxTopNavPerRelayFilter(
class AllFollowsTopNavPerRelayFilter(
val authors: Set<String>? = null,
val hashtags: Set<String>? = null,
val geotags: Set<String>? = null,
@@ -23,6 +23,6 @@ package com.vitorpamplona.amethyst.model.topNavFeeds.allFollows
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
class AllFollowsByOutboxTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, AllFollowsByOutboxTopNavPerRelayFilter>,
class AllFollowsTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, AllFollowsTopNavPerRelayFilter>,
) : IFeedTopNavPerRelayFilterSet
@@ -26,30 +26,35 @@ import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
class AroundMeFeedFlow(
val location: StateFlow<LocationState.LocationResult>,
val allFollowRelays: StateFlow<Set<NormalizedRelayUrl>>,
val outboxRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
) : IFeedFlowsType {
fun convert(result: LocationState.LocationResult): LocationTopNavFilter =
fun convert(
result: LocationState.LocationResult,
outboxRelays: Set<NormalizedRelayUrl>,
proxyRelays: Set<NormalizedRelayUrl>,
): LocationTopNavFilter =
if (result is LocationState.LocationResult.Success) {
// 2 neighbors deep = 25x25km
LocationTopNavFilter(
geotags = compute50kmRange(result.geoHash).toSet(),
relays = allFollowRelays,
relayList = proxyRelays.ifEmpty { outboxRelays },
)
} else {
// empty feed until we have a successful geohash
LocationTopNavFilter(
geotags = emptySet(),
relays = allFollowRelays,
relayList = proxyRelays.ifEmpty { outboxRelays },
)
}
override fun flow() = location.map(::convert)
override fun flow() = combine(location, outboxRelays, proxyRelays, ::convert)
override fun startValue(): LocationTopNavFilter = convert(location.value)
override fun startValue(): LocationTopNavFilter = convert(location.value, outboxRelays.value, proxyRelays.value)
override suspend fun startValue(collector: FlowCollector<IFeedTopNavFilter>) {
collector.emit(startValue())
@@ -30,15 +30,14 @@ import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHashes
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.MutableStateFlow
@Immutable
class LocationTopNavFilter(
val geotags: Set<String>,
val relays: StateFlow<Set<NormalizedRelayUrl>>,
val relayList: Set<NormalizedRelayUrl>,
) : IFeedTopNavFilter {
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf<String>()) { GeohashId.Companion.toScope(it) }
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf<String>()) { GeohashId.toScope(it) }
override fun matchAuthor(pubkey: HexKey): Boolean = true
@@ -54,9 +53,12 @@ class LocationTopNavFilter(
}
override fun toPerRelayFlow(cache: LocalCache): Flow<LocationTopNavPerRelayFilterSet> =
relays.map {
LocationTopNavPerRelayFilterSet(it.associateWith { LocationTopNavPerRelayFilter(geotags) })
}
MutableStateFlow(
LocationTopNavPerRelayFilterSet(relayList.associateWith { LocationTopNavPerRelayFilter(geotags) }),
)
override fun startValue(cache: LocalCache): LocationTopNavPerRelayFilterSet = LocationTopNavPerRelayFilterSet(relays.value.associateWith { LocationTopNavPerRelayFilter(geotags) })
override fun startValue(cache: LocalCache): LocationTopNavPerRelayFilterSet =
LocationTopNavPerRelayFilterSet(
relayList.associateWith { LocationTopNavPerRelayFilter(geotags) },
)
}
@@ -28,9 +28,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class GlobalFeedFlow(
val relays: StateFlow<Set<NormalizedRelayUrl>>,
val outboxRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
) : IFeedFlowsType {
val default = GlobalTopNavFilter(relays)
val default = GlobalTopNavFilter(outboxRelays, proxyRelays)
override fun flow() = MutableStateFlow(default)
@@ -28,23 +28,32 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
import kotlin.collections.associateWith
import kotlin.collections.isNotEmpty
@Immutable
class GlobalTopNavFilter(
val relays: StateFlow<Set<NormalizedRelayUrl>>,
val outboxRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
) : IFeedTopNavFilter {
override fun matchAuthor(pubkey: HexKey): Boolean = true
override fun match(noteEvent: Event) = true
override fun toPerRelayFlow(cache: LocalCache): Flow<GlobalTopNavPerRelayFilterSet> =
relays.map {
GlobalTopNavPerRelayFilterSet(it.associateWith { GlobalTopNavPerRelayFilter })
combine(outboxRelays, proxyRelays) { outboxRelays, proxyRelays ->
if (proxyRelays.isNotEmpty()) {
GlobalTopNavPerRelayFilterSet(proxyRelays.associateWith { GlobalTopNavPerRelayFilter })
} else {
GlobalTopNavPerRelayFilterSet(outboxRelays.associateWith { GlobalTopNavPerRelayFilter })
}
}
override fun startValue(cache: LocalCache): GlobalTopNavPerRelayFilterSet =
GlobalTopNavPerRelayFilterSet(
relays.value.associateWith { GlobalTopNavPerRelayFilter },
)
if (proxyRelays.value.isNotEmpty()) {
GlobalTopNavPerRelayFilterSet(proxyRelays.value.associateWith { GlobalTopNavPerRelayFilter })
} else {
GlobalTopNavPerRelayFilterSet(outboxRelays.value.associateWith { GlobalTopNavPerRelayFilter })
}
}
@@ -30,13 +30,12 @@ import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHashes
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.MutableStateFlow
@Immutable
class HashtagTopNavFilter(
val hashtags: Set<String>,
val relays: StateFlow<Set<NormalizedRelayUrl>>,
val relayList: Set<NormalizedRelayUrl>,
) : IFeedTopNavFilter {
val hashtagScopes: Set<String> = hashtags.mapTo(mutableSetOf()) { HashtagId.toScope(it) }
@@ -50,16 +49,14 @@ class HashtagTopNavFilter(
}
override fun toPerRelayFlow(cache: LocalCache): Flow<HashtagTopNavPerRelayFilterSet> =
relays.map {
MutableStateFlow(
HashtagTopNavPerRelayFilterSet(
it.associateWith { HashtagTopNavPerRelayFilter(hashtags) },
relayList.associateWith { HashtagTopNavPerRelayFilter(hashtags) },
),
)
}
override fun startValue(cache: LocalCache): HashtagTopNavPerRelayFilterSet =
HashtagTopNavPerRelayFilterSet(
relays.value.associateWith {
HashtagTopNavPerRelayFilter(hashtags)
},
relayList.associateWith { HashtagTopNavPerRelayFilter(hashtags) },
)
}
@@ -28,8 +28,10 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavFilte
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
@@ -43,16 +45,20 @@ import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.combineTransform
class NoteFeedFlow(
val metadataFlow: StateFlow<NoteState?>,
val signer: NostrSigner,
val allFollowRelays: StateFlow<Set<NormalizedRelayUrl>>,
val outboxRelays: StateFlow<Set<NormalizedRelayUrl>>,
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
val caches: FeedDecryptionCaches,
) : IFeedFlowsType {
fun process(noteEvent: Event): IFeedTopNavFilter =
fun processByOutbox(
noteEvent: Event,
outboxRelays: Set<NormalizedRelayUrl>,
): IFeedTopNavFilter =
when (noteEvent) {
is PeopleListEvent -> {
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
@@ -71,10 +77,10 @@ class NoteFeedFlow(
AllCommunitiesTopNavFilter(caches.communityListCache.cachedCommunityIdSet(noteEvent), blockedRelays)
}
is HashtagListEvent -> {
HashtagTopNavFilter(caches.hashtagCache.cachedHashtags(noteEvent), allFollowRelays)
HashtagTopNavFilter(caches.hashtagCache.cachedHashtags(noteEvent), outboxRelays)
}
is GeohashListEvent -> {
LocationTopNavFilter(caches.geohashCache.cachedGeohashes(noteEvent), allFollowRelays)
LocationTopNavFilter(caches.geohashCache.cachedGeohashes(noteEvent), outboxRelays)
}
is CommunityDefinitionEvent -> {
SingleCommunityTopNavFilter(
@@ -87,7 +93,10 @@ class NoteFeedFlow(
else -> AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
}
suspend fun FlowCollector<IFeedTopNavFilter>.process(noteEvent: Event) {
suspend fun FlowCollector<IFeedTopNavFilter>.processByOutbox(
noteEvent: Event,
outboxRelays: Set<NormalizedRelayUrl>,
) {
when (noteEvent) {
is PeopleListEvent -> {
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
@@ -106,10 +115,10 @@ class NoteFeedFlow(
emit(AllCommunitiesTopNavFilter(caches.communityListCache.communityIdSet(noteEvent), blockedRelays))
}
is HashtagListEvent -> {
emit(HashtagTopNavFilter(caches.hashtagCache.hashtags(noteEvent), allFollowRelays))
emit(HashtagTopNavFilter(caches.hashtagCache.hashtags(noteEvent), outboxRelays))
}
is GeohashListEvent -> {
emit(LocationTopNavFilter(caches.geohashCache.geohashes(noteEvent), allFollowRelays))
emit(LocationTopNavFilter(caches.geohashCache.geohashes(noteEvent), outboxRelays))
}
is CommunityDefinitionEvent -> {
emit(
@@ -128,23 +137,113 @@ class NoteFeedFlow(
}
}
fun processByProxy(
noteEvent: Event,
proxyRelays: Set<NormalizedRelayUrl>,
): IFeedTopNavFilter =
when (noteEvent) {
is PeopleListEvent -> {
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
MutedAuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
} else {
AuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
}
}
is MuteListEvent -> {
MutedAuthorsByProxyTopNavFilter(caches.muteListCache.cachedUserIdSet(noteEvent), proxyRelays)
}
is FollowListEvent -> {
AuthorsByProxyTopNavFilter(noteEvent.followIdSet(), proxyRelays)
}
is CommunityListEvent -> {
AllCommunitiesTopNavFilter(caches.communityListCache.cachedCommunityIdSet(noteEvent), blockedRelays)
}
is HashtagListEvent -> {
HashtagTopNavFilter(caches.hashtagCache.cachedHashtags(noteEvent), proxyRelays)
}
is GeohashListEvent -> {
LocationTopNavFilter(caches.geohashCache.cachedGeohashes(noteEvent), proxyRelays)
}
is CommunityDefinitionEvent -> {
SingleCommunityTopNavFilter(
community = noteEvent.addressTag(),
authors = noteEvent.moderatorKeys().toSet().ifEmpty { null },
relays = noteEvent.relayUrls().toSet(),
blockedRelays = blockedRelays,
)
}
else -> AuthorsByProxyTopNavFilter(emptySet(), proxyRelays)
}
suspend fun FlowCollector<IFeedTopNavFilter>.processByProxy(
noteEvent: Event,
proxyRelays: Set<NormalizedRelayUrl>,
) {
when (noteEvent) {
is PeopleListEvent -> {
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
emit(MutedAuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
} else {
emit(AuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
}
}
is MuteListEvent -> {
emit(MutedAuthorsByProxyTopNavFilter(caches.muteListCache.mutedUserIdSet(noteEvent), proxyRelays))
}
is FollowListEvent -> {
emit(AuthorsByProxyTopNavFilter(noteEvent.followIdSet(), proxyRelays))
}
is CommunityListEvent -> {
emit(AllCommunitiesTopNavFilter(caches.communityListCache.communityIdSet(noteEvent), blockedRelays))
}
is HashtagListEvent -> {
emit(HashtagTopNavFilter(caches.hashtagCache.hashtags(noteEvent), proxyRelays))
}
is GeohashListEvent -> {
emit(LocationTopNavFilter(caches.geohashCache.geohashes(noteEvent), proxyRelays))
}
is CommunityDefinitionEvent -> {
emit(
SingleCommunityTopNavFilter(
community = noteEvent.addressTag(),
authors = noteEvent.moderatorKeys().toSet().ifEmpty { null },
relays = noteEvent.relayUrls().toSet(),
blockedRelays = blockedRelays,
),
)
}
else ->
emit(
AuthorsByProxyTopNavFilter(emptySet(), proxyRelays),
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun flow() =
metadataFlow.transformLatest { noteState ->
combineTransform(metadataFlow, outboxRelays, proxyRelays) { noteState, outboxRelays, proxyRelays ->
val noteEvent = noteState?.note?.event
if (noteEvent == null) {
AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
} else {
process(noteEvent)
if (proxyRelays.isEmpty()) {
processByOutbox(noteEvent, outboxRelays)
} else {
processByProxy(noteEvent, proxyRelays)
}
}
}
override fun startValue(): IFeedTopNavFilter {
val noteEvent = metadataFlow.value?.note?.event
if (noteEvent == null) {
return if (noteEvent == null) {
return AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
} else {
return process(noteEvent)
if (proxyRelays.value.isEmpty()) {
processByOutbox(noteEvent, outboxRelays.value)
} else {
processByProxy(noteEvent, proxyRelays.value)
}
}
}
@@ -153,7 +252,11 @@ class NoteFeedFlow(
if (noteEvent == null) {
collector.emit(AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays))
} else {
collector.process(noteEvent)
if (proxyRelays.value.isEmpty()) {
collector.processByOutbox(noteEvent, outboxRelays.value)
} else {
collector.processByProxy(noteEvent, proxyRelays.value)
}
}
}
}
@@ -47,11 +47,11 @@ class AuthorsByOutboxTopNavFilter(
}
fun convert(map: Map<NormalizedRelayUrl, Set<HexKey>>) =
AuthorsByOutboxTopNavPerRelayFilterSet(
map.mapValues { AuthorsByOutboxTopNavPerRelayFilter(it.value) },
AuthorsTopNavPerRelayFilterSet(
map.mapValues { AuthorsTopNavPerRelayFilter(it.value) },
)
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsByOutboxTopNavPerRelayFilterSet> {
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsTopNavPerRelayFilterSet> {
val authorsPerRelay = OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it }
return combine(authorsPerRelay, blockedRelays) { authors, blocked ->
@@ -59,7 +59,7 @@ class AuthorsByOutboxTopNavFilter(
}
}
override fun startValue(cache: LocalCache): AuthorsByOutboxTopNavPerRelayFilterSet {
override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet {
val authorsPerRelay = OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it }
return convert(authorsPerRelay.minus(blockedRelays.value))
@@ -0,0 +1,58 @@
/**
* 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.model.topNavFeeds.noteBased.author
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@Immutable
class AuthorsByProxyTopNavFilter(
val authors: Set<String>,
val proxyRelays: Set<NormalizedRelayUrl>,
) : IFeedTopNavFilter {
override fun matchAuthor(pubkey: HexKey) = pubkey in authors
override fun match(noteEvent: Event): Boolean =
if (noteEvent is LiveActivitiesEvent) {
noteEvent.participantsIntersect(authors)
} else {
noteEvent.pubKey in authors
}
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsTopNavPerRelayFilterSet> =
MutableStateFlow(
AuthorsTopNavPerRelayFilterSet(
proxyRelays.associateWith { AuthorsTopNavPerRelayFilter(authors) },
),
)
override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet =
AuthorsTopNavPerRelayFilterSet(
proxyRelays.associateWith { AuthorsTopNavPerRelayFilter(authors) },
)
}
@@ -24,6 +24,6 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilter
@Immutable
class AuthorsByOutboxTopNavPerRelayFilter(
class AuthorsTopNavPerRelayFilter(
val authors: Set<String>,
) : IFeedTopNavPerRelayFilter
@@ -23,6 +23,6 @@ package com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
class AuthorsByOutboxTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, AuthorsByOutboxTopNavPerRelayFilter>,
class AuthorsTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, AuthorsTopNavPerRelayFilter>,
) : IFeedTopNavPerRelayFilterSet
@@ -47,11 +47,11 @@ class MutedAuthorsByOutboxTopNavFilter(
}
fun convert(map: Map<NormalizedRelayUrl, Set<HexKey>>) =
MutedAuthorsByOutboxTopNavPerRelayFilterSet(
map.mapValues { MutedAuthorsByOutboxTopNavPerRelayFilter(it.value) },
MutedAuthorsTopNavPerRelayFilterSet(
map.mapValues { MutedAuthorsTopNavPerRelayFilter(it.value) },
)
override fun toPerRelayFlow(cache: LocalCache): Flow<MutedAuthorsByOutboxTopNavPerRelayFilterSet> {
override fun toPerRelayFlow(cache: LocalCache): Flow<MutedAuthorsTopNavPerRelayFilterSet> {
val authorsPerRelay = OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it }
return combine(authorsPerRelay, blockedRelays) { authors, blocked ->
@@ -59,7 +59,7 @@ class MutedAuthorsByOutboxTopNavFilter(
}
}
override fun startValue(cache: LocalCache): MutedAuthorsByOutboxTopNavPerRelayFilterSet {
override fun startValue(cache: LocalCache): MutedAuthorsTopNavPerRelayFilterSet {
val authorsPerRelay = OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it }
return convert(authorsPerRelay.minus(blockedRelays.value))
@@ -0,0 +1,58 @@
/**
* 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.model.topNavFeeds.noteBased.muted
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@Immutable
class MutedAuthorsByProxyTopNavFilter(
val authors: Set<String>,
val proxyRelays: Set<NormalizedRelayUrl>,
) : IFeedTopNavFilter {
override fun matchAuthor(pubkey: HexKey) = pubkey in authors
override fun match(noteEvent: Event): Boolean =
if (noteEvent is LiveActivitiesEvent) {
noteEvent.participantsIntersect(authors)
} else {
noteEvent.pubKey in authors
}
override fun toPerRelayFlow(cache: LocalCache): Flow<MutedAuthorsTopNavPerRelayFilterSet> =
MutableStateFlow(
MutedAuthorsTopNavPerRelayFilterSet(
proxyRelays.associateWith { MutedAuthorsTopNavPerRelayFilter(authors) },
),
)
override fun startValue(cache: LocalCache): MutedAuthorsTopNavPerRelayFilterSet =
MutedAuthorsTopNavPerRelayFilterSet(
proxyRelays.associateWith { MutedAuthorsTopNavPerRelayFilter(authors) },
)
}
@@ -24,6 +24,6 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilter
@Immutable
class MutedAuthorsByOutboxTopNavPerRelayFilter(
class MutedAuthorsTopNavPerRelayFilter(
val authors: Set<String>,
) : IFeedTopNavPerRelayFilter
@@ -23,6 +23,6 @@ package com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
class MutedAuthorsByOutboxTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, MutedAuthorsByOutboxTopNavPerRelayFilter>,
class MutedAuthorsTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, MutedAuthorsTopNavPerRelayFilter>,
) : IFeedTopNavPerRelayFilterSet
@@ -31,9 +31,12 @@ import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
@@ -59,6 +62,9 @@ val AccountInfoAndListsFromKeyKinds2 =
TrustedRelayListEvent.KIND,
BroadcastRelayListEvent.KIND,
IndexerRelayListEvent.KIND,
ProxyRelayListEvent.KIND,
HashtagListEvent.KIND,
GeohashListEvent.KIND,
)
val AmethystMetadataKinds = listOf(AppSpecificDataEvent.KIND)
@@ -28,9 +28,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
@@ -53,6 +56,9 @@ val BasicAccountInfoKinds2 =
TrustedRelayListEvent.KIND,
BroadcastRelayListEvent.KIND,
IndexerRelayListEvent.KIND,
ProxyRelayListEvent.KIND,
HashtagListEvent.KIND,
GeohashListEvent.KIND,
)
fun filterBasicAccountInfoFromKeys(
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -62,7 +63,7 @@ class FilterByListParams(
fun isGlobal(comingFrom: List<NormalizedRelayUrl>) =
followLists is GlobalTopNavFilter &&
comingFrom.any { followLists.relays.value.contains(it) }
comingFrom.any { followLists.outboxRelays.value.contains(it) }
fun match(
noteEvent: Event,
@@ -89,7 +90,7 @@ class FilterByListParams(
hiddenUsers: HiddenUsersState.LiveHiddenUsers,
): FilterByListParams =
FilterByListParams(
isHiddenList = followLists is MutedAuthorsByOutboxTopNavFilter,
isHiddenList = followLists is MutedAuthorsByOutboxTopNavFilter || followLists is MutedAuthorsByProxyTopNavFilter,
followLists = followLists,
hiddenLists = hiddenUsers,
)
@@ -85,6 +85,7 @@ import com.vitorpamplona.amethyst.ui.note.types.DisplayFollowList
import com.vitorpamplona.amethyst.ui.note.types.DisplayIndexerRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList
import com.vitorpamplona.amethyst.ui.note.types.DisplayProxyRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet
import com.vitorpamplona.amethyst.ui.note.types.DisplaySearchRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayTrustedRelayList
@@ -191,6 +192,7 @@ import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
@@ -659,6 +661,7 @@ private fun RenderNoteRow(
is BlockedRelayListEvent -> DisplayBlockedRelayList(baseNote, backgroundColor, accountViewModel, nav)
is TrustedRelayListEvent -> DisplayTrustedRelayList(baseNote, backgroundColor, accountViewModel, nav)
is IndexerRelayListEvent -> DisplayIndexerRelayList(baseNote, backgroundColor, accountViewModel, nav)
is ProxyRelayListEvent -> DisplayProxyRelayList(baseNote, backgroundColor, accountViewModel, nav)
is BroadcastRelayListEvent -> DisplayBroadcastRelayList(baseNote, backgroundColor, accountViewModel, nav)
is PinListEvent -> RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
is EmojiPackEvent -> RenderEmojiPack(baseNote, true, backgroundColor, accountViewModel)
@@ -234,6 +234,27 @@ fun DisplayTrustedRelayList(
)
}
@Composable
fun DisplayProxyRelayList(
baseNote: Note,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: INav,
) {
val relays by accountViewModel.account.proxyRelayListDecryptionCache.observeDecryptedRelayList(baseNote).collectAsStateWithLifecycle(
accountViewModel.account.proxyRelayListDecryptionCache.fastStartValueForRelayList(baseNote),
)
DisplayRelaySet(
relays,
stringRes(id = R.string.proxy_relays_title),
null,
backgroundColor,
accountViewModel,
nav,
)
}
@Composable
fun DisplayIndexerRelayList(
baseNote: Note,
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies.filterLongFormByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies.filterLongFormByAuthors
@@ -46,12 +46,12 @@ fun makeLongFormFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterLongFormByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterLongFormByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterLongFormByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterLongFormByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterLongFormByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterLongFormGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterLongFormByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterLongFormByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterLongFormByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterLongFormByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterLongFormByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -50,7 +50,7 @@ fun filterLongFormAuthors(
}
fun filterLongFormByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -71,7 +71,7 @@ fun filterLongFormByAuthors(
}
fun filterLongFormByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterLongFormByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -46,9 +46,12 @@ import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
@@ -115,6 +118,9 @@ fun RenderPublicChatChannelThumb(
is MutedAuthorsByOutboxTopNavFilter -> topFilter.authors
is AllFollowsByOutboxTopNavFilter -> topFilter.authors
is SingleCommunityTopNavFilter -> topFilter.authors
is AuthorsByProxyTopNavFilter -> topFilter.authors
is MutedAuthorsByProxyTopNavFilter -> topFilter.authors
is AllFollowsByProxyTopNavFilter -> topFilter.authors
else -> null
}
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies.filterPublicChatsByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies.filterPublicChatsByAuthors
@@ -46,12 +46,12 @@ fun makePublicChatsFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterPublicChatsByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterPublicChatsByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterPublicChatsByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterPublicChatsByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterPublicChatsByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterPublicChatsGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterPublicChatsByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterPublicChatsByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterPublicChatsByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterPublicChatsByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterPublicChatsByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -57,7 +57,7 @@ fun filterPublicChatsAuthors(
}
fun filterPublicChatsByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -78,7 +78,7 @@ fun filterPublicChatsByAuthors(
}
fun filterPublicChatsByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterPublicChatsByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies.filterFollowSetsByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies.filterFollowSetsByAuthors
@@ -46,12 +46,12 @@ fun makeFollowSetsFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterFollowSetsByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterFollowSetsByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterFollowSetsByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterFollowSetsByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterFollowSetsByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterFollowSetsGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterFollowSetsByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterFollowSetsByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterFollowSetsByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterFollowSetsByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterFollowSetsByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -50,7 +50,7 @@ fun filterFollowSetsAuthors(
}
fun filterFollowSetsByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -71,7 +71,7 @@ fun filterFollowSetsByAuthors(
}
fun filterFollowSetsByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterFollowSetsByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -25,9 +25,12 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
@@ -80,6 +83,9 @@ open class DiscoverLiveFeedFilter(
is MutedAuthorsByOutboxTopNavFilter -> topFilter.authors
is AllFollowsByOutboxTopNavFilter -> topFilter.authors
is SingleCommunityTopNavFilter -> topFilter.authors
is AuthorsByProxyTopNavFilter -> topFilter.authors
is MutedAuthorsByProxyTopNavFilter -> topFilter.authors
is AllFollowsByProxyTopNavFilter -> topFilter.authors
else -> null
}
@@ -46,9 +46,12 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
@@ -242,6 +245,9 @@ fun LoadParticipants(
is MutedAuthorsByOutboxTopNavFilter -> topFilter.authors
is AllFollowsByOutboxTopNavFilter -> topFilter.authors
is SingleCommunityTopNavFilter -> topFilter.authors
is AuthorsByProxyTopNavFilter -> topFilter.authors
is MutedAuthorsByProxyTopNavFilter -> topFilter.authors
is AllFollowsByProxyTopNavFilter -> topFilter.authors
else -> emptySet()
}
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies.filterLiveActivitiesByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies.filterLiveActivitiesByAuthors
@@ -46,12 +46,12 @@ fun makeLiveActivitiesFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterLiveActivitiesByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterLiveActivitiesByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterLiveActivitiesByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterLiveActivitiesByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterLiveActivitiesByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterLiveActivitiesGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterLiveActivitiesByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterLiveActivitiesByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterLiveActivitiesByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterLiveActivitiesByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterLiveActivitiesByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -62,7 +62,7 @@ fun filterLiveActivitiesAuthors(
}
fun filterLiveActivitiesByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -83,7 +83,7 @@ fun filterLiveActivitiesByAuthors(
}
fun filterLiveActivitiesByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterLiveActivitiesByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -48,9 +48,12 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
@@ -208,6 +211,9 @@ fun LoadModerators(
is MutedAuthorsByOutboxTopNavFilter -> topFilter.authors
is AllFollowsByOutboxTopNavFilter -> topFilter.authors
is SingleCommunityTopNavFilter -> topFilter.authors
is AuthorsByProxyTopNavFilter -> topFilter.authors
is MutedAuthorsByProxyTopNavFilter -> topFilter.authors
is AllFollowsByProxyTopNavFilter -> topFilter.authors
else -> null
}
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies.filterCommunitiesByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies.filterCommunitiesByAuthors
@@ -46,12 +46,12 @@ fun makeCommunitiesFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterCommunitiesByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterCommunitiesByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterCommunitiesByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterCommunitiesByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterCommunitiesByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterCommunitiesGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterCommunitiesByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterCommunitiesByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterCommunitiesByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterCommunitiesByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterCommunitiesByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -51,7 +51,7 @@ fun filterCommunitiesAuthors(
}
fun filterCommunitiesByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -72,7 +72,7 @@ fun filterCommunitiesByAuthors(
}
fun filterCommunitiesByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterCommunitiesByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies.filterContentDVMsByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies.filterContentDVMsByAuthors
@@ -46,12 +46,12 @@ fun makeContentDVMsFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterContentDVMsByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterContentDVMsByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterContentDVMsByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterContentDVMsByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterContentDVMsByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterContentDVMsGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterContentDVMsByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterContentDVMsByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterContentDVMsByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterContentDVMsByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterContentDVMsByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -52,7 +52,7 @@ fun filterContentDVMsAuthors(
}
fun filterContentDVMsByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -73,7 +73,7 @@ fun filterContentDVMsByAuthors(
}
fun filterContentDVMsByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterContentDVMsByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies.filterClassifiedsByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies.filterClassifiedsByAuthors
@@ -46,12 +46,12 @@ fun makeClassifiedsFilter(
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterClassifiedsByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterClassifiedsByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterClassifiedsByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterClassifiedsByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterClassifiedsByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterClassifiedsGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterClassifiedsByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterClassifiedsByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterClassifiedsByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterClassifiedsByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterClassifiedsByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
@@ -50,7 +50,7 @@ fun filterClassifiedsAuthors(
}
fun filterClassifiedsByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -71,7 +71,7 @@ fun filterClassifiedsByAuthors(
}
fun filterClassifiedsByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,12 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterClassifiedsByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -68,7 +68,7 @@ fun PrepareViewModelsGeoHashScreen(
factory =
GeoHashFeedViewModel.Factory(
tag.geohash,
accountViewModel.account.followOutboxes.flow.value,
accountViewModel.account.followOutboxesOrProxy.flow.value,
accountViewModel.account,
),
)
@@ -68,7 +68,7 @@ fun filterPostsByGeohash(
Filter(
tags = geohashesToFollowMap,
kinds = PostsByGeohashKinds,
limit = 200,
limit = 100,
since = since,
),
),
@@ -78,7 +78,7 @@ fun filterPostsByGeohash(
Filter(
tags = geohashesScoreMap,
kinds = CommentKinds,
limit = 200,
limit = 100,
since = since,
),
),
@@ -37,7 +37,7 @@ fun GeoHashFilterAssemblerSubscription(
// even if they are tracking the same tag.
val state =
remember(tag) {
GeohashQueryState(tag.geohash, accountViewModel.account.followOutboxes.flow.value)
GeohashQueryState(tag.geohash, accountViewModel.account.followOutboxesOrProxy.flow.value)
}
KeyDataSourceSubscription(state, accountViewModel.dataSources().geohashes)
@@ -73,7 +73,7 @@ fun PrepareViewModelsHashtagScreen(
factory =
HashtagFeedViewModel.Factory(
tag.hashtag,
accountViewModel.account.followOutboxes.flow.value,
accountViewModel.account.followOutboxesOrProxy.flow.value,
accountViewModel.account,
),
)
@@ -35,7 +35,7 @@ fun HashtagFilterAssemblerSubscription(
// even if they are tracking the same tag.
val state =
remember(tag) {
HashtagQueryState(tag.hashtag, accountViewModel.account.followOutboxes.flow.value)
HashtagQueryState(tag.hashtag, accountViewModel.account.followOutboxesOrProxy.flow.value)
}
KeyDataSourceSubscription(state, accountViewModel.dataSources().hashtags)
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
@@ -39,7 +40,9 @@ class HomeConversationsFeedFilter(
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultHomeFollowList.value
override fun showHiddenKey(): Boolean = account.liveHomeFollowLists.value is MutedAuthorsByOutboxTopNavFilter
override fun showHiddenKey(): Boolean =
account.liveHomeFollowLists.value is MutedAuthorsByOutboxTopNavFilter ||
account.liveHomeFollowLists.value is MutedAuthorsByProxyTopNavFilter
override fun feed(): List<Note> {
val filterParams = buildFilterParams(account)
@@ -25,9 +25,12 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.ui.dal.AdditiveComplexFeedFilter
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
@@ -130,6 +133,9 @@ class HomeLiveFilter(
is MutedAuthorsByOutboxTopNavFilter -> topFilter.authors
is AllFollowsByOutboxTopNavFilter -> topFilter.authors
is SingleCommunityTopNavFilter -> topFilter.authors
is AuthorsByProxyTopNavFilter -> topFilter.authors
is MutedAuthorsByProxyTopNavFilter -> topFilter.authors
is AllFollowsByProxyTopNavFilter -> topFilter.authors
else -> null
}
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByProxyTopNavFilter
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
@@ -45,7 +46,9 @@ class HomeNewThreadFeedFilter(
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultHomeFollowList.value
override fun showHiddenKey(): Boolean = account.liveHomeFollowLists.value is MutedAuthorsByOutboxTopNavFilter
override fun showHiddenKey(): Boolean =
account.liveHomeFollowLists.value is MutedAuthorsByOutboxTopNavFilter ||
account.liveHomeFollowLists.value is MutedAuthorsByProxyTopNavFilter
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip65Follows
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip01Core.filterHomePostsByGeohashes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip01Core.filterHomePostsByHashtags
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip72Commun
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterHomePostsByAllFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
sinceBoundaryNew: Long?,
sinceBoundaryReply: Long?,
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip65Follows
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
@@ -106,7 +106,7 @@ fun filterReplyHomePostsByAuthors(
}
fun filterHomePostsByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
sinceBoundaryNew: Long?,
sinceBoundaryReply: Long?,
@@ -133,7 +133,7 @@ fun filterHomePostsByAuthors(
}
fun filterHomePostsByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
sinceBoundaryNew: Long?,
sinceBoundaryReply: Long?,
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip65Follows
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeQueryState
@@ -60,12 +60,12 @@ class HomeOutboxEventsEoseManager(
val repliesSince = key.feedState.homeReplies.lastNoteCreatedAtIfFilled()
return when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterHomePostsByAllCommunities(feedSettings, since, newThreadSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterHomePostsByAllFollows(feedSettings, since, newThreadSince, repliesSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterHomePostsByAuthors(feedSettings, since, newThreadSince, repliesSince)
is AllFollowsTopNavPerRelayFilterSet -> filterHomePostsByAllFollows(feedSettings, since, newThreadSince, repliesSince)
is AuthorsTopNavPerRelayFilterSet -> filterHomePostsByAuthors(feedSettings, since, newThreadSince, repliesSince)
is GlobalTopNavPerRelayFilterSet -> filterHomePostsByGlobal(feedSettings, since, newThreadSince, repliesSince)
is HashtagTopNavPerRelayFilterSet -> filterHomePostsByHashtags(feedSettings, since, newThreadSince)
is LocationTopNavPerRelayFilterSet -> filterHomePostsByGeohashes(feedSettings, since, newThreadSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterHomePostsByAuthors(feedSettings, since, newThreadSince, repliesSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterHomePostsByAuthors(feedSettings, since, newThreadSince, repliesSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterHomePostsByCommunity(feedSettings, since, newThreadSince)
else -> emptyList()
}
@@ -64,6 +64,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip37.renderPrivateO
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.Nip65RelayListViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.renderNip65HomeItems
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.renderNip65NotifItems
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.proxy.ProxyRelayListViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.proxy.renderProxyItems
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayListViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.renderSearchItems
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.trusted.TrustedRelayListViewModel
@@ -90,6 +92,7 @@ fun AllRelayListScreen(
val connectedViewModel: ConnectedRelayListViewModel = viewModel()
val broadcastViewModel: BroadcastRelayListViewModel = viewModel()
val indexerViewModel: IndexerRelayListViewModel = viewModel()
val proxyViewModel: ProxyRelayListViewModel = viewModel()
dmViewModel.init(accountViewModel)
nip65ViewModel.init(accountViewModel)
@@ -101,6 +104,7 @@ fun AllRelayListScreen(
trustedViewModel.init(accountViewModel)
broadcastViewModel.init(accountViewModel)
indexerViewModel.init(accountViewModel)
proxyViewModel.init(accountViewModel)
LaunchedEffect(accountViewModel) {
dmViewModel.load()
@@ -113,6 +117,7 @@ fun AllRelayListScreen(
trustedViewModel.load()
broadcastViewModel.load()
indexerViewModel.load()
proxyViewModel.load()
}
MappedAllRelayListView(
@@ -126,6 +131,7 @@ fun AllRelayListScreen(
trustedViewModel,
broadcastViewModel,
indexerViewModel,
proxyViewModel,
accountViewModel,
nav,
)
@@ -144,6 +150,7 @@ fun MappedAllRelayListView(
trustedViewModel: TrustedRelayListViewModel,
broadcastViewModel: BroadcastRelayListViewModel,
indexerViewModel: IndexerRelayListViewModel,
proxyViewModel: ProxyRelayListViewModel,
accountViewModel: AccountViewModel,
newNav: INav,
) {
@@ -158,6 +165,7 @@ fun MappedAllRelayListView(
val connectedRelays by connectedViewModel.relays.collectAsStateWithLifecycle()
val broadcastRelays by broadcastViewModel.relays.collectAsStateWithLifecycle()
val indexerRelays by indexerViewModel.relays.collectAsStateWithLifecycle()
val proxyRelays by proxyViewModel.relays.collectAsStateWithLifecycle()
Scaffold(
topBar = {
@@ -173,6 +181,7 @@ fun MappedAllRelayListView(
blockedViewModel.clear()
broadcastViewModel.clear()
indexerViewModel.clear()
proxyViewModel.clear()
newNav.popBack()
},
onPost = {
@@ -185,6 +194,7 @@ fun MappedAllRelayListView(
blockedViewModel.create()
broadcastViewModel.create()
indexerViewModel.create()
proxyViewModel.create()
newNav.popBack()
},
)
@@ -242,6 +252,15 @@ fun MappedAllRelayListView(
}
renderPrivateOutboxItems(privateOutboxFeedState, privateOutboxViewModel, accountViewModel, newNav)
item {
SettingsCategory(
stringRes(R.string.proxy_section),
stringRes(R.string.proxy_section_explainer),
SettingsCategorySpacingModifier,
)
}
renderProxyItems(proxyRelays, proxyViewModel, accountViewModel, newNav)
item {
SettingsCategory(
stringRes(R.string.broadcast_section),
@@ -0,0 +1,81 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.proxy
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
@Composable
fun ProxyRelayList(
postViewModel: ProxyRelayListViewModel,
accountViewModel: AccountViewModel,
onClose: () -> Unit,
nav: INav,
) {
val feedState by postViewModel.relays.collectAsStateWithLifecycle()
val newNav = rememberExtendedNav(nav, onClose)
Row(verticalAlignment = Alignment.CenterVertically) {
LazyColumn(
contentPadding = FeedPadding,
) {
renderProxyItems(feedState, postViewModel, accountViewModel, newNav)
}
}
}
fun LazyListScope.renderProxyItems(
feedState: List<BasicRelaySetupInfo>,
postViewModel: ProxyRelayListViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
itemsIndexed(feedState, key = { _, item -> "Proxy" + item.relay }) { index, item ->
BasicRelaySetupInfoDialog(
item,
onDelete = { postViewModel.deleteRelay(item) },
accountViewModel = accountViewModel,
nav,
)
}
item {
Spacer(modifier = StdVertSpacer)
RelayUrlEditField { postViewModel.addRelay(relaySetupInfoBuilder(it)) }
}
}
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.proxy
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
class ProxyRelayListViewModel : BasicRelaySetupInfoModel() {
override fun getRelayList(): List<NormalizedRelayUrl>? =
account.proxyRelayList.flow.value
.toList()
override suspend fun saveRelayList(urlList: List<NormalizedRelayUrl>) {
account.saveProxyRelayList(urlList)
}
}
@@ -125,6 +125,7 @@ import com.vitorpamplona.amethyst.ui.note.types.DisplayFollowList
import com.vitorpamplona.amethyst.ui.note.types.DisplayIndexerRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList
import com.vitorpamplona.amethyst.ui.note.types.DisplayProxyRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet
import com.vitorpamplona.amethyst.ui.note.types.DisplaySearchRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayTrustedRelayList
@@ -212,6 +213,7 @@ import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
@@ -605,6 +607,8 @@ private fun FullBleedNoteCompose(
DisplaySearchRelayList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is BlockedRelayListEvent) {
DisplayBlockedRelayList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is ProxyRelayListEvent) {
DisplayProxyRelayList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is TrustedRelayListEvent) {
DisplayTrustedRelayList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is IndexerRelayListEvent) {
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassemblies
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoQueryState
@@ -61,12 +61,12 @@ class VideoOutboxEventsFilterSubAssembler(
val defaultSince = key.feedState.videoFeed.lastNoteCreatedAtWhenFullyLoaded.value
return when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterPictureAndVideoByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsByOutboxTopNavPerRelayFilterSet -> filterPictureAndVideoByFollows(feedSettings, since, defaultSince)
is AuthorsByOutboxTopNavPerRelayFilterSet -> filterPictureAndVideoByAuthors(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterPictureAndVideoByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterPictureAndVideoByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterPictureAndVideoGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterPictureAndVideoByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterPictureAndVideoByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsByOutboxTopNavPerRelayFilterSet -> filterPictureAndVideoByAuthors(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterPictureAndVideoByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterPictureAndVideoByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -20,8 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassemblies.nip65Follows
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.LegacyMimeTypeMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.PictureAndVideoKinds
@@ -64,7 +64,7 @@ fun filterPictureAndVideoAuthors(
}
fun filterPictureAndVideoByAuthors(
authorSet: AuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -85,7 +85,7 @@ fun filterPictureAndVideoByAuthors(
}
fun filterPictureAndVideoByAuthors(
authorSet: MutedAuthorsByOutboxTopNavPerRelayFilterSet,
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassemblies.nip65Follows
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsByOutboxTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassemblies.nip01Core.filterPictureAndVideoGeohash
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassemblies.nip01Core.filterPictureAndVideoHashtag
@@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.subassembl
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterPictureAndVideoByFollows(
followsSet: AllFollowsByOutboxTopNavPerRelayFilterSet,
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
+5
View File
@@ -494,6 +494,7 @@
<string name="follow_list_selection">Follow List</string>
<string name="follow_list_kind3follows">All Follows</string>
<string name="follow_list_kind3follows_proxy">Follows via Proxy</string>
<string name="follow_list_aroundme">Around Me</string>
<string name="follow_list_global">Global</string>
<string name="follow_list_mute_list">Mute List</string>
@@ -1092,6 +1093,10 @@
<string name="trusted_section">Trusted Relays</string>
<string name="trusted_section_explainer">Relays you trust to not need a Tor connection for</string>
<string name="proxy_relays_title">Proxy Relays</string>
<string name="proxy_section">Proxy Relays</string>
<string name="proxy_section_explainer">Aggregator relays the app must use to download your feeds from, like filter.nostr.wine. This replaces the outbox model and will make the app connect to only the relays in your lists.</string>
<string name="broadcast_relays_title">Broadcast Relays</string>
<string name="broadcast_section">Broadcast Relays</string>
<string name="broadcast_section_explainer">Relays that specialize in pushing your notes to all of the other relays, like sendit.nosflare.com. Amethyst will add this relay to all new events made by you</string>
@@ -84,6 +84,7 @@ import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
@@ -262,6 +263,7 @@ class EventFactory {
PollNoteEvent.KIND -> PollNoteEvent(id, pubKey, createdAt, tags, content, sig)
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
ProxyRelayListEvent.KIND -> ProxyRelayListEvent(id, pubKey, createdAt, tags, content, sig)
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
RelationshipStatusEvent.KIND -> RelationshipStatusEvent(id, pubKey, createdAt, tags, content, sig)
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
@@ -58,7 +58,7 @@ class BlockedRelayListEvent(
companion object {
const val KIND = 10006
val ALT = "Blocked relays from this author"
const val ALT = "Blocked relays from this author"
val ALT_TAG = arrayOf(AltTag.Companion.assemble(ALT))
fun createAddress(pubKey: HexKey): Address = Address(KIND, pubKey, "")
@@ -59,7 +59,7 @@ class BroadcastRelayListEvent(
companion object {
const val KIND = 10088
const val ALT = "Broadcasting relays from this author"
const val ALT = "Broadcast relays from this author"
val TAGS = arrayOf(AltTag.assemble(ALT))
fun createAddress(pubKey: HexKey): Address = Address(KIND, pubKey, "")
@@ -75,9 +75,7 @@ class BroadcastRelayListEvent(
createdAt: Long = TimeUtils.now(),
): BroadcastRelayListEvent {
val newRelayList = relays.map { RelayTag.assemble(it) }
println("AABBCC 1 ${earlierVersion.content}")
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
println("AABBCC 2 ${privateTags.size}")
val publicTags = earlierVersion.tags.remove(RelayTag::match)
val newPrivateTags = privateTags.remove(RelayTag::notMatch).plus(newRelayList)
@@ -58,8 +58,8 @@ class IndexerRelayListEvent(
suspend fun decryptRelays(signer: NostrSigner): List<NormalizedRelayUrl> = publicRelays() + (decryptPrivateRelays(signer) ?: emptyList())
companion object {
const val KIND = 10087
const val ALT = "Broadcasting relays from this author"
const val KIND = 10086
const val ALT = "Indexer relays from this author"
val TAGS = arrayOf(AltTag.assemble(ALT))
fun createAddress(pubKey: HexKey): Address = Address(KIND, pubKey, "")
@@ -0,0 +1,121 @@
/**
* 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.quartz.nip51Lists.relayLists
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent
import com.vitorpamplona.quartz.nip51Lists.encryption.signNip51List
import com.vitorpamplona.quartz.nip51Lists.relayLists.tags.RelayTag
import com.vitorpamplona.quartz.nip51Lists.relayLists.tags.proxyRelays
import com.vitorpamplona.quartz.nip51Lists.relayLists.tags.relays
import com.vitorpamplona.quartz.nip51Lists.remove
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.collections.plus
@Immutable
class ProxyRelayListEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : PrivateTagArrayEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
fun publicRelays() = tags.relays()
suspend fun decryptPrivateRelays(signer: NostrSigner) = privateTags(signer)?.relays()
suspend fun decryptRelays(signer: NostrSigner): List<NormalizedRelayUrl> = publicRelays() + (decryptPrivateRelays(signer) ?: emptyList())
companion object {
const val KIND = 10087
const val ALT = "Proxy relays from this author"
val TAGS = arrayOf(AltTag.assemble(ALT))
fun createAddress(pubKey: HexKey): Address = Address(KIND, pubKey, "")
fun createAddressATag(pubKey: HexKey): ATag = ATag(KIND, pubKey, "", null)
fun createAddressTag(pubKey: HexKey): String = Address.Companion.assemble(KIND, pubKey, "")
suspend fun updateRelayList(
earlierVersion: ProxyRelayListEvent,
relays: List<NormalizedRelayUrl>,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
): ProxyRelayListEvent {
val newRelayList = relays.map { RelayTag.assemble(it) }
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
val publicTags = earlierVersion.tags.remove(RelayTag::match)
val newPrivateTags = privateTags.remove(RelayTag::notMatch).plus(newRelayList)
return signer.signNip51List(createdAt, KIND, publicTags, newPrivateTags)
}
suspend fun create(
relays: List<NormalizedRelayUrl>,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
): ProxyRelayListEvent {
val privateTagArray = relays.map { RelayTag.assemble(it) }.toTypedArray()
return signer.signNip51List(createdAt, KIND, TAGS, privateTagArray)
}
suspend fun create(
relays: List<NormalizedRelayUrl>,
signer: NostrSignerSync,
createdAt: Long = TimeUtils.now(),
): ProxyRelayListEvent {
val privateTagArray = relays.map { RelayTag.assemble(it) }.toTypedArray()
return signer.signNip51List(createdAt, KIND, TAGS, privateTagArray)
}
suspend fun build(
publicRelays: List<NormalizedRelayUrl> = emptyList(),
privateRelays: List<NormalizedRelayUrl> = emptyList(),
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<ProxyRelayListEvent>.() -> Unit = {},
) = eventTemplate<ProxyRelayListEvent>(
kind = KIND,
description = PrivateTagsInContent.encryptNip44(privateRelays.map { RelayTag.assemble(it) }.toTypedArray(), signer),
createdAt = createdAt,
) {
alt(ALT)
proxyRelays(publicRelays)
initializer()
}
}
}
@@ -28,6 +28,7 @@ import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.ProxyRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent
import com.vitorpamplona.quartz.nip51Lists.tags.NameTag
@@ -45,6 +46,8 @@ fun TagArrayBuilder<BroadcastRelayListEvent>.broadcastRelays(relays: List<Normal
fun TagArrayBuilder<IndexerRelayListEvent>.indexerRelays(relays: List<NormalizedRelayUrl>) = addAll(relays.map { RelayTag.assemble(it) })
fun TagArrayBuilder<ProxyRelayListEvent>.proxyRelays(relays: List<NormalizedRelayUrl>) = addAll(relays.map { RelayTag.assemble(it) })
fun TagArrayBuilder<PrivateOutboxRelayListEvent>.privateRelays(relays: List<NormalizedRelayUrl>) = addAll(relays.map { RelayTag.assemble(it) })
fun TagArrayBuilder<RelaySetEvent>.relaySet(relays: List<NormalizedRelayUrl>) = addAll(relays.map { RelayTag.assemble(it) })