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,97 +416,65 @@ 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)
}
@Composable
fun CrossfadeToDisplayPicture(authorComment: MutableState<ZapAmountCommentNotification>, accountViewModel: AccountViewModel) {
Crossfade(authorComment.value) {
WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(it.user, accountViewModel)
}
}
@Composable
fun CrossfadeToDisplayAmount(authorComment: MutableState<ZapAmountCommentNotification>) {
val visible by remember(authorComment) {
derivedStateOf {
authorComment.value.amount != null
} }
} }
AnimatedVisibility( authorComment.comment?.let {
visible = visible, CrossfadeToDisplayComment(it, backgroundColor, nav, accountViewModel)
}
}
@Composable
fun CrossfadeToDisplayAmount(amount: String) {
Box(
modifier = amountBoxModifier, modifier = amountBoxModifier,
enter = fadeIn(), contentAlignment = Alignment.BottomCenter
exit = fadeOut()
) { ) {
authorComment.value.amount?.let { val backgroundColor = MaterialTheme.colors.overPictureBackground
Box( Box(
modifier = amountBoxModifier, modifier = remember {
contentAlignment = Alignment.BottomCenter Modifier
) { .width(Size35dp)
val backgroundColor = MaterialTheme.colors.overPictureBackground .background(backgroundColor)
Box( },
modifier = remember { contentAlignment = Alignment.BottomCenter
Modifier ) {
.width(Size35dp) Text(
.background(backgroundColor) text = amount,
}, fontWeight = FontWeight.Bold,
contentAlignment = Alignment.BottomCenter color = MaterialTheme.colors.bitcoinColor,
) { fontSize = commentTextSize,
Text( modifier = bottomPadding1dp
text = it, )
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.bitcoinColor,
fontSize = commentTextSize,
modifier = bottomPadding1dp
)
}
}
} }
} }
} }
@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) { TranslatableRichTextViewer(
derivedStateOf { content = comment,
authorComment.value.comment != null canPreview = true,
} tags = EmptyTagList,
} modifier = textBoxModifier,
backgroundColor = backgroundColor,
AnimatedVisibility( accountViewModel = accountViewModel,
visible, nav = nav
enter = fadeIn(), )
exit = fadeOut()
) {
authorComment.value.comment?.let {
TranslatableRichTextViewer(
content = it,
canPreview = true,
tags = EmptyTagList,
modifier = textBoxModifier,
backgroundColor = backgroundColor,
accountViewModel = accountViewModel,
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?.let {
(baseNoteRequest?.note?.event as? LnZapRequestEvent)?.let { accountViewModel.decryptAmountMessage(it, baseReqResponse.zapEvent) {
baseNoteRequest?.note?.let { baseAuthor = it?.user
val decryptedContent = accountViewModel.decryptZap(it)
if (decryptedContent != null) {
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?,