Replace first hack with a better solution for determining a follow.

Add FollowSetState to MergedFollowListsState and modify it to take into account users from follow sets when displaying a user's follows feed.
This commit is contained in:
KotlinGeekDev
2025-09-26 12:24:42 +01:00
parent 058278956b
commit 9c024e334b
3 changed files with 42 additions and 31 deletions

View File

@@ -20,22 +20,25 @@
*/ */
package com.vitorpamplona.amethyst.model.nip51Lists.followSets package com.vitorpamplona.amethyst.model.nip51Lists.followSets
import androidx.compose.ui.util.fastAny
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.core.value
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class FollowSetState( class FollowSetState(
@@ -44,20 +47,24 @@ class FollowSetState(
val scope: CoroutineScope, val scope: CoroutineScope,
) { ) {
val user = cache.getOrCreateUser(signer.pubKey) val user = cache.getOrCreateUser(signer.pubKey)
private val isActive = MutableStateFlow(false)
suspend fun getFollowSetNotes() = suspend fun getFollowSetNotes() =
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val followSetNotes = LocalCache.getFollowSetNotesFor(user) val followSetNotes = LocalCache.getFollowSetNotesFor(user)
Log.d(this.javaClass.simpleName, "Number of follow sets: ${followSetNotes.size}") Log.d(this@FollowSetState.javaClass.simpleName, "Number of follow sets: ${followSetNotes.size}")
return@withContext followSetNotes return@withContext followSetNotes
} }
private fun getFollowSetNotesFlow() = private fun getFollowSetNotesFlow() =
flow { flow {
val followSetNotes = getFollowSetNotes() while (isActive.value) {
val followSets = followSetNotes.map { mapNoteToFollowSet(it) } val followSetNotes = getFollowSetNotes()
emit(followSets) val followSets = followSetNotes.map { mapNoteToFollowSet(it) }
}.flowOn(Dispatchers.IO) emit(followSets)
delay(1000)
}
}.flowOn(Dispatchers.Default)
val profilesFlow = val profilesFlow =
getFollowSetNotesFlow() getFollowSetNotesFlow()
@@ -65,27 +72,25 @@ class FollowSetState(
it.flatMapTo(mutableSetOf()) { it.profiles }.toSet() it.flatMapTo(mutableSetOf()) { it.profiles }.toSet()
}.stateIn(scope, SharingStarted.Eagerly, emptySet()) }.stateIn(scope, SharingStarted.Eagerly, emptySet())
fun isUserInFollowSets(user: User): Boolean =
runBlocking(scope.coroutineContext) {
LocalCache.getFollowSetNotesFor(user).fastAny { it ->
val listEvent = it.event as PeopleListEvent
val isInPublicSets =
listEvent
.publicPeople()
.fastAny { it.toTagArray().value() == user.pubkeyHex }
val isInPrivateSets =
listEvent
.privatePeople(signer)
?.fastAny { it.toTagArray().value() == user.pubkeyHex } ?: false
isInPublicSets || isInPrivateSets
}
}
fun mapNoteToFollowSet(note: Note): FollowSet = fun mapNoteToFollowSet(note: Note): FollowSet =
FollowSet FollowSet
.mapEventToSet( .mapEventToSet(
event = note.event as PeopleListEvent, event = note.event as PeopleListEvent,
signer, signer,
) )
fun isUserInFollowSets(user: User): Boolean = profilesFlow.value.contains(user.pubkeyHex)
init {
isActive.update { true }
scope.launch(Dispatchers.Default) {
getFollowSetNotesFlow()
.onCompletion {
isActive.update { false }
}.catch {
Log.e(this@FollowSetState.javaClass.simpleName, "Error on flow collection: ${it.message}")
isActive.update { false }
}.collect {}
}
}
} }

View File

@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.model.serverList package com.vitorpamplona.amethyst.model.serverList
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSetState
import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListState import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListState
import com.vitorpamplona.amethyst.model.nip51Lists.hashtagLists.HashtagListState import com.vitorpamplona.amethyst.model.nip51Lists.hashtagLists.HashtagListState
import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState
@@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.stateIn
class MergedFollowListsState( class MergedFollowListsState(
val kind3List: FollowListState, val kind3List: FollowListState,
val followSetList: FollowSetState,
val hashtagList: HashtagListState, val hashtagList: HashtagListState,
val geohashList: GeohashListState, val geohashList: GeohashListState,
val communityList: CommunityListState, val communityList: CommunityListState,
@@ -44,12 +46,13 @@ class MergedFollowListsState(
) { ) {
fun mergeLists( fun mergeLists(
kind3: FollowListState.Kind3Follows, kind3: FollowListState.Kind3Follows,
followSetProfiles: Set<String>,
hashtags: Set<String>, hashtags: Set<String>,
geohashes: Set<String>, geohashes: Set<String>,
community: Set<CommunityTag>, community: Set<CommunityTag>,
): FollowListState.Kind3Follows = ): FollowListState.Kind3Follows =
FollowListState.Kind3Follows( FollowListState.Kind3Follows(
kind3.authors, kind3.authors + followSetProfiles,
kind3.authorsPlusMe, kind3.authorsPlusMe,
kind3.hashtags + hashtags, kind3.hashtags + hashtags,
kind3.geotags + geohashes, kind3.geotags + geohashes,
@@ -59,15 +62,17 @@ class MergedFollowListsState(
val flow: StateFlow<FollowListState.Kind3Follows> = val flow: StateFlow<FollowListState.Kind3Follows> =
combine( combine(
kind3List.flow, kind3List.flow,
followSetList.profilesFlow,
hashtagList.flow, hashtagList.flow,
geohashList.flow, geohashList.flow,
communityList.flow, communityList.flow,
) { kind3, hashtag, geohash, community -> ) { kind3, followSet, hashtag, geohash, community ->
mergeLists(kind3, hashtag, geohash, community) mergeLists(kind3, followSet, hashtag, geohash, community)
}.onStart { }.onStart {
emit( emit(
mergeLists( mergeLists(
kind3List.flow.value, kind3List.flow.value,
followSetList.profilesFlow.value,
hashtagList.flow.value, hashtagList.flow.value,
geohashList.flow.value, geohashList.flow.value,
communityList.flow.value, communityList.flow.value,

View File

@@ -411,6 +411,8 @@ fun observeUserIsFollowing(
): State<Boolean> { ): State<Boolean> {
// Subscribe in the relay for changes in the metadata of this user. // Subscribe in the relay for changes in the metadata of this user.
UserFinderFilterAssemblerSubscription(user1, accountViewModel) UserFinderFilterAssemblerSubscription(user1, accountViewModel)
val isUserInFollowSets = accountViewModel.account.followSetsState.isUserInFollowSets(user2)
println("Is ${user2.toBestDisplayName()} in a Follow set? $isUserInFollowSets")
// Subscribe in the LocalCache for changes that arrive in the device // Subscribe in the LocalCache for changes that arrive in the device
val flow = val flow =
@@ -420,14 +422,13 @@ fun observeUserIsFollowing(
.follows.stateFlow .follows.stateFlow
.sample(1000) .sample(1000)
.mapLatest { userState -> .mapLatest { userState ->
userState.user.isFollowing(user2) || userState.user.isFollowing(user2) || isUserInFollowSets
accountViewModel.account.isUserInFollowSets(user2)
}.distinctUntilChanged() }.distinctUntilChanged()
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
} }
return flow.collectAsStateWithLifecycle( return flow.collectAsStateWithLifecycle(
user1.isFollowing(user2) || accountViewModel.account.isUserInFollowSets(user2), user1.isFollowing(user2) || isUserInFollowSets,
) )
} }