From 382319addf2053f400c09f4b594f20388a92d759 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 20 Oct 2025 20:21:18 -0400 Subject: [PATCH] Adds a declared follows state --- .../vitorpamplona/amethyst/model/Account.kt | 4 ++ .../DeclaredFollowsPerOutboxRelay.kt | 69 +++++++++++++++++++ .../connected/ConnectedRelayListViewModel.kt | 2 +- 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/DeclaredFollowsPerOutboxRelay.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 851b58367..dfc9fd2a3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountHomeRelayState 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.DeclaredFollowsPerOutboxRelay import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxOrProxyRelays import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListReusedOutboxOrProxyRelays import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState @@ -317,6 +318,9 @@ class Account( val followPlusAllMineWithSearch = MergedFollowPlusMineWithSearchRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, searchRelayList, scope) val defaultGlobalRelays = MergedFollowPlusMineRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, scope) + // keeps a cache of the declared outbox relays for each author + val declaredFollowsPerRelay = DeclaredFollowsPerOutboxRelay(kind3FollowList, cache, scope).flow + // keeps a cache of the outbox relays for each author val followsPerRelay = FollowsPerOutboxRelay(kind3FollowList, blockedRelayList, proxyRelayList, cache, scope).flow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/DeclaredFollowsPerOutboxRelay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/DeclaredFollowsPerOutboxRelay.kt new file mode 100644 index 000000000..ac2fb3eef --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/DeclaredFollowsPerOutboxRelay.kt @@ -0,0 +1,69 @@ +/** + * 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.nip02FollowLists + +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState +import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState +import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +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 DeclaredFollowsPerOutboxRelay( + kind3Follows: FollowListState, + val cache: LocalCache, + scope: CoroutineScope, +) { + val calculator = OutboxRelayLoader(true) + + @OptIn(ExperimentalCoroutinesApi::class) + val flow: StateFlow>> = + kind3Follows.flow + .transformLatest { + emitAll( + calculator.toAuthorsPerRelayFlow(it.authors, cache) { it }, + ) + }.onStart { + emit( + calculator.authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { it }, + ) + }.distinctUntilChanged() + .flowOn(Dispatchers.Default) + .stateIn( + scope, + SharingStarted.Eagerly, + emptyMap(), + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt index 65bd3f89a..0479378fb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt @@ -39,7 +39,7 @@ class ConnectedRelayListViewModel : BasicRelaySetupInfoModel() { Amethyst.instance.torEvaluatorFlow.flow.value .useTor(it), users = - account.followsPerRelay.value[it]?.mapNotNull { hex -> + account.declaredFollowsPerRelay.value[it]?.mapNotNull { hex -> LocalCache.checkGetOrCreateUser(hex) } ?: emptyList(), )