From ee80361478e12d30d811afccfd1910eb25fd3134 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 22 Oct 2025 18:31:00 -0400 Subject: [PATCH] - Removes the deprecated hacks from Contact Lists - Removes the use of kind 3 to show hashtags which fixes issues when people unfollowing the hashtag and it would just affect the InterestListEvent --- .../vitorpamplona/amethyst/model/Account.kt | 4 +- .../DeclaredFollowsPerOutboxRelay.kt | 2 +- .../FollowListOutboxOrProxyRelays.kt | 2 +- .../FollowListReusedOutboxOrProxyRelays.kt | 2 +- .../nip02FollowLists/FollowsPerOutboxRelay.kt | 2 +- ...owListState.kt => Kind3FollowListState.kt} | 35 +++---------- .../serverList/MergedFollowListsState.kt | 51 +++++++++++++------ .../topNavFeeds/FeedTopNavFilterState.kt | 4 +- .../allFollows/AllFollowsFeedFlow.kt | 24 ++++----- .../allUserFollows/AllUserFollowsFeedFlow.kt | 12 ++--- .../nip02FollowList/ContactListEvent.kt | 17 ------- 11 files changed, 69 insertions(+), 86 deletions(-) rename amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/{FollowListState.kt => Kind3FollowListState.kt} (82%) 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 fce48286a..334949ff1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -38,8 +38,8 @@ 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 import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowsPerOutboxRelay +import com.vitorpamplona.amethyst.model.nip02FollowLists.Kind3FollowListState import com.vitorpamplona.amethyst.model.nip03Timestamp.OtsState import com.vitorpamplona.amethyst.model.nip17Dms.DmInboxRelayState import com.vitorpamplona.amethyst.model.nip17Dms.DmRelayListState @@ -266,7 +266,7 @@ class Account( val blockedRelayListDecryptionCache = BlockedRelayListDecryptionCache(signer) val blockedRelayList = BlockedRelayListState(signer, cache, blockedRelayListDecryptionCache, scope, settings) - val kind3FollowList = FollowListState(signer, cache, scope, settings) + val kind3FollowList = Kind3FollowListState(signer, cache, scope, settings) val followSetsState = FollowSetState(signer, cache, scope) val ephemeralChatListDecryptionCache = EphemeralChatListDecryptionCache(signer) 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 index 5bf15c92f..f2934b113 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/DeclaredFollowsPerOutboxRelay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/DeclaredFollowsPerOutboxRelay.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest class DeclaredFollowsPerOutboxRelay( - kind3Follows: FollowListState, + kind3Follows: Kind3FollowListState, val cache: LocalCache, scope: CoroutineScope, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt index ed08e8bb8..13d29c44f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt @@ -42,7 +42,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest class FollowListOutboxOrProxyRelays( - kind3Follows: FollowListState, + kind3Follows: Kind3FollowListState, blockedRelayList: BlockedRelayListState, proxyRelayList: ProxyRelayListState, val cache: LocalCache, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListReusedOutboxOrProxyRelays.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListReusedOutboxOrProxyRelays.kt index f356cf779..2f5751936 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListReusedOutboxOrProxyRelays.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListReusedOutboxOrProxyRelays.kt @@ -42,7 +42,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest class FollowListReusedOutboxOrProxyRelays( - kind3Follows: FollowListState, + kind3Follows: Kind3FollowListState, blockedRelayList: BlockedRelayListState, proxyRelayList: ProxyRelayListState, val cache: LocalCache, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt index a98e5fe7d..921c8d353 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt @@ -42,7 +42,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest class FollowsPerOutboxRelay( - kind3Follows: FollowListState, + kind3Follows: Kind3FollowListState, blockedRelayList: BlockedRelayListState, proxyRelayList: ProxyRelayListState, val cache: LocalCache, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/Kind3FollowListState.kt similarity index 82% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListState.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/Kind3FollowListState.kt index 8fb2d857e..46f8a0cba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/Kind3FollowListState.kt @@ -25,12 +25,10 @@ import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner -import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag -import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId -import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -45,7 +43,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch -class FollowListState( +class Kind3FollowListState( val signer: NostrSigner, val cache: LocalCache, val scope: CoroutineScope, @@ -77,8 +75,8 @@ class FollowListState( // Creates a long-term reference for all follows of a user val userList = flow - .map { - it.authors.mapNotNull { + .map { kind3Follows -> + kind3Follows.authors.mapNotNull { cache.checkGetOrCreateUser(it) } }.flowOn(Dispatchers.IO) @@ -96,15 +94,9 @@ class FollowListState( */ @Immutable class Kind3Follows( - val authors: Set = emptySet(), - val authorsPlusMe: Set, - val hashtags: Set = emptySet(), - val geotags: Set = emptySet(), - val communities: Set = emptySet(), - ) { - val geotagScopes: Set = geotags.mapTo(mutableSetOf()) { GeohashId.toScope(it) } - val hashtagScopes: Set = hashtags.mapTo(mutableSetOf()) { HashtagId.toScope(it) } - } + val authors: Set = emptySet(), + val authorsPlusMe: Set, + ) fun buildKind3Follows(latestContactList: ContactListEvent?): Kind3Follows { // makes sure the output include only valid p tags @@ -113,19 +105,6 @@ class FollowListState( return Kind3Follows( authors = verifiedFollowingUsers, authorsPlusMe = verifiedFollowingUsers + signer.pubKey, - hashtags = - latestContactList - ?.unverifiedFollowTagSet() - ?.map { it.lowercase() } - ?.toSet() ?: emptySet(), - geotags = - latestContactList - ?.geohashes() - ?.toSet() ?: emptySet(), - communities = - latestContactList - ?.verifiedFollowAddressSet() - ?.toSet() ?: emptySet(), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowListsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowListsState.kt index 81f4e7d6f..c794eb0bb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowListsState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowListsState.kt @@ -20,12 +20,15 @@ */ package com.vitorpamplona.amethyst.model.serverList -import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState +import androidx.compose.runtime.Immutable +import com.vitorpamplona.amethyst.model.nip02FollowLists.Kind3FollowListState import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSetState import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListState import com.vitorpamplona.amethyst.model.nip51Lists.hashtagLists.HashtagListState import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState import com.vitorpamplona.quartz.nip72ModCommunities.follow.tags.CommunityTag +import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId +import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted @@ -37,38 +40,50 @@ import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.stateIn class MergedFollowListsState( - val kind3List: FollowListState, + val kind3List: Kind3FollowListState, val followSetList: FollowSetState, val hashtagList: HashtagListState, val geohashList: GeohashListState, val communityList: CommunityListState, val scope: CoroutineScope, ) { + /** + This contains a big OR of everything the user wants to see in the a single feed. + */ + @Immutable + class AllFollows( + val authors: Set = emptySet(), + val hashtags: Set = emptySet(), + val geotags: Set = emptySet(), + val communities: Set = emptySet(), + ) { + val geotagScopes: Set = geotags.mapTo(mutableSetOf()) { GeohashId.toScope(it) } + val hashtagScopes: Set = hashtags.mapTo(mutableSetOf()) { HashtagId.toScope(it) } + } + fun mergeLists( - kind3: FollowListState.Kind3Follows, + kind3: Kind3FollowListState.Kind3Follows, followSetProfiles: Set, hashtags: Set, geohashes: Set, community: Set, - ): FollowListState.Kind3Follows = - FollowListState.Kind3Follows( - kind3.authors + followSetProfiles, - kind3.authorsPlusMe, - kind3.hashtags + hashtags, - kind3.geotags + geohashes, - kind3.communities + community.map { it.address.toValue() }, + ): AllFollows = + AllFollows( + authors = kind3.authors + followSetProfiles, + hashtags = hashtags, + geotags = geohashes, + communities = community.mapTo(mutableSetOf()) { it.address.toValue() }, ) - val flow: StateFlow = + val flow: StateFlow = combine( kind3List.flow, followSetList.profilesFlow, hashtagList.flow, geohashList.flow, communityList.flow, - ) { kind3, followSet, hashtag, geohash, community -> - mergeLists(kind3, followSet, hashtag, geohash, community) - }.onStart { + ::mergeLists, + ).onStart { emit( mergeLists( kind3List.flow.value, @@ -83,6 +98,12 @@ class MergedFollowListsState( .stateIn( scope, SharingStarted.Eagerly, - kind3List.flow.value, + mergeLists( + kind3List.flow.value, + followSetList.profilesFlow.value, + hashtagList.flow.value, + geohashList.flow.value, + communityList.flow.value, + ), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt index cd6267e09..89db92684 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/FeedTopNavFilterState.kt @@ -25,7 +25,7 @@ import com.vitorpamplona.amethyst.model.ALL_USER_FOLLOWS import com.vitorpamplona.amethyst.model.AROUND_ME import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState +import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.AllUserFollowsFeedFlow import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow @@ -49,7 +49,7 @@ import kotlinx.coroutines.flow.transformLatest class FeedTopNavFilterState( val feedFilterListName: MutableStateFlow, - val allFollows: StateFlow, + val allFollows: StateFlow, val locationFlow: StateFlow, val followsRelays: StateFlow>, val blockedRelays: StateFlow>, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsFeedFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsFeedFlow.kt index f233cea57..e451c3129 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsFeedFlow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsFeedFlow.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.model.topNavFeeds.allFollows -import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState +import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedFlowsType import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -29,31 +29,31 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine class AllFollowsFeedFlow( - val allFollows: StateFlow, + val allFollows: StateFlow, val followsRelays: StateFlow>, val blockedRelays: StateFlow>, val proxyRelays: StateFlow>, ) : IFeedFlowsType { fun convert( - kind3: FollowListState.Kind3Follows?, + allFollows: MergedFollowListsState.AllFollows?, proxyRelays: Set, ): IFeedTopNavFilter = - if (kind3 != null) { + if (allFollows != null) { if (proxyRelays.isEmpty()) { AllFollowsByOutboxTopNavFilter( - authors = kind3.authors, - hashtags = kind3.hashtags, - geotags = kind3.geotags, - communities = kind3.communities, + authors = allFollows.authors, + hashtags = allFollows.hashtags, + geotags = allFollows.geotags, + communities = allFollows.communities, defaultRelays = followsRelays, blockedRelays = blockedRelays, ) } else { AllFollowsByProxyTopNavFilter( - authors = kind3.authors, - hashtags = kind3.hashtags, - geotags = kind3.geotags, - communities = kind3.communities, + authors = allFollows.authors, + hashtags = allFollows.hashtags, + geotags = allFollows.geotags, + communities = allFollows.communities, proxyRelays = proxyRelays, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/AllUserFollowsFeedFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/AllUserFollowsFeedFlow.kt index 9ad834025..24a26e3f7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/AllUserFollowsFeedFlow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allUserFollows/AllUserFollowsFeedFlow.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows -import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState +import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedFlowsType import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -29,25 +29,25 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine class AllUserFollowsFeedFlow( - val allFollows: StateFlow, + val allFollows: StateFlow, val followsRelays: StateFlow>, val blockedRelays: StateFlow>, val proxyRelays: StateFlow>, ) : IFeedFlowsType { fun convert( - kind3: FollowListState.Kind3Follows?, + allFollows: MergedFollowListsState.AllFollows?, proxyRelays: Set, ): IFeedTopNavFilter = - if (kind3 != null) { + if (allFollows != null) { if (proxyRelays.isEmpty()) { AllUserFollowsByOutboxTopNavFilter( - authors = kind3.authors, + authors = allFollows.authors, defaultRelays = followsRelays, blockedRelays = blockedRelays, ) } else { AllUserFollowsByProxyTopNavFilter( - authors = kind3.authors, + authors = allFollows.authors, proxyRelays = proxyRelays, ) } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt index 5fd4e5d0a..e25e3011a 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt @@ -23,14 +23,11 @@ package com.vitorpamplona.quartz.nip02FollowList import androidx.compose.runtime.Stable import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync -import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag -import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag import com.vitorpamplona.quartz.nip31Alts.AltTag @@ -45,12 +42,7 @@ class ContactListEvent( content: String, sig: HexKey, ) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig), - AddressHintProvider, PubKeyHintProvider { - override fun addressHints() = tags.mapNotNull(ATag::parseAsHint) - - override fun linkedAddressIds() = tags.mapNotNull(ATag::parseAddressId) - override fun pubKeyHints() = tags.mapNotNull(ContactTag::parseAsHint) override fun linkedPubKeys() = tags.mapNotNull(ContactTag::parseKey) @@ -60,17 +52,8 @@ class ContactListEvent( */ fun verifiedFollowKeySet(): Set = tags.mapNotNullTo(mutableSetOf(), ContactTag::parseValidKey) - /** - * Returns a list of a-tags that are verified as correct. - */ - @Deprecated("Use CommunityListEvent instead.") - fun verifiedFollowAddressSet(): Set = tags.mapNotNullTo(mutableSetOf(), ATag::parseValidAddress) - fun unverifiedFollowKeySet() = tags.mapNotNull(ContactTag::parseKey) - @Deprecated("Use HashtagListEvent instead.") - fun unverifiedFollowTagSet() = tags.hashtags() - fun follows() = tags.mapNotNull(ContactTag::parseValid) fun relays(): Map? {