Revert "feat: modernize reaction and zap cards into activity cards"

This reverts commit 11b991ef50.
This commit is contained in:
Claude
2026-06-12 22:16:42 +00:00
parent 11b991ef50
commit cb9580941c
7 changed files with 85 additions and 225 deletions
@@ -90,7 +90,6 @@ import com.vitorpamplona.amethyst.ui.note.elements.Reward
import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation
import com.vitorpamplona.amethyst.ui.note.elements.StaleRelayHint
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
import com.vitorpamplona.amethyst.ui.note.types.ActivityActionChip
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.types.DisplayBlockedRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayBroadcastRelayList
@@ -1737,22 +1736,18 @@ fun FirstUserInfoRow(
null
}
Row(Modifier.weight(1f), verticalAlignment = CenterVertically) {
if (zapSender != null) {
if (showAuthorPicture) {
UserPicture(zapSender, Size25dp, accountViewModel = accountViewModel, nav = nav)
Spacer(HalfPadding)
}
UsernameDisplay(zapSender, Modifier.weight(1f, fill = false), textColor = textColor, accountViewModel = accountViewModel)
} else if (showAuthorPicture) {
NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav)
if (zapSender != null) {
if (showAuthorPicture) {
UserPicture(zapSender, Size25dp, accountViewModel = accountViewModel, nav = nav)
Spacer(HalfPadding)
NoteUsernameDisplay(baseNote, Modifier.weight(1f, fill = false), textColor = textColor, accountViewModel = accountViewModel)
} else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f, fill = false), textColor = textColor, accountViewModel = accountViewModel)
}
ActivityActionChip(baseNote, accountViewModel, nav)
UsernameDisplay(zapSender, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel)
} else if (showAuthorPicture) {
NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav)
Spacer(HalfPadding)
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel)
} else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel)
}
if (isDraft) {
@@ -1,167 +0,0 @@
/*
* 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.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.commons.hashtags.Cashu
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.LikedIcon
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.ZapIcon
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.note.showAmountInteger
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
import com.vitorpamplona.amethyst.ui.theme.Size19Modifier
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.bitcoinColor
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.claimedSatsTotal
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
import androidx.compose.material3.Icon as Material3Icon
/**
* Inline "activity" chip for the author header of reaction and zap cards:
* the action (emoji, or amount recipient) renders next to the actor's name,
* so the card reads as "Alice ❤️" / "Alice ⚡ 500 → Bob" with the target post
* quoted below. Renders nothing for other kinds.
*/
@Composable
fun ActivityActionChip(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: INav,
) {
when (val event = baseNote.event) {
is ReactionEvent -> {
Spacer(HalfPadding)
ReactionActionChip(event.content)
}
is LnZapEvent -> {
Spacer(HalfPadding)
AmountToRecipientChip(
amount = showAmountInteger(event.amount),
recipient = event.zappedAuthor().firstOrNull(),
accountViewModel = accountViewModel,
nav = nav,
) {
ZapIcon(Size19Modifier, MaterialTheme.colorScheme.bitcoinColor)
}
}
is NutzapEvent -> {
Spacer(HalfPadding)
AmountToRecipientChip(
amount = showAmount(BigDecimal(event.claimedSatsTotal())),
recipient = event.linkedPubKeys().firstOrNull(),
accountViewModel = accountViewModel,
nav = nav,
) {
Material3Icon(
imageVector = CustomHashTagIcons.Cashu,
contentDescription = null,
modifier = Size16Modifier,
)
}
}
}
}
@Composable
private fun ReactionActionChip(reactionType: String) {
if (reactionType.startsWith(":")) {
val url = reactionType.removePrefix(":").substringAfter(":")
InLineIconRenderer(
persistentListOf(CustomEmoji.ImageUrlType(url)),
style = SpanStyle(color = MaterialTheme.colorScheme.onBackground),
maxLines = 1,
fontSize = 16.sp,
)
} else {
when (reactionType) {
"+" -> LikedIcon(Size19Modifier)
"-" ->
Text(
text = "👎",
maxLines = 1,
fontSize = 16.sp,
)
else ->
Text(
text = reactionType,
maxLines = 1,
fontSize = 16.sp,
)
}
}
}
@Composable
private fun AmountToRecipientChip(
amount: String,
recipient: HexKey?,
accountViewModel: AccountViewModel,
nav: INav,
icon: @Composable () -> Unit,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
icon()
Text(
text = amount,
color = MaterialTheme.colorScheme.bitcoinColor,
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
maxLines = 1,
)
recipient?.let {
Icon(
symbol = MaterialSymbols.AutoMirrored.ArrowForwardIos,
contentDescription = null,
tint = MaterialTheme.colorScheme.bitcoinColor,
modifier = Size16Modifier,
)
UserPicture(it, Size20dp, accountViewModel = accountViewModel, nav = nav)
}
}
}
@@ -20,23 +20,23 @@
*/
package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.CrossfadeToDisplayComment
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.claimedSatsTotal
import java.math.BigDecimal
/**
* Renders a NIP-61 nutzap (kind 9321) as an activity card, like lightning zaps.
* Renders a NIP-61 nutzap (kind 9321) as a transfer card, like lightning zaps.
* Unlike kind 9735 receipts, the nutzap is signed by the sender, so [Note.author]
* is already the right person to attribute; the amount and recipient render in
* the card header (ActivityActionChip). The body is the comment, when present,
* over the quoted target post.
* is already the right person to attribute.
*/
@Composable
fun RenderNutzap(
@@ -48,10 +48,25 @@ fun RenderNutzap(
) {
val nutzapEvent = note.event as? NutzapEvent ?: return
nutzapEvent.content.ifBlank { null }?.let {
CrossfadeToDisplayComment(it, backgroundColor, nav, accountViewModel)
Spacer(StdVertSpacer)
}
val recipientKey = nutzapEvent.linkedPubKeys().firstOrNull() ?: return
RenderActionTarget(note, quotesLeft, backgroundColor, accountViewModel, nav)
RenderZappedPost(note, quotesLeft, backgroundColor, accountViewModel, nav)
val card =
remember(note) {
ZapAmountCommentNotification(
user = note.author,
comment = nutzapEvent.content.ifBlank { null },
amount = showAmount(BigDecimal(nutzapEvent.claimedSatsTotal())),
zapNote = note,
)
}
TransferCard(
card,
recipientKey,
backgroundColor,
accountViewModel = accountViewModel,
nav = nav,
)
}
@@ -99,7 +99,7 @@ fun RenderOnchainZap(
val sender = note.author?.pubkeyHex ?: event.pubKey
val recipient = event.recipient() ?: return
RenderActionTarget(note, quotesLeft, backgroundColor, accountViewModel, nav)
RenderZappedPost(note, quotesLeft, backgroundColor, accountViewModel, nav)
val sats = event.claimedAmountInSats() ?: 0L
val txid = event.txid()
val message = event.content.takeIf { it.isNotBlank() }
@@ -20,11 +20,14 @@
*/
package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable
@@ -35,7 +38,25 @@ fun RenderReaction(
accountViewModel: AccountViewModel,
nav: INav,
) {
// The reaction emoji renders in the card header (ActivityActionChip);
// the body is just the post being reacted to, framed as a quote.
RenderActionTarget(note, quotesLeft, backgroundColor, accountViewModel, nav)
note.replyTo?.lastOrNull()?.let {
NoteCompose(
it,
modifier = Modifier,
isBoostedNote = true,
makeItShort = true,
unPackReply = ReplyRenderType.NONE,
quotesLeft = quotesLeft - 1,
parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel,
nav = nav,
)
}
// Reposts have trash in their contents.
val refactorReactionText = if (note.event?.content == "+") "" else note.event?.content ?: ""
Text(
text = refactorReactionText,
maxLines = 1,
)
}
@@ -55,32 +55,30 @@ import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.bitcoinColor
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/**
* Shows the post a reaction or zap targets, framed as a quote so it reads as
* subordinate to the activity card around it.
* Shows the post a zap targets above the transfer card, mirroring how
* reactions and reposts embed their target.
*/
@Composable
fun RenderActionTarget(
actionNote: Note,
fun RenderZappedPost(
zapNote: Note,
quotesLeft: Int,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: INav,
) {
actionNote.replyTo?.lastOrNull()?.let {
zapNote.replyTo?.lastOrNull()?.let {
NoteCompose(
it,
modifier = MaterialTheme.colorScheme.replyModifier,
isQuotedNote = true,
modifier = Modifier,
isBoostedNote = true,
makeItShort = true,
unPackReply = ReplyRenderType.NONE,
quotesLeft = quotesLeft - 1,
@@ -99,19 +97,22 @@ fun RenderLnZap(
accountViewModel: AccountViewModel,
nav: INav,
) {
if (note.event !is LnZapEvent) return
val zapEvent = note.event as? LnZapEvent ?: return
RenderZappedPost(note, quotesLeft, backgroundColor, accountViewModel, nav)
// The amount and sender render in the card header (ActivityActionChip plus
// the zap-sender attribution); the body is the zap comment, when present,
// over the quoted target post.
val card by parseAuthorCommentAndAmount(note, accountViewModel)
card.comment?.let {
CrossfadeToDisplayComment(it, backgroundColor, nav, accountViewModel)
Spacer(StdVertSpacer)
}
val destinationKey = zapEvent.zappedAuthor().firstOrNull() ?: return
RenderActionTarget(note, quotesLeft, backgroundColor, accountViewModel, nav)
TransferCard(
card,
destinationKey,
backgroundColor,
Modifier,
accountViewModel,
nav,
)
}
/**
@@ -124,7 +124,6 @@ import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
import com.vitorpamplona.amethyst.ui.note.observeEdits
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.note.types.ActivityActionChip
import com.vitorpamplona.amethyst.ui.note.types.AudioHeader
import com.vitorpamplona.amethyst.ui.note.types.AudioTrackHeader
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
@@ -554,14 +553,10 @@ private fun FullBleedNoteCompose(
Column(modifier = Modifier.padding(start = 10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
if (zapSender != null) {
UsernameDisplay(zapSender, Modifier.weight(1f, fill = false), accountViewModel = accountViewModel)
} else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f, fill = false), accountViewModel = accountViewModel)
}
ActivityActionChip(baseNote, accountViewModel, nav)
if (zapSender != null) {
UsernameDisplay(zapSender, Modifier.weight(1f), accountViewModel = accountViewModel)
} else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel)
}
if (isDraft) {