From 953f1fe66c76e47b2b0e93eaf5a0763ff79cda76 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 23:37:43 +0000 Subject: [PATCH] feat: add design-time previews for the activity cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five previews covering the gradient cards: like (+ heart), custom emoji reaction, lightning zap, cashu nutzap, and onchain zap — each rendered in the light/dark ThemeComparisonColumn with a hand-built target note so the embedded makeItShort post shows. RenderLnZap splits into a thin resolver plus a stateless RenderLnZapCard so the preview can feed the decrypted sender/amount/comment directly instead of waiting on async decryption. https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa --- .../ui/note/types/ActivityCardPreviews.kt | 225 ++++++++++++++++++ .../amethyst/ui/note/types/ZapEvent.kt | 23 +- 2 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ActivityCardPreviews.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ActivityCardPreviews.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ActivityCardPreviews.kt new file mode 100644 index 0000000000..8271e033b1 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ActivityCardPreviews.kt @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.note.types + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav +import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification +import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent +import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent +import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent +import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent + +/** + * Design-time fixtures for the activity cards (reaction / lightning zap / + * cashu nutzap / onchain zap). The notes are built by hand and never consumed + * into [LocalCache], except for the users behind the avatars. + */ +private object ActivityCardPreviewData { + val senderHex = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c" + val authorHex = "ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b" + const val SIG = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + const val TIME = 1_700_000_000L + + val targetId = "a".repeat(64) + + fun targetNote(): Note = + Note(targetId).apply { + event = + TextNoteEvent( + targetId, + authorHex, + TIME, + emptyArray(), + "Just finished the new release — zaps, nests, and a faster feed. What should we build next?", + SIG, + ) + author = LocalCache.getOrCreateUser(authorHex) + replyTo = emptyList() + } + + fun reactionNote(reaction: String): Note { + val id = "b".repeat(64) + return Note(id).apply { + event = + ReactionEvent( + id, + senderHex, + TIME, + arrayOf(arrayOf("e", targetId), arrayOf("p", authorHex)), + reaction, + SIG, + ) + author = LocalCache.getOrCreateUser(senderHex) + replyTo = listOf(targetNote()) + } + } + + fun nutzapNote(): Note { + val id = "c".repeat(64) + return Note(id).apply { + event = + NutzapEvent( + id, + senderHex, + TIME, + arrayOf( + arrayOf("proof", """{"amount":2100,"id":"00ad268c4d1f5826","secret":"s","C":"c"}"""), + arrayOf("u", "https://mint.example.com"), + arrayOf("e", targetId), + arrayOf("p", authorHex), + ), + "Have some cashu, great post!", + SIG, + ) + author = LocalCache.getOrCreateUser(senderHex) + replyTo = listOf(targetNote()) + } + } + + fun lnZapNote(): Note { + val id = "d".repeat(64) + // Receipts are signed by the lightning provider; the preview skips the + // embedded request and feeds the decrypted card to RenderLnZapCard directly. + return Note(id).apply { + event = null + author = null + replyTo = listOf(targetNote()) + } + } + + fun onchainZapNote(): Note { + val id = "e".repeat(64) + return Note(id).apply { + event = + OnchainZapEvent( + id, + senderHex, + TIME, + arrayOf( + arrayOf("i", "bitcoin:tx:" + "ab".repeat(32)), + arrayOf("amount", "150000"), + arrayOf("e", targetId), + arrayOf("p", authorHex), + ), + "Welcome to the timechain!", + SIG, + ) + author = LocalCache.getOrCreateUser(senderHex) + replyTo = listOf(targetNote()) + } + } +} + +@Preview +@Composable +fun LikeActivityCardPreview() { + val accountViewModel = mockAccountViewModel() + + ThemeComparisonColumn { + RenderReaction( + note = ActivityCardPreviewData.reactionNote("+"), + quotesLeft = 1, + backgroundColor = remember { mutableStateOf(Color.Transparent) }, + accountViewModel = accountViewModel, + nav = EmptyNav(), + ) + } +} + +@Preview +@Composable +fun EmojiReactionActivityCardPreview() { + val accountViewModel = mockAccountViewModel() + + ThemeComparisonColumn { + RenderReaction( + note = ActivityCardPreviewData.reactionNote("🤙"), + quotesLeft = 1, + backgroundColor = remember { mutableStateOf(Color.Transparent) }, + accountViewModel = accountViewModel, + nav = EmptyNav(), + ) + } +} + +@Preview +@Composable +fun LnZapActivityCardPreview() { + val accountViewModel = mockAccountViewModel() + + ThemeComparisonColumn { + RenderLnZapCard( + note = ActivityCardPreviewData.lnZapNote(), + card = + ZapAmountCommentNotification( + user = LocalCache.getOrCreateUser(ActivityCardPreviewData.senderHex), + comment = "Great post! ⚡", + amount = "100k", + ), + recipientKey = ActivityCardPreviewData.authorHex, + quotesLeft = 1, + backgroundColor = remember { mutableStateOf(Color.Transparent) }, + accountViewModel = accountViewModel, + nav = EmptyNav(), + ) + } +} + +@Preview +@Composable +fun NutzapActivityCardPreview() { + val accountViewModel = mockAccountViewModel() + + ThemeComparisonColumn { + RenderNutzap( + note = ActivityCardPreviewData.nutzapNote(), + quotesLeft = 1, + backgroundColor = remember { mutableStateOf(Color.Transparent) }, + accountViewModel = accountViewModel, + nav = EmptyNav(), + ) + } +} + +@Preview +@Composable +fun OnchainZapActivityCardPreview() { + val accountViewModel = mockAccountViewModel() + + ThemeComparisonColumn { + RenderOnchainZap( + note = ActivityCardPreviewData.onchainZapNote(), + quotesLeft = 1, + backgroundColor = remember { mutableStateOf(Color.Transparent) }, + accountViewModel = accountViewModel, + nav = EmptyNav(), + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ZapEvent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ZapEvent.kt index 27a4f89786..3330a012fb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ZapEvent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ZapEvent.kt @@ -103,7 +103,28 @@ fun RenderLnZap( val zapEvent = note.event as? LnZapEvent ?: return val card by parseAuthorCommentAndAmount(note, accountViewModel) - val recipientKey = zapEvent.zappedAuthor().firstOrNull() + + RenderLnZapCard( + note = note, + card = card, + recipientKey = zapEvent.zappedAuthor().firstOrNull(), + quotesLeft = quotesLeft, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) +} + +@Composable +fun RenderLnZapCard( + note: Note, + card: ZapAmountCommentNotification, + recipientKey: String?, + quotesLeft: Int, + backgroundColor: MutableState, + accountViewModel: AccountViewModel, + nav: INav, +) { val orange = MaterialTheme.colorScheme.bitcoinColor ActivityCardFrame(orange) {