Moves the ContactList cache lists to AccountViewModel, where it can be disposed more efficiently.

This commit is contained in:
Vitor Pamplona
2024-08-06 15:36:04 -04:00
parent e5328d7975
commit 5fdff97cf8
11 changed files with 74 additions and 100 deletions

View File

@@ -225,6 +225,7 @@ class Account(
@Immutable
class LiveFollowLists(
val users: Set<String> = emptySet(),
val usersPlusMe: Set<String>,
val hashtags: Set<String> = emptySet(),
val geotags: Set<String> = emptySet(),
val communities: Set<String> = emptySet(),
@@ -423,17 +424,29 @@ class Account(
val liveKind3FollowsFlow: Flow<LiveFollowLists> =
userProfile().flow().follows.stateFlow.transformLatest {
checkNotInMainThread()
// makes sure the output include only valid p tags
val verifiedFollowingUsers = it.user.latestContactList?.verifiedFollowKeySet() ?: emptySet()
emit(
LiveFollowLists(
it.user.cachedFollowingKeySet(),
it.user.cachedFollowingTagSet(),
it.user.cachedFollowingGeohashSet(),
it.user.cachedFollowingCommunitiesSet(),
verifiedFollowingUsers,
verifiedFollowingUsers + keyPair.pubKeyHex,
it.user.latestContactList
?.unverifiedFollowTagSet()
?.map { it.lowercase() }
?.toSet() ?: emptySet(),
it.user.latestContactList
?.unverifiedFollowGeohashSet()
?.toSet() ?: emptySet(),
it.user.latestContactList
?.verifiedFollowAddressSet()
?.toSet() ?: emptySet(),
),
)
}
val liveKind3Follows = liveKind3FollowsFlow.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
val liveKind3Follows = liveKind3FollowsFlow.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
@OptIn(ExperimentalCoroutinesApi::class)
private val liveHomeList: Flow<ListNameNotePair> by lazy {
@@ -465,11 +478,11 @@ class Account(
} else if (peopleListFollows.listName == KIND3_FOLLOWS) {
emit(kind3Follows)
} else if (peopleListFollows.event == null) {
emit(LiveFollowLists())
emit(LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
} else {
val result = waitToDecrypt(peopleListFollows.event)
if (result == null) {
emit(LiveFollowLists())
emit(LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
} else {
emit(result)
}
@@ -481,7 +494,7 @@ class Account(
}
val liveHomeFollowLists: StateFlow<LiveFollowLists?> by lazy {
liveHomeFollowListFlow.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
liveHomeFollowListFlow.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
}
fun relaysFromPeopleListFlows(
@@ -540,7 +553,7 @@ class Account(
val liveNotificationFollowLists: StateFlow<LiveFollowLists?> by lazy {
combinePeopleListFlows(liveKind3FollowsFlow, liveNotificationList)
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -552,7 +565,7 @@ class Account(
val liveStoriesFollowLists: StateFlow<LiveFollowLists?> by lazy {
combinePeopleListFlows(liveKind3FollowsFlow, liveStoriesList)
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -564,7 +577,7 @@ class Account(
val liveDiscoveryFollowLists: StateFlow<LiveFollowLists?> by lazy {
combinePeopleListFlows(liveKind3FollowsFlow, liveDiscoveryList)
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists(usersPlusMe = setOf(keyPair.pubKeyHex)))
}
private fun decryptLiveFollows(
@@ -572,10 +585,11 @@ class Account(
onReady: (LiveFollowLists) -> Unit,
) {
listEvent.privateTags(signer) { privateTagList ->
val users = (listEvent.bookmarkedPeople() + listEvent.filterUsers(privateTagList)).toSet()
onReady(
LiveFollowLists(
users =
(listEvent.bookmarkedPeople() + listEvent.filterUsers(privateTagList)).toSet(),
users = users,
usersPlusMe = users + userProfile().pubkeyHex,
hashtags =
(listEvent.hashtags() + listEvent.filterHashtags(privateTagList)).toSet(),
geotags =
@@ -759,6 +773,10 @@ class Account(
}
}
suspend fun countFollowersOf(pubkey: HexKey): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkey) ?: false }
suspend fun followerCount(): Int = countFollowersOf(keyPair.pubKeyHex)
fun sendNewUserMetadata(
name: String? = null,
picture: String? = null,
@@ -2813,9 +2831,7 @@ class Account(
flowHiddenUsers.value.hiddenUsers.contains(userHex) ||
flowHiddenUsers.value.spammers.contains(userHex)
fun followingKeySet(): Set<HexKey> = userProfile().cachedFollowingKeySet()
fun followingTagSet(): Set<HexKey> = userProfile().cachedFollowingTagSet()
fun followingKeySet(): Set<HexKey> = liveKind3Follows.value.users
fun isAcceptable(user: User): Boolean {
if (userProfile().pubkeyHex == user.pubkeyHex) {
@@ -2865,8 +2881,6 @@ class Account(
}
fun getRelevantReports(note: Note): Set<Note> {
val followsPlusMe = userProfile().latestContactList?.verifiedFollowKeySetAndMe ?: emptySet()
val innerReports =
if (note.event is RepostEvent || note.event is GenericRepostEvent) {
note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList()
@@ -2875,8 +2889,8 @@ class Account(
}
return (
note.reportsBy(followsPlusMe) +
(note.author?.reportsBy(followsPlusMe) ?: emptyList()) +
note.reportsBy(liveKind3Follows.value.usersPlusMe) +
(note.author?.reportsBy(liveKind3Follows.value.usersPlusMe) ?: emptyList()) +
innerReports
).toSet()
}

View File

@@ -336,52 +336,24 @@ class User(
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
fun isFollowingHashtag(tag: String): Boolean = latestContactList?.isTaggedHash(tag) ?: false
fun isFollowingHashtagCached(tag: String): Boolean {
return latestContactList?.verifiedFollowTagSet?.let {
fun isFollowingHashtag(tag: String): Boolean {
return latestContactList?.unverifiedFollowTagSet()?.map { it.lowercase() }?.toSet()?.let {
return tag.lowercase() in it
}
?: false
}
fun isFollowingGeohashCached(geoTag: String): Boolean {
return latestContactList?.verifiedFollowGeohashSet?.let {
fun isFollowingGeohash(geoTag: String): Boolean {
return latestContactList?.unverifiedFollowAddressSet()?.toSet()?.let {
return geoTag.lowercase() in it
}
?: false
}
fun isFollowingCached(user: User): Boolean {
return latestContactList?.verifiedFollowKeySet?.let {
return user.pubkeyHex in it
}
?: false
}
fun isFollowingCached(userHex: String): Boolean {
return latestContactList?.verifiedFollowKeySet?.let {
return userHex in it
}
?: false
}
fun transientFollowCount(): Int? = latestContactList?.unverifiedFollowKeySet()?.size
suspend fun transientFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
fun cachedFollowingKeySet(): Set<HexKey> = latestContactList?.verifiedFollowKeySet ?: emptySet()
fun cachedFollowingTagSet(): Set<String> = latestContactList?.verifiedFollowTagSet ?: emptySet()
fun cachedFollowingGeohashSet(): Set<HexKey> = latestContactList?.verifiedFollowGeohashSet ?: emptySet()
fun cachedFollowingCommunitiesSet(): Set<HexKey> = latestContactList?.verifiedFollowCommunitySet ?: emptySet()
fun cachedFollowCount(): Int? = latestContactList?.verifiedFollowKeySet?.size
suspend fun cachedFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
fun hasSentMessagesTo(key: ChatroomKey?): Boolean {
val messagesToUser = privateChatrooms[key] ?: return false

View File

@@ -763,9 +763,11 @@ class FollowListViewModel(
@OptIn(ExperimentalCoroutinesApi::class)
val liveKind3FollowsFlow: Flow<List<CodeName>> =
account.userProfile().flow().follows.stateFlow.transformLatest {
account.liveKind3FollowsFlow.transformLatest {
checkNotInMainThread()
val communities =
it.user.cachedFollowingCommunitiesSet().mapNotNull {
it.communities.mapNotNull {
LocalCache.checkGetOrCreateAddressableNote(it)?.let { communityNote ->
CodeName(
"Community/${communityNote.idHex}",
@@ -776,12 +778,12 @@ class FollowListViewModel(
}
val hashtags =
it.user.cachedFollowingTagSet().map {
it.hashtags.map {
CodeName("Hashtag/$it", HashtagName(it), CodeNameType.ROUTE)
}
val geotags =
it.user.cachedFollowingGeohashSet().map {
it.geotags.map {
CodeName("Geohash/$it", GeoHashName(it), CodeNameType.ROUTE)
}

View File

@@ -83,6 +83,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.mediaServers.MediaServersListView
@@ -114,7 +115,6 @@ import com.vitorpamplona.ammolite.relays.RelayPoolStatus
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
@@ -151,7 +151,7 @@ fun DrawerContent(
EditStatusBoxes(accountViewModel.account.userProfile(), accountViewModel, drawerState)
}
FollowingAndFollowerCounts(accountViewModel.account.userProfile(), onClickUser)
FollowingAndFollowerCounts(accountViewModel.account, onClickUser)
HorizontalDivider(
thickness = DividerThickness,
@@ -380,18 +380,12 @@ fun UserStatusDeleteButton(onClick: () -> Unit) {
@Composable
private fun FollowingAndFollowerCounts(
baseAccountUser: User,
baseAccountUser: Account,
onClick: () -> Unit,
) {
var followingCount by remember { mutableStateOf("--") }
val followingCount = baseAccountUser.liveKind3Follows.collectAsStateWithLifecycle()
var followerCount by remember { mutableStateOf("--") }
WatchFollow(baseAccountUser = baseAccountUser) { newFollowing ->
if (followingCount != newFollowing) {
followingCount = newFollowing
}
}
WatchFollower(baseAccountUser = baseAccountUser) { newFollower ->
if (followerCount != newFollower) {
followerCount = newFollower
@@ -402,7 +396,9 @@ private fun FollowingAndFollowerCounts(
modifier = drawerSpacing.clickable(onClick = onClick),
) {
Text(
text = followingCount,
text =
followingCount.value.users.size
.toString(),
fontWeight = FontWeight.Bold,
)
@@ -419,31 +415,19 @@ private fun FollowingAndFollowerCounts(
}
}
@Composable
fun WatchFollow(
baseAccountUser: User,
onReady: (String) -> Unit,
) {
val accountUserFollowsState by baseAccountUser.live().follows.observeAsState()
LaunchedEffect(key1 = accountUserFollowsState) {
launch(Dispatchers.IO) {
onReady(accountUserFollowsState?.user?.cachedFollowCount()?.toString() ?: "--")
}
}
}
@Composable
fun WatchFollower(
baseAccountUser: User,
baseAccountUser: Account,
onReady: (String) -> Unit,
) {
val accountUserFollowersState by baseAccountUser.live().followers.observeAsState()
val accountUserFollowersState by baseAccountUser
.userProfile()
.live()
.followers
.observeAsState()
LaunchedEffect(key1 = accountUserFollowersState) {
launch(Dispatchers.IO) {
onReady(accountUserFollowersState?.user?.cachedFollowerCount()?.toString() ?: "--")
}
onReady(baseAccountUser.followerCount().toString() ?: "--")
}
}

View File

@@ -676,7 +676,7 @@ fun LoadModerators(
val newParticipantUsers =
if (followingKeySet == null) {
val allFollows = accountViewModel.account.userProfile().cachedFollowingKeySet()
val allFollows = accountViewModel.account.liveKind3Follows.value.users
val followingParticipants =
ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts)
@@ -732,7 +732,7 @@ private fun LoadParticipants(
val newParticipantUsers =
if (followingKeySet == null) {
val allFollows = accountViewModel.account.userProfile().cachedFollowingKeySet()
val allFollows = accountViewModel.account.liveKind3Follows.value.users
val followingParticipants =
ParticipantListBuilder()
.followsThatParticipateOn(baseNote, allFollows)
@@ -889,7 +889,7 @@ fun RenderChannelThumb(
val newParticipantUsers =
if (followingKeySet == null) {
val allFollows = accountViewModel.account.userProfile().cachedFollowingKeySet()
val allFollows = accountViewModel.account.liveKind3Follows.value.users
val followingParticipants =
ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).toList()

View File

@@ -367,11 +367,10 @@ fun WatchUserFollows(
remember {
accountViewModel.userFollows
.map {
it.user.isFollowingCached(userHex) ||
(userHex == accountViewModel.account.userProfile().pubkeyHex)
accountViewModel.isFollowing(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
}.distinctUntilChanged()
}.observeAsState(
accountViewModel.account.userProfile().isFollowingCached(userHex) ||
accountViewModel.isFollowing(userHex) ||
(userHex == accountViewModel.account.userProfile().pubkeyHex),
)

View File

@@ -29,12 +29,12 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.AnnotatedString
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -44,12 +44,11 @@ fun DisplayFollowingHashtagsInPost(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val userFollowState by accountViewModel.userFollows.observeAsState()
val userFollowState by accountViewModel.account.liveKind3Follows.collectAsStateWithLifecycle()
var firstTag by remember(baseNote) { mutableStateOf<String?>(null) }
LaunchedEffect(key1 = userFollowState) {
val followingTags = userFollowState?.user?.cachedFollowingTagSet() ?: emptySet()
val newFirstTag = baseNote.event?.firstIsTaggedHashes(followingTags)
val newFirstTag = baseNote.event?.firstIsTaggedHashes(userFollowState.hashtags)
if (firstTag != newFirstTag) {
firstTag = newFirstTag

View File

@@ -807,10 +807,10 @@ class AccountViewModel(
fun isFollowing(user: User?): Boolean {
if (user == null) return false
return account.userProfile().isFollowingCached(user)
return account.isFollowing(user)
}
fun isFollowing(user: HexKey): Boolean = account.userProfile().isFollowingCached(user)
fun isFollowing(user: HexKey): Boolean = account.isFollowing(user)
val hideDeleteRequestDialog: Boolean
get() = account.hideDeleteRequestDialog

View File

@@ -188,7 +188,7 @@ fun GeoHashActionOptions(
.observeAsState()
val isFollowingTag by
remember(userState) {
derivedStateOf { userState?.user?.isFollowingGeohashCached(tag) ?: false }
derivedStateOf { userState?.user?.isFollowingGeohash(tag) ?: false }
}
if (isFollowingTag) {

View File

@@ -167,7 +167,7 @@ fun HashtagActionOptions(
.observeAsState()
val isFollowingTag by
remember(userState) {
derivedStateOf { userState?.user?.isFollowingHashtagCached(tag) ?: false }
derivedStateOf { userState?.user?.isFollowingHashtag(tag) ?: false }
}
if (isFollowingTag) {

View File

@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.quartz.crypto
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toHexKey
class KeyPair(
@@ -29,6 +30,7 @@ class KeyPair(
) {
val privKey: ByteArray?
val pubKey: ByteArray
val pubKeyHex: HexKey
init {
if (privKey == null) {
@@ -52,6 +54,8 @@ class KeyPair(
this.pubKey = pubKey
}
}
this.pubKeyHex = this.pubKey.toHexKey().intern()
}
override fun toString(): String = "KeyPair(privateKey=${privKey?.toHexKey()}, publicKey=${pubKey.toHexKey()}"