- Moves zaps from a Map<Request,Response> to a List<CustomClass> to avoid recompositions due to the use of the unstable Pair object

- Creates the class ZapAmountCommentNotification to be used instead of the unstable Triple class.
This commit is contained in:
Vitor Pamplona
2023-06-13 16:33:52 -04:00
parent a01a060abc
commit 9b5c7ad23f
4 changed files with 147 additions and 93 deletions
@@ -22,6 +22,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Bolt
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@@ -47,10 +48,12 @@ import com.vitorpamplona.amethyst.R
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.amethyst.model.UserMetadata
import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
@@ -58,7 +61,6 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -92,48 +94,32 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
} }
} }
val columnModifier = Modifier val columnModifier = remember(backgroundColor.value) {
.drawBehind { Modifier
drawRect(backgroundColor.value) .drawBehind {
} drawRect(backgroundColor.value)
.padding( }
start = 12.dp, .padding(
end = 12.dp, start = 12.dp,
top = 10.dp end = 12.dp,
) top = 10.dp
.combinedClickable( )
onClick = { .combinedClickable(
scope.launch { onClick = {
routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) } scope.launch {
} routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) }
}, }
onLongClick = { popupExpanded = true } },
) onLongClick = { popupExpanded = true }
.fillMaxWidth() )
.fillMaxWidth()
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } } }
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
val likeEvents by remember { derivedStateOf { multiSetCard.likeEvents } }
val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } }
val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } }
val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } }
Column(modifier = columnModifier) { Column(modifier = columnModifier) {
if (hasZapEvents) { Galeries(multiSetCard, backgroundColor, nav, accountViewModel)
RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel)
}
if (hasBoostEvents) { Row(remember { Modifier.fillMaxWidth() }) {
RenderBoostGallery(boostEvents, backgroundColor, nav, accountViewModel) Spacer(modifier = remember { Modifier.width(65.dp) })
}
if (hasLikeEvents) {
RenderLikeGallery(likeEvents, backgroundColor, nav, accountViewModel)
}
Row(Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.width(65.dp))
NoteCompose( NoteCompose(
baseNote = baseNote, baseNote = baseNote,
@@ -150,6 +136,34 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
} }
} }
@Composable
private fun Galeries(
multiSetCard: MultiSetCard,
backgroundColor: MutableState<Color>,
nav: (String) -> Unit,
accountViewModel: AccountViewModel
) {
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } }
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
val likeEvents by remember { derivedStateOf { multiSetCard.likeEvents } }
val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } }
val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } }
val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } }
if (hasZapEvents) {
RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel)
}
if (hasBoostEvents) {
RenderBoostGallery(boostEvents, backgroundColor, nav, accountViewModel)
}
if (hasLikeEvents) {
RenderLikeGallery(likeEvents, backgroundColor, nav, accountViewModel)
}
}
@Composable @Composable
fun RenderLikeGallery( fun RenderLikeGallery(
likeEvents: ImmutableList<Note>, likeEvents: ImmutableList<Note>,
@@ -157,7 +171,7 @@ fun RenderLikeGallery(
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
Row(Modifier.fillMaxWidth()) { Row(remember { Modifier.fillMaxWidth() }) {
Box( Box(
modifier = remember { modifier = remember {
Modifier Modifier
@@ -183,12 +197,12 @@ fun RenderLikeGallery(
@Composable @Composable
fun RenderZapGallery( fun RenderZapGallery(
zapEvents: ImmutableMap<Note, Note?>, zapEvents: ImmutableList<CombinedZap>,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
Row(Modifier.fillMaxWidth()) { Row(remember { Modifier.fillMaxWidth() }) {
Box( Box(
modifier = remember { modifier = remember {
Modifier Modifier
@@ -250,7 +264,7 @@ fun RenderBoostGallery(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun AuthorGalleryZaps( fun AuthorGalleryZaps(
authorNotes: ImmutableMap<Note, Note?>, authorNotes: ImmutableList<CombinedZap>,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
@@ -258,12 +272,19 @@ fun AuthorGalleryZaps(
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() { FlowRow() {
authorNotes.forEach { authorNotes.forEach {
AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountViewModel) AuthorPictureAndComment(it.request, it.response, backgroundColor, nav, accountViewModel)
} }
} }
} }
} }
@Immutable
data class ZapAmountCommentNotification(
val user: User?,
val comment: String?,
val amount: String?
)
@Composable @Composable
private fun AuthorPictureAndComment( private fun AuthorPictureAndComment(
zapRequest: Note, zapRequest: Note,
@@ -272,7 +293,15 @@ private fun AuthorPictureAndComment(
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
var content by remember { mutableStateOf<Triple<User?, String?, String?>>(Triple(zapRequest.author, null, null)) } var content by remember {
mutableStateOf(
ZapAmountCommentNotification(
user = zapRequest.author,
comment = null,
amount = null
)
)
}
LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) { LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) {
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
@@ -281,32 +310,34 @@ private fun AuthorPictureAndComment(
val amount = (zapEvent?.event as? LnZapEvent)?.amount val amount = (zapEvent?.event as? LnZapEvent)?.amount
if (decryptedContent != null) { if (decryptedContent != null) {
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount)) content = ZapAmountCommentNotification(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount))
} else { } else {
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) { if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
content = Triple(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount)) content = ZapAmountCommentNotification(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount))
} }
} }
} }
} }
} }
content.first?.let { content.let {
val route by remember { val route by remember {
derivedStateOf { derivedStateOf {
"User/${it.pubkeyHex}" "User/${it.user?.pubkeyHex}"
} }
} }
AuthorPictureAndComment( it.user?.let { user ->
author = it, AuthorPictureAndComment(
comment = content.second, author = user,
amount = content.third, comment = it.comment,
route = route, amount = it.amount,
backgroundColor = backgroundColor, route = route,
nav = nav, backgroundColor = backgroundColor,
accountViewModel = accountViewModel nav = nav,
) accountViewModel = accountViewModel
)
}
} }
} }
@@ -341,12 +372,19 @@ private fun AuthorPictureAndComment(
) )
amount?.let { amount?.let {
Box(modifier = Modifier.fillMaxSize().clip(shape = CircleShape), contentAlignment = Alignment.BottomCenter) { Box(
modifier = Modifier
.fillMaxSize()
.clip(shape = CircleShape),
contentAlignment = Alignment.BottomCenter
) {
val backgroundColor = MaterialTheme.colors.overPictureBackground val backgroundColor = MaterialTheme.colors.overPictureBackground
Box( Box(
modifier = Modifier modifier = remember {
.fillMaxWidth() Modifier
.drawBehind { drawRect(backgroundColor) }, .fillMaxWidth()
.drawBehind { drawRect(backgroundColor) }
},
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter
) { ) {
Text( Text(
@@ -424,17 +462,20 @@ fun FastNoteAuthorPicture(
pictureModifier: Modifier = Modifier, pictureModifier: Modifier = Modifier,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val userState by author.live().metadata.observeAsState() var profilePicture by remember {
val profilePicture by remember(userState) { mutableStateOf(author.info?.picture)
derivedStateOf {
userState?.user?.profilePicture()
}
} }
val authorPubKey = remember { val authorPubKey = remember {
author.pubkeyHex author.pubkeyHex
} }
WatchUserMetadata(author) {
if (it.picture != profilePicture) {
profilePicture = it.picture
}
}
UserPicture( UserPicture(
userHex = authorPubKey, userHex = authorPubKey,
userPicture = profilePicture, userPicture = profilePicture,
@@ -443,3 +484,15 @@ fun FastNoteAuthorPicture(
accountViewModel = accountViewModel accountViewModel = accountViewModel
) )
} }
@Composable
fun WatchUserMetadata(userBase: User, onMetadataChanges: (UserMetadata) -> Unit) {
val userState by userBase.live().metadata.observeAsState()
LaunchedEffect(key1 = userState) {
launch(Dispatchers.Default) {
userState?.user?.info?.let {
onMetadataChanges(it)
}
}
}
}
@@ -64,12 +64,12 @@ import com.vitorpamplona.amethyst.R
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.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -188,7 +188,7 @@ private fun ReactionDetailGallery(
if (hasReactions) { if (hasReactions) {
Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp)) { Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp)) {
Column() { Column() {
val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.toImmutableMap() } } val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.mapNotNull { it.value?.let { zapEvent -> CombinedZap(it.key, zapEvent) } }.toImmutableList() } }
val boostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.toImmutableList() } } val boostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.toImmutableList() } }
val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableList() } } val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableList() } }
@@ -6,7 +6,6 @@ import androidx.compose.runtime.Stable
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 kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
@Immutable @Immutable
abstract class Card() { abstract class Card() {
@@ -33,8 +32,8 @@ class NoteCard(val note: Note) : Card() {
} }
@Immutable @Immutable
class ZapUserSetCard(val user: User, val zapEvents: ImmutableMap<Note, Note>) : Card() { class ZapUserSetCard(val user: User, val zapEvents: ImmutableList<CombinedZap>) : Card() {
val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 } val createdAt = zapEvents.maxOf { it.createdAt() ?: 0 }
override fun createdAt(): Long { override fun createdAt(): Long {
return createdAt return createdAt
} }
@@ -42,15 +41,15 @@ class ZapUserSetCard(val user: User, val zapEvents: ImmutableMap<Note, Note>) :
} }
@Immutable @Immutable
class MultiSetCard(val note: Note, val boostEvents: ImmutableList<Note>, val likeEvents: ImmutableList<Note>, val zapEvents: ImmutableMap<Note, Note>) : Card() { class MultiSetCard(val note: Note, val boostEvents: ImmutableList<Note>, val likeEvents: ImmutableList<Note>, val zapEvents: ImmutableList<CombinedZap>) : Card() {
val maxCreatedAt = maxOf( val maxCreatedAt = maxOf(
zapEvents.maxOfOrNull { it.value.createdAt() ?: 0 } ?: 0, zapEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0, likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
boostEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0 boostEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0
) )
val minCreatedAt = minOf( val minCreatedAt = minOf(
zapEvents.minOfOrNull { it.value.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE, zapEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE,
likeEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE, likeEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE,
boostEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE boostEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE
) )
@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.screen package com.vitorpamplona.amethyst.ui.screen
import android.util.Log import android.util.Log
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -25,7 +26,6 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -132,8 +132,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
} }
// val reactionCards = reactionsPerEvent.map { LikeSetCard(it.key, it.value) } // val reactionCards = reactionsPerEvent.map { LikeSetCard(it.key, it.value) }
val zapsPerUser = mutableMapOf<User, MutableMap<Note, Note>>() val zapsPerUser = mutableMapOf<User, MutableList<CombinedZap>>()
val zapsPerEvent = mutableMapOf<Note, MutableMap<Note, Note>>() val zapsPerEvent = mutableMapOf<Note, MutableList<CombinedZap>>()
notes notes
.filter { it.event is LnZapEvent } .filter { it.event is LnZapEvent }
.forEach { zapEvent -> .forEach { zapEvent ->
@@ -143,27 +143,23 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
if (zapRequest != null) { if (zapRequest != null) {
// var newZapRequestEvent = LocalCache.checkPrivateZap(zapRequest.event as Event) // var newZapRequestEvent = LocalCache.checkPrivateZap(zapRequest.event as Event)
// zapRequest.event = newZapRequestEvent // zapRequest.event = newZapRequestEvent
zapsPerEvent.getOrPut(zappedPost, { mutableMapOf() }).put(zapRequest, zapEvent) zapsPerEvent.getOrPut(zappedPost, { mutableListOf() }).add(CombinedZap(zapRequest, zapEvent))
} }
} else { } else {
val event = (zapEvent.event as LnZapEvent) val event = (zapEvent.event as LnZapEvent)
val author = event.zappedAuthor().mapNotNull { val author = event.zappedAuthor().firstNotNullOfOrNull {
LocalCache.checkGetOrCreateUser( LocalCache.users[it] // don't create user if it doesn't exist
it }
)
}.firstOrNull()
if (author != null) { if (author != null) {
val zapRequest = author.zaps.filter { it.value == zapEvent }.keys.firstOrNull() val zapRequest = author.zaps.filter { it.value == zapEvent }.keys.firstOrNull()
if (zapRequest != null) { if (zapRequest != null) {
zapsPerUser.getOrPut(author, { mutableMapOf() }) zapsPerUser.getOrPut(author, { mutableListOf() })
.put(zapRequest, zapEvent) .add(CombinedZap(zapRequest, zapEvent))
} }
} }
} }
} }
// val zapCards = zapsPerEvent.map { ZapSetCard(it.key, it.value) }
val boostsPerEvent = mutableMapOf<Note, MutableList<Note>>() val boostsPerEvent = mutableMapOf<Note, MutableList<Note>>()
notes notes
.filter { it.event is RepostEvent } .filter { it.event is RepostEvent }
@@ -178,9 +174,9 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
val multiCards = allBaseNotes.map { baseNote -> val multiCards = allBaseNotes.map { baseNote ->
val boostsInCard = boostsPerEvent[baseNote] ?: emptyList() val boostsInCard = boostsPerEvent[baseNote] ?: emptyList()
val reactionsInCard = reactionsPerEvent[baseNote] ?: emptyList() val reactionsInCard = reactionsPerEvent[baseNote] ?: emptyList()
val zapsInCard = zapsPerEvent[baseNote] ?: emptyMap() val zapsInCard = zapsPerEvent[baseNote] ?: emptyList()
val singleList = (boostsInCard + zapsInCard.values + reactionsInCard) val singleList = (boostsInCard + zapsInCard.map { it.response } + reactionsInCard)
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed() .reversed()
@@ -189,7 +185,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
baseNote, baseNote,
boostsInCard.filter { it in chunk }.toImmutableList(), boostsInCard.filter { it in chunk }.toImmutableList(),
reactionsInCard.filter { it in chunk }.toImmutableList(), reactionsInCard.filter { it in chunk }.toImmutableList(),
zapsInCard.filter { it.value in chunk }.toImmutableMap() zapsInCard.filter { it.response in chunk }.toImmutableList()
) )
} }
}.flatten() }.flatten()
@@ -197,7 +193,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
val userZaps = zapsPerUser.map { val userZaps = zapsPerUser.map {
ZapUserSetCard( ZapUserSetCard(
it.key, it.key,
it.value.toImmutableMap() it.value.toImmutableList()
) )
} }
@@ -346,3 +342,9 @@ fun <T> equalImmutableLists(list1: ImmutableList<T>, list2: ImmutableList<T>): B
} }
return true return true
} }
@Immutable
data class CombinedZap(val request: Note, val response: Note) {
fun createdAt() = response.createdAt()
fun idHex() = response.idHex
}