From 11b062c41f31405ef229f9aff47fcb8a5b6e0f26 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 20 Sep 2023 09:37:56 -0400 Subject: [PATCH] Moves zap decryption to run in a group, avoiding multiple co-routines per zap --- .../amethyst/ui/note/MultiSetCompose.kt | 197 ++++++++---------- .../amethyst/ui/note/ReactionsRow.kt | 12 +- .../amethyst/ui/note/ZapNoteCompose.kt | 15 +- .../ui/screen/loggedIn/AccountViewModel.kt | 33 +++ 4 files changed, 135 insertions(+), 122 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 601e1f535..435fc4968 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -1,10 +1,7 @@ package com.vitorpamplona.amethyst.ui.note import android.util.Log -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -33,6 +30,7 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.quartz.events.EmptyTagList import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -172,7 +171,6 @@ private fun Galeries( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } } val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } } val likeEvents by remember { derivedStateOf { multiSetCard.likeEventsByType } } @@ -181,6 +179,16 @@ private fun Galeries( val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } } if (hasZapEvents) { + var zapEvents by remember(multiSetCard) { + mutableStateOf>(persistentListOf()) + } + + LaunchedEffect(key1 = Unit) { + accountViewModel.decryptAmountMessageInGroup(multiSetCard.zapEvents) { + zapEvents = it + } + } + val (value, elapsed) = measureTimedValue { RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel) } @@ -250,7 +258,7 @@ fun RenderLikeGallery( @Composable fun RenderZapGallery( - zapEvents: ImmutableList, + zapEvents: ImmutableList, backgroundColor: MutableState, nav: (String) -> Unit, accountViewModel: AccountViewModel @@ -297,10 +305,29 @@ fun RenderBoostGallery( } } +@Composable +fun MapZaps( + zaps: ImmutableList, + accountViewModel: AccountViewModel, + content: @Composable (ImmutableList) -> Unit +) { + var zapEvents by remember(zaps) { + mutableStateOf>(persistentListOf()) + } + + LaunchedEffect(key1 = Unit) { + accountViewModel.decryptAmountMessageInGroup(zaps) { + zapEvents = it + } + } + + content(zapEvents) +} + @OptIn(ExperimentalLayoutApi::class) @Composable fun AuthorGalleryZaps( - authorNotes: ImmutableList, + authorNotes: ImmutableList, backgroundColor: MutableState, nav: (String) -> Unit, accountViewModel: AccountViewModel @@ -308,24 +335,12 @@ fun AuthorGalleryZaps( Column(modifier = StdStartPadding) { FlowRow() { authorNotes.forEach { - ParseAuthorCommentAndAmount(it, backgroundColor, nav, accountViewModel) + RenderState(it, backgroundColor, accountViewModel, nav) } } } } -@Composable -private fun ParseAuthorCommentAndAmount( - zap: CombinedZap, - backgroundColor: MutableState, - nav: (String) -> Unit, - accountViewModel: AccountViewModel -) { - ParseAuthorCommentAndAmount(zap.request, zap.response, accountViewModel) { state -> - RenderState(state, backgroundColor, accountViewModel, nav) - } -} - @Immutable data class ZapAmountCommentNotification( val user: User?, @@ -361,15 +376,15 @@ private fun ParseAuthorCommentAndAmount( onReady(content) } -fun click(content: MutableState, nav: (String) -> Unit) { - content.value.user?.let { +fun click(content: ZapAmountCommentNotification, nav: (String) -> Unit) { + content.user?.let { nav(routeFor(it)) } } @Composable private fun RenderState( - content: MutableState, + content: ZapAmountCommentNotification, backgroundColor: MutableState, accountViewModel: AccountViewModel, nav: (String) -> Unit @@ -401,97 +416,65 @@ val commentTextSize = 12.sp @Composable private fun DisplayAuthorCommentAndAmount( - authorComment: MutableState, + authorComment: ZapAmountCommentNotification, backgroundColor: MutableState, nav: (String) -> Unit, accountViewModel: AccountViewModel ) { Box(modifier = Size35Modifier, contentAlignment = Alignment.BottomCenter) { - CrossfadeToDisplayPicture(authorComment, accountViewModel) - CrossfadeToDisplayAmount(authorComment) - } - - CrossfadeToDisplayComment(authorComment, backgroundColor, nav, accountViewModel) -} - -@Composable -fun CrossfadeToDisplayPicture(authorComment: MutableState, accountViewModel: AccountViewModel) { - Crossfade(authorComment.value) { - WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(it.user, accountViewModel) - } -} - -@Composable -fun CrossfadeToDisplayAmount(authorComment: MutableState) { - val visible by remember(authorComment) { - derivedStateOf { - authorComment.value.amount != null + WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(authorComment.user, accountViewModel) + authorComment.amount?.let { + CrossfadeToDisplayAmount(it) } } - AnimatedVisibility( - visible = visible, + authorComment.comment?.let { + CrossfadeToDisplayComment(it, backgroundColor, nav, accountViewModel) + } +} + +@Composable +fun CrossfadeToDisplayAmount(amount: String) { + Box( modifier = amountBoxModifier, - enter = fadeIn(), - exit = fadeOut() + contentAlignment = Alignment.BottomCenter ) { - authorComment.value.amount?.let { - Box( - modifier = amountBoxModifier, - contentAlignment = Alignment.BottomCenter - ) { - val backgroundColor = MaterialTheme.colors.overPictureBackground - Box( - modifier = remember { - Modifier - .width(Size35dp) - .background(backgroundColor) - }, - contentAlignment = Alignment.BottomCenter - ) { - Text( - text = it, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colors.bitcoinColor, - fontSize = commentTextSize, - modifier = bottomPadding1dp - ) - } - } + val backgroundColor = MaterialTheme.colors.overPictureBackground + Box( + modifier = remember { + Modifier + .width(Size35dp) + .background(backgroundColor) + }, + contentAlignment = Alignment.BottomCenter + ) { + Text( + text = amount, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colors.bitcoinColor, + fontSize = commentTextSize, + modifier = bottomPadding1dp + ) } } } @Composable fun CrossfadeToDisplayComment( - authorComment: MutableState, + comment: String, backgroundColor: MutableState, nav: (String) -> Unit, accountViewModel: AccountViewModel ) { - val visible by remember(authorComment) { - derivedStateOf { - authorComment.value.comment != null - } - } - - AnimatedVisibility( - visible, - enter = fadeIn(), - exit = fadeOut() - ) { - authorComment.value.comment?.let { - TranslatableRichTextViewer( - content = it, - canPreview = true, - tags = EmptyTagList, - modifier = textBoxModifier, - backgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav - ) - } - } + TranslatableRichTextViewer( + content = comment, + canPreview = true, + tags = EmptyTagList, + modifier = textBoxModifier, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav + ) } @OptIn(ExperimentalLayoutApi::class) @@ -543,25 +526,25 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture( accountViewModel: AccountViewModel ) { WatchUserMetadata(author) { baseUserPicture -> - Crossfade(targetState = baseUserPicture) { userPicture -> - RobohashAsyncImageProxy( - robot = author.pubkeyHex, - model = userPicture, - contentDescription = stringResource(id = R.string.profile_image), - modifier = MaterialTheme.colors.profile35dpModifier, - contentScale = ContentScale.Crop - ) - } + // Crossfade(targetState = baseUserPicture) { userPicture -> + RobohashAsyncImageProxy( + robot = author.pubkeyHex, + model = baseUserPicture, + contentDescription = stringResource(id = R.string.profile_image), + modifier = MaterialTheme.colors.profile35dpModifier, + contentScale = ContentScale.Crop + ) + // } } WatchUserFollows(author.pubkeyHex, accountViewModel) { isFollowing -> - Crossfade(targetState = isFollowing) { - if (it) { - Box(modifier = Size35Modifier, contentAlignment = Alignment.TopEnd) { - FollowingIcon(Size10dp) - } + // Crossfade(targetState = isFollowing) { + if (isFollowing) { + Box(modifier = Size35Modifier, contentAlignment = Alignment.TopEnd) { + FollowingIcon(Size10dp) } } + // } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index e0d3e6835..41f1bf22a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -83,7 +83,6 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer 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.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DarkerGreen @@ -460,8 +459,15 @@ private fun WatchZapAndRenderGallery( accountViewModel: AccountViewModel ) { 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>(persistentListOf()) + } + + LaunchedEffect(key1 = zapsState) { + accountViewModel.decryptAmountMessageInGroup(baseNote) { + zapEvents = it + } } if (zapEvents.isNotEmpty()) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 5326a3bc8..3d4319459 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User 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.placeholderText import com.vitorpamplona.quartz.events.LnZapEvent -import com.vitorpamplona.quartz.events.LnZapRequestEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -53,16 +51,9 @@ fun ZapNoteCompose(baseReqResponse: ZapReqResponse, accountViewModel: AccountVie } LaunchedEffect(baseNoteRequest) { - launch(Dispatchers.Default) { - (baseNoteRequest?.note?.event as? LnZapRequestEvent)?.let { - baseNoteRequest?.note?.let { - val decryptedContent = accountViewModel.decryptZap(it) - if (decryptedContent != null) { - baseAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) - } else { - baseAuthor = it.author - } - } + baseNoteRequest?.note?.let { + accountViewModel.decryptAmountMessage(it, baseReqResponse.zapEvent) { + baseAuthor = it?.user } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 562f0a826..2759aae2c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.ui.components.UrlPreviewState import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus 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.HexKey import com.vitorpamplona.quartz.events.Event @@ -182,6 +183,38 @@ class AccountViewModel(val account: Account) : ViewModel() { } } + fun decryptAmountMessageInGroup( + zaps: ImmutableList, + onNewState: (ImmutableList) -> Unit + ) { + viewModelScope.launch(Dispatchers.IO) { + val list = ArrayList(zaps.size) + zaps.forEach { + innerDecryptAmountMessage(it.request, it.response)?.let { + list.add(it) + } + } + + onNewState(list.toImmutableList()) + } + } + + fun decryptAmountMessageInGroup( + baseNote: Note, + onNewState: (ImmutableList) -> Unit + ) { + viewModelScope.launch(Dispatchers.IO) { + val list = ArrayList(baseNote.zaps.size) + baseNote.zaps.forEach { + innerDecryptAmountMessage(it.key, it.value)?.let { + list.add(it) + } + } + + onNewState(list.toImmutableList()) + } + } + fun decryptAmountMessage( zapRequest: Note, zapEvent: Note?,