Moves zap decryption to run in a group, avoiding multiple co-routines per zap

This commit is contained in:
Vitor Pamplona
2023-09-20 09:37:56 -04:00
parent faeb2a3894
commit 11b062c41f
4 changed files with 135 additions and 122 deletions

View File

@@ -1,10 +1,7 @@
package com.vitorpamplona.amethyst.ui.note package com.vitorpamplona.amethyst.ui.note
import android.util.Log import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -33,6 +30,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -72,6 +70,7 @@ import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
import com.vitorpamplona.amethyst.ui.theme.profile35dpModifier import com.vitorpamplona.amethyst.ui.theme.profile35dpModifier
import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.EmptyTagList
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -172,7 +171,6 @@ private fun Galeries(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } }
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } } val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
val likeEvents by remember { derivedStateOf { multiSetCard.likeEventsByType } } val likeEvents by remember { derivedStateOf { multiSetCard.likeEventsByType } }
@@ -181,6 +179,16 @@ private fun Galeries(
val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } } val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } }
if (hasZapEvents) { if (hasZapEvents) {
var zapEvents by remember(multiSetCard) {
mutableStateOf<ImmutableList<ZapAmountCommentNotification>>(persistentListOf())
}
LaunchedEffect(key1 = Unit) {
accountViewModel.decryptAmountMessageInGroup(multiSetCard.zapEvents) {
zapEvents = it
}
}
val (value, elapsed) = measureTimedValue { val (value, elapsed) = measureTimedValue {
RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel) RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel)
} }
@@ -250,7 +258,7 @@ fun RenderLikeGallery(
@Composable @Composable
fun RenderZapGallery( fun RenderZapGallery(
zapEvents: ImmutableList<CombinedZap>, zapEvents: ImmutableList<ZapAmountCommentNotification>,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
@@ -297,10 +305,29 @@ fun RenderBoostGallery(
} }
} }
@Composable
fun MapZaps(
zaps: ImmutableList<CombinedZap>,
accountViewModel: AccountViewModel,
content: @Composable (ImmutableList<ZapAmountCommentNotification>) -> Unit
) {
var zapEvents by remember(zaps) {
mutableStateOf<ImmutableList<ZapAmountCommentNotification>>(persistentListOf())
}
LaunchedEffect(key1 = Unit) {
accountViewModel.decryptAmountMessageInGroup(zaps) {
zapEvents = it
}
}
content(zapEvents)
}
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun AuthorGalleryZaps( fun AuthorGalleryZaps(
authorNotes: ImmutableList<CombinedZap>, authorNotes: ImmutableList<ZapAmountCommentNotification>,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
@@ -308,24 +335,12 @@ fun AuthorGalleryZaps(
Column(modifier = StdStartPadding) { Column(modifier = StdStartPadding) {
FlowRow() { FlowRow() {
authorNotes.forEach { authorNotes.forEach {
ParseAuthorCommentAndAmount(it, backgroundColor, nav, accountViewModel) RenderState(it, backgroundColor, accountViewModel, nav)
} }
} }
} }
} }
@Composable
private fun ParseAuthorCommentAndAmount(
zap: CombinedZap,
backgroundColor: MutableState<Color>,
nav: (String) -> Unit,
accountViewModel: AccountViewModel
) {
ParseAuthorCommentAndAmount(zap.request, zap.response, accountViewModel) { state ->
RenderState(state, backgroundColor, accountViewModel, nav)
}
}
@Immutable @Immutable
data class ZapAmountCommentNotification( data class ZapAmountCommentNotification(
val user: User?, val user: User?,
@@ -361,15 +376,15 @@ private fun ParseAuthorCommentAndAmount(
onReady(content) onReady(content)
} }
fun click(content: MutableState<ZapAmountCommentNotification>, nav: (String) -> Unit) { fun click(content: ZapAmountCommentNotification, nav: (String) -> Unit) {
content.value.user?.let { content.user?.let {
nav(routeFor(it)) nav(routeFor(it))
} }
} }
@Composable @Composable
private fun RenderState( private fun RenderState(
content: MutableState<ZapAmountCommentNotification>, content: ZapAmountCommentNotification,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
@@ -401,41 +416,25 @@ val commentTextSize = 12.sp
@Composable @Composable
private fun DisplayAuthorCommentAndAmount( private fun DisplayAuthorCommentAndAmount(
authorComment: MutableState<ZapAmountCommentNotification>, authorComment: ZapAmountCommentNotification,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
Box(modifier = Size35Modifier, contentAlignment = Alignment.BottomCenter) { Box(modifier = Size35Modifier, contentAlignment = Alignment.BottomCenter) {
CrossfadeToDisplayPicture(authorComment, accountViewModel) WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(authorComment.user, accountViewModel)
CrossfadeToDisplayAmount(authorComment) authorComment.amount?.let {
CrossfadeToDisplayAmount(it)
}
} }
CrossfadeToDisplayComment(authorComment, backgroundColor, nav, accountViewModel) authorComment.comment?.let {
} CrossfadeToDisplayComment(it, backgroundColor, nav, accountViewModel)
@Composable
fun CrossfadeToDisplayPicture(authorComment: MutableState<ZapAmountCommentNotification>, accountViewModel: AccountViewModel) {
Crossfade(authorComment.value) {
WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(it.user, accountViewModel)
} }
} }
@Composable @Composable
fun CrossfadeToDisplayAmount(authorComment: MutableState<ZapAmountCommentNotification>) { fun CrossfadeToDisplayAmount(amount: String) {
val visible by remember(authorComment) {
derivedStateOf {
authorComment.value.amount != null
}
}
AnimatedVisibility(
visible = visible,
modifier = amountBoxModifier,
enter = fadeIn(),
exit = fadeOut()
) {
authorComment.value.amount?.let {
Box( Box(
modifier = amountBoxModifier, modifier = amountBoxModifier,
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter
@@ -450,7 +449,7 @@ fun CrossfadeToDisplayAmount(authorComment: MutableState<ZapAmountCommentNotific
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter
) { ) {
Text( Text(
text = it, text = amount,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.bitcoinColor, color = MaterialTheme.colors.bitcoinColor,
fontSize = commentTextSize, fontSize = commentTextSize,
@@ -458,31 +457,17 @@ fun CrossfadeToDisplayAmount(authorComment: MutableState<ZapAmountCommentNotific
) )
} }
} }
}
}
} }
@Composable @Composable
fun CrossfadeToDisplayComment( fun CrossfadeToDisplayComment(
authorComment: MutableState<ZapAmountCommentNotification>, comment: String,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val visible by remember(authorComment) {
derivedStateOf {
authorComment.value.comment != null
}
}
AnimatedVisibility(
visible,
enter = fadeIn(),
exit = fadeOut()
) {
authorComment.value.comment?.let {
TranslatableRichTextViewer( TranslatableRichTextViewer(
content = it, content = comment,
canPreview = true, canPreview = true,
tags = EmptyTagList, tags = EmptyTagList,
modifier = textBoxModifier, modifier = textBoxModifier,
@@ -490,8 +475,6 @@ fun CrossfadeToDisplayComment(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav nav = nav
) )
}
}
} }
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@@ -543,25 +526,25 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
WatchUserMetadata(author) { baseUserPicture -> WatchUserMetadata(author) { baseUserPicture ->
Crossfade(targetState = baseUserPicture) { userPicture -> // Crossfade(targetState = baseUserPicture) { userPicture ->
RobohashAsyncImageProxy( RobohashAsyncImageProxy(
robot = author.pubkeyHex, robot = author.pubkeyHex,
model = userPicture, model = baseUserPicture,
contentDescription = stringResource(id = R.string.profile_image), contentDescription = stringResource(id = R.string.profile_image),
modifier = MaterialTheme.colors.profile35dpModifier, modifier = MaterialTheme.colors.profile35dpModifier,
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
} // }
} }
WatchUserFollows(author.pubkeyHex, accountViewModel) { isFollowing -> WatchUserFollows(author.pubkeyHex, accountViewModel) { isFollowing ->
Crossfade(targetState = isFollowing) { // Crossfade(targetState = isFollowing) {
if (it) { if (isFollowing) {
Box(modifier = Size35Modifier, contentAlignment = Alignment.TopEnd) { Box(modifier = Size35Modifier, contentAlignment = Alignment.TopEnd) {
FollowingIcon(Size10dp) FollowingIcon(Size10dp)
} }
} }
} // }
} }
} }

View File

@@ -83,7 +83,6 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.ImageUrlType
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
import com.vitorpamplona.amethyst.ui.components.TextType import com.vitorpamplona.amethyst.ui.components.TextType
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.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DarkerGreen import com.vitorpamplona.amethyst.ui.theme.DarkerGreen
@@ -460,8 +459,15 @@ private fun WatchZapAndRenderGallery(
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val zapsState by baseNote.live().zaps.observeAsState() val zapsState by baseNote.live().zaps.observeAsState()
val zapEvents by remember(zapsState) {
derivedStateOf { baseNote.zaps.mapNotNull { it.value?.let { zapEvent -> CombinedZap(it.key, zapEvent) } }.toImmutableList() } var zapEvents by remember(zapsState) {
mutableStateOf<ImmutableList<ZapAmountCommentNotification>>(persistentListOf())
}
LaunchedEffect(key1 = zapsState) {
accountViewModel.decryptAmountMessageInGroup(baseNote) {
zapEvents = it
}
} }
if (zapEvents.isNotEmpty()) { if (zapEvents.isNotEmpty()) {

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
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.ui.screen.ZapReqResponse import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
@@ -40,7 +39,6 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -53,16 +51,9 @@ fun ZapNoteCompose(baseReqResponse: ZapReqResponse, accountViewModel: AccountVie
} }
LaunchedEffect(baseNoteRequest) { LaunchedEffect(baseNoteRequest) {
launch(Dispatchers.Default) {
(baseNoteRequest?.note?.event as? LnZapRequestEvent)?.let {
baseNoteRequest?.note?.let { baseNoteRequest?.note?.let {
val decryptedContent = accountViewModel.decryptZap(it) accountViewModel.decryptAmountMessage(it, baseReqResponse.zapEvent) {
if (decryptedContent != null) { baseAuthor = it?.user
baseAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
} else {
baseAuthor = it.author
}
}
} }
} }
} }

View File

@@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
@@ -182,6 +183,38 @@ class AccountViewModel(val account: Account) : ViewModel() {
} }
} }
fun decryptAmountMessageInGroup(
zaps: ImmutableList<CombinedZap>,
onNewState: (ImmutableList<ZapAmountCommentNotification>) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
val list = ArrayList<ZapAmountCommentNotification>(zaps.size)
zaps.forEach {
innerDecryptAmountMessage(it.request, it.response)?.let {
list.add(it)
}
}
onNewState(list.toImmutableList())
}
}
fun decryptAmountMessageInGroup(
baseNote: Note,
onNewState: (ImmutableList<ZapAmountCommentNotification>) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
val list = ArrayList<ZapAmountCommentNotification>(baseNote.zaps.size)
baseNote.zaps.forEach {
innerDecryptAmountMessage(it.key, it.value)?.let {
list.add(it)
}
}
onNewState(list.toImmutableList())
}
}
fun decryptAmountMessage( fun decryptAmountMessage(
zapRequest: Note, zapRequest: Note,
zapEvent: Note?, zapEvent: Note?,