mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 22:19:35 +01:00
Moves the ContactList cache lists to AccountViewModel, where it can be disposed more efficiently.
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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() ?: "--")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()}"
|
||||
|
||||
Reference in New Issue
Block a user