Remove map in FollowSetFeedFilter, in favor of retrieving note from cache if modification is needed. Amend FollowSet to carry the address of the note. Add Refreshable for the Lists screen.

This commit is contained in:
KotlinGeekDev
2025-03-31 21:31:50 +01:00
parent 28093256e7
commit 7d90b9f87b
3 changed files with 57 additions and 23 deletions
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.dal
import android.util.Log import android.util.Log
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
@@ -30,12 +29,10 @@ import kotlin.coroutines.cancellation.CancellationException
class FollowSetFeedFilter( class FollowSetFeedFilter(
val account: Account, val account: Account,
) : FeedFilter<FollowSet>() { ) : FeedFilter<FollowSet>() {
private val followSetEventPairs: MutableMap<AddressableNote, FollowSet> = mutableMapOf()
override fun feedKey(): String = account.userProfile().pubkeyHex override fun feedKey(): String = account.userProfile().pubkeyHex
override fun feed(): List<FollowSet> { override fun feed(): List<FollowSet> {
val userFollowSets = account.userProfile().followSets val userFollowSets = account.userProfile().followSetNotes
if (userFollowSets.isEmpty()) { if (userFollowSets.isEmpty()) {
account.scope.launch { account.scope.launch {
try { try {
@@ -47,14 +44,7 @@ class FollowSetFeedFilter(
} }
} }
} }
updateFollowSetEventPairs(userFollowSets) val followSets = userFollowSets.map { account.mapNoteToFollowSet(it) }
return followSetEventPairs.values.toList() return followSets
}
private fun updateFollowSetEventPairs(notes: Set<AddressableNote>) {
notes.forEach { note ->
val noteAndSetPair = note to account.mapNoteToFollowSet(note)
followSetEventPairs.putIfAbsent(noteAndSetPair.first, noteAndSetPair.second)
}
} }
} }
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
import android.util.Log import android.util.Log
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -42,6 +43,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -60,6 +62,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
import com.vitorpamplona.amethyst.ui.feeds.FeedError import com.vitorpamplona.amethyst.ui.feeds.FeedError
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.NostrUserFollowSetFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserFollowSetFeedViewModel
@@ -71,6 +74,9 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable @Composable
fun ListsScreen( fun ListsScreen(
@@ -83,6 +89,7 @@ fun ListsScreen(
factory = NostrUserFollowSetFeedViewModel.Factory(accountViewModel.account), factory = NostrUserFollowSetFeedViewModel.Factory(accountViewModel.account),
) )
val currentCoroutineScope = rememberCoroutineScope()
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifeCycleOwner) { DisposableEffect(lifeCycleOwner) {
@@ -109,6 +116,17 @@ fun ListsScreen(
refresh = { refresh = {
followSetsViewModel.invalidateData() followSetsViewModel.invalidateData()
}, },
openItem = {
currentCoroutineScope.launch(Dispatchers.IO) {
val note = followSetsViewModel.getFollowSetAddressable(it, accountViewModel.account)
if (note != null) {
val event = note.event as PeopleListEvent
println("Found list, with title: ${event.nameOrTitle()}")
} else {
println("No corresponding note found for this list.")
}
}
},
accountViewModel, accountViewModel,
nav, nav,
) )
@@ -118,6 +136,7 @@ fun ListsScreen(
fun CustomListsScreen( fun CustomListsScreen(
followSetState: FollowSetState, followSetState: FollowSetState,
refresh: () -> Unit, refresh: () -> Unit,
openItem: (identifier: String) -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
@@ -130,7 +149,11 @@ fun CustomListsScreen(
TopBarWithBackButton(stringRes(R.string.my_lists), nav::popBack) TopBarWithBackButton(stringRes(R.string.my_lists), nav::popBack)
}, },
) { ) {
Column(Modifier.padding(it).fillMaxHeight()) { Column(
Modifier
.padding(it)
.fillMaxHeight(),
) {
when (followSetState) { when (followSetState) {
FollowSetState.Loading -> LoadingFeed() FollowSetState.Loading -> LoadingFeed()
@@ -139,6 +162,7 @@ fun CustomListsScreen(
FollowListLoaded( FollowListLoaded(
loadedFeedState = followSetFeed, loadedFeedState = followSetFeed,
onRefresh = refresh, onRefresh = refresh,
onItemClick = openItem,
) )
} }
@@ -164,16 +188,26 @@ fun FollowListLoaded(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
loadedFeedState: List<FollowSet>, loadedFeedState: List<FollowSet>,
onRefresh: () -> Unit = {}, onRefresh: () -> Unit = {},
onItemClick: (String) -> Unit = {},
) { ) {
Log.d("FollowSetComposable", "FollowListLoaded: Follow Set size: ${loadedFeedState.size}") Log.d("FollowSetComposable", "FollowListLoaded: Follow Set size: ${loadedFeedState.size}")
val listState = rememberLazyListState() val listState = rememberLazyListState()
LazyColumn( RefresheableBox(
state = listState, onRefresh = onRefresh,
contentPadding = FeedPadding,
) { ) {
itemsIndexed(loadedFeedState, key = { _, item -> item.title }) { index, set -> LazyColumn(
CustomListItem(followSet = set) state = listState,
contentPadding = FeedPadding,
) {
itemsIndexed(loadedFeedState, key = { _, item -> item.identifierTag }) { index, set ->
CustomListItem(
followSet = set,
onFollowSetClick = {
onItemClick(set.identifierTag)
},
)
}
} }
} }
} }
@@ -182,10 +216,12 @@ fun FollowListLoaded(
fun CustomListItem( fun CustomListItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
followSet: FollowSet, followSet: FollowSet,
onFollowSetClick: () -> Unit,
) { ) {
Row( Row(
modifier = modifier =
modifier modifier
.clickable(onClick = onFollowSetClick)
.border( .border(
width = Dp.Hairline, width = Dp.Hairline,
color = Color.Gray, color = Color.Gray,
@@ -261,15 +297,19 @@ fun CustomListItem(
private fun ListItemPreview() { private fun ListItemPreview() {
val sampleFollowSet = val sampleFollowSet =
FollowSet( FollowSet(
visibility = ListVisibility.Mixed, identifierTag = "00001-2222",
title = "Sample List Title", title = "Sample List Title",
description = "Sample List Description", description = "Sample List Description",
visibility = ListVisibility.Mixed,
emptySet(), emptySet(),
) )
ThemeComparisonColumn { ThemeComparisonColumn {
CustomListItem( CustomListItem(
modifier = Modifier, modifier = Modifier,
sampleFollowSet, sampleFollowSet,
onFollowSetClick = {
println("follow set: ${sampleFollowSet.identifierTag}")
},
) )
} }
} }
@@ -26,9 +26,10 @@ import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
@Stable @Stable
data class FollowSet( data class FollowSet(
val visibility: ListVisibility, val identifierTag: String,
val title: String, val title: String,
val description: String?, val description: String?,
val visibility: ListVisibility,
val profileList: Set<String>, val profileList: Set<String>,
) : NostrList(listVisibility = visibility, content = profileList) { ) : NostrList(listVisibility = visibility, content = profileList) {
companion object { companion object {
@@ -36,6 +37,7 @@ data class FollowSet(
event: PeopleListEvent, event: PeopleListEvent,
signer: NostrSigner, signer: NostrSigner,
): FollowSet { ): FollowSet {
val address = event.address()
val dTag = event.dTag() val dTag = event.dTag()
val listTitle = event.nameOrTitle() ?: dTag val listTitle = event.nameOrTitle() ?: dTag
val listDescription = event.description() ?: "" val listDescription = event.description() ?: ""
@@ -44,16 +46,18 @@ data class FollowSet(
event.privateTaggedUsers(signer) { userList -> privateFollows.addAll(userList) } event.privateTaggedUsers(signer) { userList -> privateFollows.addAll(userList) }
return if (publicFollows.isEmpty() && privateFollows.isNotEmpty()) { return if (publicFollows.isEmpty() && privateFollows.isNotEmpty()) {
FollowSet( FollowSet(
visibility = ListVisibility.Private, identifierTag = address.toValue(),
title = listTitle, title = listTitle,
description = listDescription, description = listDescription,
visibility = ListVisibility.Private,
profileList = privateFollows.toSet(), profileList = privateFollows.toSet(),
) )
} else if (publicFollows.isNotEmpty() && privateFollows.isEmpty()) { } else if (publicFollows.isNotEmpty() && privateFollows.isEmpty()) {
FollowSet( FollowSet(
visibility = ListVisibility.Public, identifierTag = address.toValue(),
title = listTitle, title = listTitle,
description = listDescription, description = listDescription,
visibility = ListVisibility.Public,
profileList = publicFollows.toSet(), profileList = publicFollows.toSet(),
) )
} else { } else {