diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt index a9b79bb71..e45562fe9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt @@ -60,7 +60,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.blocked.BlockedRelay import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.blocked.renderBlockedItems import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.broadcast.BroadcastRelayListViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.broadcast.renderBroadcastItems -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayEventCountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayExporter import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayListCollection import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayZipExporter @@ -110,13 +109,6 @@ fun AllRelayListScreen( val indexerViewModel: IndexerRelayListViewModel = viewModel() val proxyViewModel: ProxyRelayListViewModel = viewModel() val relayFeedsViewModel: RelayFeedsListViewModel = viewModel() - val outboxCountViewModel: RelayEventCountViewModel = viewModel(key = "outboxCount") - val inboxCountViewModel: RelayEventCountViewModel = viewModel(key = "inboxCount") - val dmCountViewModel: RelayEventCountViewModel = viewModel(key = "dmCount") - val privateHomeCountViewModel: RelayEventCountViewModel = viewModel(key = "privateHomeCount") - val proxyCountViewModel: RelayEventCountViewModel = viewModel(key = "proxyCount") - val indexerCountViewModel: RelayEventCountViewModel = viewModel(key = "indexerCount") - val searchCountViewModel: RelayEventCountViewModel = viewModel(key = "searchCount") dmViewModel.init(accountViewModel) nip65ViewModel.init(accountViewModel) @@ -159,13 +151,6 @@ fun AllRelayListScreen( indexerViewModel, proxyViewModel, relayFeedsViewModel, - outboxCountViewModel, - inboxCountViewModel, - dmCountViewModel, - privateHomeCountViewModel, - proxyCountViewModel, - indexerCountViewModel, - searchCountViewModel, accountViewModel, nav, ) @@ -186,13 +171,6 @@ fun MappedAllRelayListView( indexerViewModel: IndexerRelayListViewModel, proxyViewModel: ProxyRelayListViewModel, relayFeedsViewModel: RelayFeedsListViewModel, - outboxCountViewModel: RelayEventCountViewModel, - inboxCountViewModel: RelayEventCountViewModel, - dmCountViewModel: RelayEventCountViewModel, - privateHomeCountViewModel: RelayEventCountViewModel, - proxyCountViewModel: RelayEventCountViewModel, - indexerCountViewModel: RelayEventCountViewModel, - searchCountViewModel: RelayEventCountViewModel, accountViewModel: AccountViewModel, nav: INav, ) { @@ -210,71 +188,13 @@ fun MappedAllRelayListView( val proxyRelays by proxyViewModel.relays.collectAsStateWithLifecycle() val relayFeedsFeedState by relayFeedsViewModel.relays.collectAsStateWithLifecycle() - val outboxCounts by outboxCountViewModel.counts.collectAsStateWithLifecycle() - val inboxCounts by inboxCountViewModel.counts.collectAsStateWithLifecycle() - val dmCounts by dmCountViewModel.counts.collectAsStateWithLifecycle() - val privateHomeCounts by privateHomeCountViewModel.counts.collectAsStateWithLifecycle() - val proxyCounts by proxyCountViewModel.counts.collectAsStateWithLifecycle() - val indexerCounts by indexerCountViewModel.counts.collectAsStateWithLifecycle() - val searchCounts by searchCountViewModel.counts.collectAsStateWithLifecycle() - - val userPubKey = accountViewModel.account.pubKey - - LaunchedEffect(homeFeedState) { - if (homeFeedState.isNotEmpty()) { - outboxCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.authorCountFilters(userPubKey, homeFeedState.map { it.relay }), - ) - } - } - - LaunchedEffect(notifFeedState) { - if (notifFeedState.isNotEmpty()) { - inboxCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.pTagCountFilters(userPubKey, notifFeedState.map { it.relay }), - ) - } - } - - LaunchedEffect(dmFeedState) { - if (dmFeedState.isNotEmpty()) { - dmCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.dmCountFilters(userPubKey, dmFeedState.map { it.relay }), - ) - } - } - - LaunchedEffect(privateOutboxFeedState) { - if (privateOutboxFeedState.isNotEmpty()) { - privateHomeCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.authorCountFilters(userPubKey, privateOutboxFeedState.map { it.relay }), - ) - } - } - - LaunchedEffect(proxyRelays) { - if (proxyRelays.isNotEmpty()) { - proxyCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.totalCountFilters(proxyRelays.map { it.relay }), - ) - } - } - - LaunchedEffect(indexerRelays) { - if (indexerRelays.isNotEmpty()) { - indexerCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.indexerCountFilters(indexerRelays.map { it.relay }), - ) - } - } - - LaunchedEffect(searchFeedState) { - if (searchFeedState.isNotEmpty()) { - searchCountViewModel.queryCountsForRelays( - RelayEventCountViewModel.totalCountFilters(searchFeedState.map { it.relay }), - ) - } - } + val outboxCounts by nip65ViewModel.homeCountResults.collectAsStateWithLifecycle() + val inboxCounts by nip65ViewModel.notifCountResults.collectAsStateWithLifecycle() + val dmCounts by dmViewModel.countResults.collectAsStateWithLifecycle() + val privateHomeCounts by privateOutboxViewModel.countResults.collectAsStateWithLifecycle() + val proxyCounts by proxyViewModel.countResults.collectAsStateWithLifecycle() + val indexerCounts by indexerViewModel.countResults.collectAsStateWithLifecycle() + val searchCounts by searchViewModel.countResults.collectAsStateWithLifecycle() Scaffold( topBar = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt index 758c39646..466cc1682 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt @@ -26,6 +26,11 @@ import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.replace import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener +import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient +import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountMessage +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -33,13 +38,19 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -abstract class BasicRelaySetupInfoModel : ViewModel() { +abstract class BasicRelaySetupInfoModel : ViewModel(), IRelayClientListener { lateinit var accountViewModel: AccountViewModel lateinit var account: Account private val _relays = MutableStateFlow>(emptyList()) val relays = _relays.asStateFlow() + private val _countResults = MutableStateFlow>(emptyMap()) + val countResults = _countResults.asStateFlow() + + private val subIdToRelay = mutableMapOf>() + private val relayQueryInfos = mutableMapOf>() + var hasModified = false fun init(accountViewModel: AccountViewModel) { @@ -50,12 +61,15 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { fun load() { clear() loadRelayDocuments() + loadCounts() } abstract fun getRelayList(): List? abstract suspend fun saveRelayList(urlList: List) + open fun countFilters(relayUrl: NormalizedRelayUrl): List = emptyList() + fun create() { if (hasModified) { accountViewModel.launchSigner { @@ -79,6 +93,83 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { } } + private fun loadCounts() { + val client = Amethyst.instance.client + cleanupCounts() + + val relayList = _relays.value + if (relayList.isEmpty()) return + + val hasFilters = relayList.any { countFilters(it.relay).isNotEmpty() } + if (!hasFilters) return + + client.subscribe(this) + + relayList.forEach { item -> + val filters = countFilters(item.relay) + if (filters.isEmpty()) return@forEach + + val queryInfos = mutableListOf() + + filters.forEachIndexed { index, countFilter -> + val subId = newSubId() + subIdToRelay[subId] = Pair(item.relay, index) + queryInfos.add(CountQueryInfo(subId, countFilter.label, index)) + + client.queryCount( + subId = subId, + filters = mapOf(item.relay to listOf(countFilter.filter)), + ) + } + + relayQueryInfos[item.relay] = queryInfos + } + } + + override fun onIncomingMessage( + relay: IRelayClient, + msgStr: String, + msg: Message, + ) { + if (msg is CountMessage) { + val (relayUrl, filterIndex) = subIdToRelay[msg.queryId] ?: return + val queryInfos = relayQueryInfos[relayUrl] ?: return + val queryInfo = queryInfos.find { it.filterIndex == filterIndex } ?: return + + _countResults.update { currentMap -> + val currentResult = currentMap[relayUrl] ?: RelayCountResult() + val updatedEntries = currentResult.counts.toMutableList() + + val existingIndex = updatedEntries.indexOfFirst { it.label == queryInfo.label } + val newEntry = + RelayCountResult.CountEntry( + label = queryInfo.label, + count = msg.result.count, + approximate = msg.result.approximate, + ) + + if (existingIndex >= 0) { + updatedEntries[existingIndex] = newEntry + } else { + updatedEntries.add(newEntry) + } + + currentMap + (relayUrl to RelayCountResult(updatedEntries)) + } + } + } + + private fun cleanupCounts() { + val client = Amethyst.instance.client + subIdToRelay.keys.forEach { subId -> + client.close(subId) + } + subIdToRelay.clear() + relayQueryInfos.clear() + _countResults.value = emptyMap() + client.unsubscribe(this) + } + open fun relayListBuilder(): List { val relayList = getRelayList() ?: emptyList() @@ -122,4 +213,15 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { ) { _relays.update { it.replace(relay, relay.copy(paidRelay = paid)) } } + + override fun onCleared() { + cleanupCounts() + super.onCleared() + } + + private data class CountQueryInfo( + val subId: String, + val label: String, + val filterIndex: Int, + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayEventCountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayEventCountViewModel.kt index 5529c2d92..6f77913ff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayEventCountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayEventCountViewModel.kt @@ -21,21 +21,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.compose.runtime.Immutable -import androidx.lifecycle.ViewModel -import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener -import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient -import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId -import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountMessage -import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent -import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update @Immutable data class RelayCountResult( @@ -49,165 +35,6 @@ data class RelayCountResult( ) } -class RelayEventCountViewModel : ViewModel(), IRelayClientListener { - private val client: INostrClient get() = Amethyst.instance.client - - private val _counts = MutableStateFlow>(emptyMap()) - val counts = _counts.asStateFlow() - - // Maps subId -> (relay, filterIndex) so we can route count results - private val subIdToRelay = mutableMapOf>() - // Maps relay -> list of (subId, label) to track active queries - private val relayQueries = mutableMapOf>() - - private data class QueryInfo( - val subId: String, - val label: String, - val filterIndex: Int, - ) - - fun queryCountsForRelays(queries: Map>) { - cleanup() - client.subscribe(this) - - queries.forEach { (relay, filters) -> - val queryInfos = mutableListOf() - - filters.forEachIndexed { index, countFilter -> - val subId = newSubId() - subIdToRelay[subId] = Pair(relay, index) - queryInfos.add(QueryInfo(subId, countFilter.label, index)) - - client.queryCount( - subId = subId, - filters = mapOf(relay to listOf(countFilter.filter)), - ) - } - - relayQueries[relay] = queryInfos - } - } - - override fun onIncomingMessage( - relay: IRelayClient, - msgStr: String, - msg: Message, - ) { - if (msg is CountMessage) { - val (relayUrl, filterIndex) = subIdToRelay[msg.queryId] ?: return - val queryInfos = relayQueries[relayUrl] ?: return - val queryInfo = queryInfos.find { it.filterIndex == filterIndex } ?: return - - _counts.update { currentMap -> - val currentResult = currentMap[relayUrl] ?: RelayCountResult() - val updatedEntries = currentResult.counts.toMutableList() - - // Replace or add the entry for this filter - val existingIndex = updatedEntries.indexOfFirst { it.label == queryInfo.label } - val newEntry = - RelayCountResult.CountEntry( - label = queryInfo.label, - count = msg.result.count, - approximate = msg.result.approximate, - ) - - if (existingIndex >= 0) { - updatedEntries[existingIndex] = newEntry - } else { - updatedEntries.add(newEntry) - } - - currentMap + (relayUrl to RelayCountResult(updatedEntries)) - } - } - } - - private fun cleanup() { - // Close existing queries - subIdToRelay.keys.forEach { subId -> - client.close(subId) - } - subIdToRelay.clear() - relayQueries.clear() - _counts.value = emptyMap() - client.unsubscribe(this) - } - - override fun onCleared() { - cleanup() - super.onCleared() - } - - companion object { - fun authorCountFilters( - userPubKey: HexKey, - relays: List, - ): Map> = - relays.associateWith { - listOf( - CountFilter( - label = "events", - filter = Filter(authors = listOf(userPubKey)), - ), - ) - } - - fun pTagCountFilters( - userPubKey: HexKey, - relays: List, - ): Map> = - relays.associateWith { - listOf( - CountFilter( - label = "events", - filter = Filter(tags = mapOf("p" to listOf(userPubKey))), - ), - ) - } - - fun dmCountFilters( - userPubKey: HexKey, - relays: List, - ): Map> = - relays.associateWith { - listOf( - CountFilter( - label = "events", - filter = - Filter( - kinds = listOf(GiftWrapEvent.KIND, PrivateDmEvent.KIND), - tags = mapOf("p" to listOf(userPubKey)), - ), - ), - ) - } - - fun totalCountFilters(relays: List): Map> = - relays.associateWith { - listOf( - CountFilter( - label = "events", - filter = Filter(kinds = null), - ), - ) - } - - fun indexerCountFilters(relays: List): Map> = - relays.associateWith { - listOf( - CountFilter( - label = "kind 0", - filter = Filter(kinds = listOf(0)), - ), - CountFilter( - label = "kind 10002", - filter = Filter(kinds = listOf(10002)), - ), - ) - } - } -} - data class CountFilter( val label: String, val filter: Filter, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt index d08612a48..a78df4519 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt @@ -22,7 +22,11 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.CountFilter +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent +import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent @Stable class DMRelayListViewModel : BasicRelaySetupInfoModel() { @@ -31,4 +35,16 @@ class DMRelayListViewModel : BasicRelaySetupInfoModel() { override suspend fun saveRelayList(urlList: List) { account.saveDMRelayList(urlList) } + + override fun countFilters(relayUrl: NormalizedRelayUrl): List = + listOf( + CountFilter( + label = "events", + filter = + Filter( + kinds = listOf(GiftWrapEvent.KIND, PrivateDmEvent.KIND), + tags = mapOf("p" to listOf(account.pubKey)), + ), + ), + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt index 837966624..c875c5a63 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.indexer import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.CountFilter +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Stable @@ -33,4 +35,16 @@ class IndexerRelayListViewModel : BasicRelaySetupInfoModel() { override suspend fun saveRelayList(urlList: List) { account.saveIndexerRelayList(urlList) } + + override fun countFilters(relayUrl: NormalizedRelayUrl): List = + listOf( + CountFilter( + label = "kind 0", + filter = Filter(kinds = listOf(0)), + ), + CountFilter( + label = "kind 10002", + filter = Filter(kinds = listOf(10002)), + ), + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt index a3e5f9f41..5cd3e96ff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip37 import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.CountFilter +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Stable @@ -33,4 +35,12 @@ class PrivateOutboxRelayListViewModel : BasicRelaySetupInfoModel() { override suspend fun saveRelayList(urlList: List) { account.savePrivateOutboxRelayList(urlList) } + + override fun countFilters(relayUrl: NormalizedRelayUrl): List = + listOf( + CountFilter( + label = "events", + filter = Filter(authors = listOf(account.pubKey)), + ), + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt index 134f7425c..fa1427b89 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt @@ -28,7 +28,15 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.replace 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.RelayCountResult import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder +import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener +import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient +import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountMessage +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayInfo import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType import kotlinx.coroutines.Dispatchers @@ -38,7 +46,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @Stable -class Nip65RelayListViewModel : ViewModel() { +class Nip65RelayListViewModel : ViewModel(), IRelayClientListener { private lateinit var accountViewModel: AccountViewModel private lateinit var account: Account @@ -48,6 +56,14 @@ class Nip65RelayListViewModel : ViewModel() { private val _notificationRelays = MutableStateFlow>(emptyList()) val notificationRelays = _notificationRelays.asStateFlow() + private val _homeCountResults = MutableStateFlow>(emptyMap()) + val homeCountResults = _homeCountResults.asStateFlow() + + private val _notifCountResults = MutableStateFlow>(emptyMap()) + val notifCountResults = _notifCountResults.asStateFlow() + + private val subIdToRelay = mutableMapOf>() + var hasModified = false fun init(accountViewModel: AccountViewModel) { @@ -58,6 +74,7 @@ class Nip65RelayListViewModel : ViewModel() { fun load() { clear() loadRelayDocuments() + loadCounts() } fun create() { @@ -111,6 +128,74 @@ class Nip65RelayListViewModel : ViewModel() { } } + private fun loadCounts() { + val client = Amethyst.instance.client + cleanupCounts() + + val homeList = _homeRelays.value + val notifList = _notificationRelays.value + + if (homeList.isEmpty() && notifList.isEmpty()) return + + client.subscribe(this) + + homeList.forEach { item -> + val subId = newSubId() + subIdToRelay[subId] = Pair(item.relay, true) + client.queryCount( + subId = subId, + filters = mapOf(item.relay to listOf(Filter(authors = listOf(account.pubKey)))), + ) + } + + notifList.forEach { item -> + val subId = newSubId() + subIdToRelay[subId] = Pair(item.relay, false) + client.queryCount( + subId = subId, + filters = mapOf(item.relay to listOf(Filter(tags = mapOf("p" to listOf(account.pubKey))))), + ) + } + } + + override fun onIncomingMessage( + relay: IRelayClient, + msgStr: String, + msg: Message, + ) { + if (msg is CountMessage) { + val (relayUrl, isHome) = subIdToRelay[msg.queryId] ?: return + + val newResult = + RelayCountResult( + listOf( + RelayCountResult.CountEntry( + label = "events", + count = msg.result.count, + approximate = msg.result.approximate, + ), + ), + ) + + if (isHome) { + _homeCountResults.update { it + (relayUrl to newResult) } + } else { + _notifCountResults.update { it + (relayUrl to newResult) } + } + } + } + + private fun cleanupCounts() { + val client = Amethyst.instance.client + subIdToRelay.keys.forEach { subId -> + client.close(subId) + } + subIdToRelay.clear() + _homeCountResults.value = emptyMap() + _notifCountResults.value = emptyMap() + client.unsubscribe(this) + } + fun clear() { hasModified = false _homeRelays.update { @@ -181,4 +266,9 @@ class Nip65RelayListViewModel : ViewModel() { ) { _notificationRelays.update { it.replace(relay, relay.copy(paidRelay = paid)) } } + + override fun onCleared() { + cleanupCounts() + super.onCleared() + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListViewModel.kt index e2a0f1359..01d07c40b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListViewModel.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.proxy import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.CountFilter +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Stable @@ -33,4 +35,12 @@ class ProxyRelayListViewModel : BasicRelaySetupInfoModel() { override suspend fun saveRelayList(urlList: List) { account.saveProxyRelayList(urlList) } + + override fun countFilters(relayUrl: NormalizedRelayUrl): List = + listOf( + CountFilter( + label = "events", + filter = Filter(), + ), + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt index e2e3ba0aa..aaf592a68 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.CountFilter +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Stable @@ -33,4 +35,12 @@ class SearchRelayListViewModel : BasicRelaySetupInfoModel() { override suspend fun saveRelayList(urlList: List) { account.saveSearchRelayList(urlList) } + + override fun countFilters(relayUrl: NormalizedRelayUrl): List = + listOf( + CountFilter( + label = "events", + filter = Filter(), + ), + ) }