From 99861facc9a7b4d26f924499eff21a2a0e145df4 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 25 Apr 2023 09:21:12 -0400 Subject: [PATCH] Improving the performance and UI of Zap notification messages. --- .../vitorpamplona/amethyst/model/Account.kt | 11 ++ .../amethyst/service/model/Event.kt | 2 +- .../service/model/LnZapRequestEvent.kt | 6 +- .../amethyst/ui/note/MultiSetCompose.kt | 151 +++++++++++------- .../ui/screen/loggedIn/AccountViewModel.kt | 5 + 5 files changed, 116 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 38e78fca5..f86103abb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -691,11 +691,22 @@ class Account( } event.plainContent(loggedIn.privKey!!, pubkeyToUse.toByteArray()) + } else if (event is LnZapRequestEvent && loggedIn.privKey != null) { + LnZapRequestEvent.checkForPrivateZap(event, loggedIn.privKey!!)?.content() } else { event?.content() } } + fun decryptZapContentAuthor(note: Note): Event? { + val event = note.event + return if (event is LnZapRequestEvent && loggedIn.privKey != null) { + LnZapRequestEvent.checkForPrivateZap(event, loggedIn.privKey!!) + } else { + null + } + } + fun addDontTranslateFrom(languageCode: String) { dontTranslateFrom = dontTranslateFrom.plus(languageCode) liveLanguages.invalidateData() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt index 59f7850e1..9b63fa799 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt @@ -19,7 +19,7 @@ open class Event( @SerializedName("created_at") val createdAt: Long, val kind: Int, val tags: List>, - var content: String, + val content: String, val sig: HexKey ) : EventInterface { override fun id(): HexKey = id diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt index 32583b549..b672fb570 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt @@ -149,9 +149,9 @@ class LnZapRequestEvent( } fun checkForPrivateZap(zaprequest: Event, loggedInUserPrivKey: ByteArray): Event? { - val anonTag = zaprequest.tags.firstOrNull { t -> t.count() >= 2 && t[0] == "anon" } - if (anonTag != null && anonTag.size > 1) { - val encnote = anonTag?.elementAt(1) + val anonTag = zaprequest.tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" } + if (anonTag != null) { + val encnote = anonTag[1] if (encnote != null && encnote != "") { try { val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, zaprequest.pubKey.toByteArray()) 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 5148c0632..0c848a23f 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 @@ -6,6 +6,7 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -36,10 +37,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.NostrAccountDataSource import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent -import com.vitorpamplona.amethyst.service.model.Event -import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer @@ -86,9 +84,11 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun .combinedClickable( onClick = { if (noteEvent is ChannelMessageEvent) { - note.channel()?.let { - navController.navigate("Channel/${it.idHex}") - } + note + .channel() + ?.let { + navController.navigate("Channel/${it.idHex}") + } } else if (noteEvent is PrivateDmEvent) { val replyAuthorBase = (note.event as? PrivateDmEvent) @@ -140,16 +140,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun ) } - for (i in multiSetCard.zapEvents) { - var decryptedContent = (i.value.event as LnZapEvent).zapRequest?.let { - LnZapRequestEvent.checkForPrivateZap(it, NostrAccountDataSource.account.loggedIn.privKey!!) - } - if (decryptedContent != null) { - (i.key.event as Event).content = decryptedContent.content - i.key.author = LocalCache.getOrCreateUser(decryptedContent.pubKey) - } - } - AuthorGallery(multiSetCard.zapEvents.keys, navController, account, accountViewModel, "zap") + AuthorGalleryZaps(multiSetCard.zapEvents, navController, account, accountViewModel) } } @@ -222,12 +213,11 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun } @Composable -fun AuthorGallery( - authorNotes: Collection, +fun AuthorGalleryZaps( + authorNotes: Map, navController: NavController, account: Account, - accountViewModel: AccountViewModel, - kind: String = "nonzap" + accountViewModel: AccountViewModel ) { val accountState by account.userProfile().live().follows.observeAsState() val accountUser = accountState?.user ?: return @@ -235,38 +225,92 @@ fun AuthorGallery( Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { - if (it.event?.content() != "" && kind == "zap") { - Row(Modifier.fillMaxWidth()) { - FastNoteAuthorPicture( - note = it, - navController = navController, - userAccount = accountUser, - size = 35.dp - ) - } - } else { - Row() { - FastNoteAuthorPicture( - note = it, - navController = navController, - userAccount = accountUser, - size = 35.dp - ) - } + AuthorPictureAndComment(it.key, it.value, navController, accountUser, accountViewModel) + } + } + } +} + +@Composable +private fun AuthorPictureAndComment( + zapRequest: Note, + zapEvent: Note, + navController: NavController, + accountUser: User, + accountViewModel: AccountViewModel +) { + var content by remember { mutableStateOf>(Pair(zapRequest.author!!, null)) } + + LaunchedEffect(key1 = zapRequest.idHex) { + (zapRequest.event as? LnZapRequestEvent)?.let { + val decryptedContent = accountViewModel.decryptZap(zapRequest) + if (decryptedContent != null) { + val author = LocalCache.getOrCreateUser(decryptedContent.pubKey) + content = Pair(author, decryptedContent.content) + } else { + if (!zapRequest.event?.content().isNullOrBlank()) { + content = Pair(zapRequest.author!!, zapRequest.event?.content()) } - if (it.event?.content() != "" && kind == "zap") { - Row(Modifier.fillMaxWidth()) { - it.event?.let { - TranslatableRichTextViewer( - content = it.content(), - canPreview = true, - tags = null, - backgroundColor = MaterialTheme.colors.background, - accountViewModel = accountViewModel, - navController = navController - ) - } - } + } + } + } + + AuthorPictureAndComment(content.first, content.second, navController, accountUser, accountViewModel) +} + +@Composable +private fun AuthorPictureAndComment( + author: User, + comment: String?, + navController: NavController, + accountUser: User, + accountViewModel: AccountViewModel +) { + val modifier = if (!comment.isNullOrBlank()) { + Modifier.fillMaxWidth() + } else { + Modifier + } + + Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + FastNoteAuthorPicture( + author = author, + navController = navController, + userAccount = accountUser, + size = 35.dp + ) + + if (!comment.isNullOrBlank()) { + Spacer(modifier = Modifier.width(5.dp)) + TranslatableRichTextViewer( + content = comment, + canPreview = false, + tags = null, + modifier = Modifier.weight(1f), + backgroundColor = MaterialTheme.colors.background, + accountViewModel = accountViewModel, + navController = navController + ) + } + } +} + +@Composable +fun AuthorGallery( + authorNotes: Collection, + navController: NavController, + account: Account, + accountViewModel: AccountViewModel +) { + val accountState by account.userProfile().live().follows.observeAsState() + val accountUser = accountState?.user ?: return + + Column(modifier = Modifier.padding(start = 10.dp)) { + FlowRow() { + authorNotes.forEach { + val author = it.author + if (author != null) { + AuthorPictureAndComment(author, null, navController, accountUser, accountViewModel) } } } @@ -275,15 +319,12 @@ fun AuthorGallery( @Composable fun FastNoteAuthorPicture( - note: Note, + author: User, navController: NavController, userAccount: User, size: Dp, pictureModifier: Modifier = Modifier ) { - // can't be null if here - val author = note.author ?: return - val userState by author.live().metadata.observeAsState() val user = userState?.user ?: return 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 638856e1d..7c3f24079 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 @@ -14,6 +14,7 @@ import com.vitorpamplona.amethyst.model.AccountState import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver +import com.vitorpamplona.amethyst.service.model.Event import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse import com.vitorpamplona.amethyst.service.model.ReportEvent @@ -164,6 +165,10 @@ class AccountViewModel(private val account: Account) : ViewModel() { return account.decryptContent(note) } + fun decryptZap(note: Note): Event? { + return account.decryptZapContentAuthor(note) + } + fun hide(user: User) { account.hideUser(user.pubkeyHex) }