From 764c2241cae17c65939de0395f064fbe1a422153 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 15:02:09 +0000 Subject: [PATCH] refactor: integrate relay event counts into each ViewModel Move count query logic from separate RelayEventCountViewModel into each relay list's own ViewModel. BasicRelaySetupInfoModel now manages counts via countFilters() override. Nip65RelayListViewModel gets its own count infrastructure for home (outbox) and notification (inbox) relay lists. Simplify AllRelayListScreen by removing 7 extra count ViewModels and their LaunchedEffects. https://claude.ai/code/session_016158D5mq5BygS1uBbLNbsA --- .../loggedIn/relays/AllRelayListScreen.kt | 94 +--------- .../relays/common/BasicRelaySetupInfoModel.kt | 104 ++++++++++- .../relays/common/RelayEventCountViewModel.kt | 173 ------------------ .../relays/dm/DMRelayListViewModel.kt | 16 ++ .../indexer/IndexerRelayListViewModel.kt | 14 ++ .../nip37/PrivateOutboxRelayListViewModel.kt | 10 + .../relays/nip65/Nip65RelayListViewModel.kt | 92 +++++++++- .../relays/proxy/ProxyRelayListViewModel.kt | 10 + .../relays/search/SearchRelayListViewModel.kt | 10 + 9 files changed, 261 insertions(+), 262 deletions(-) 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(), + ), + ) }