From a83db4c18f6d20e988afba40e69a0780c2c7e91d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Jun 2026 22:05:49 +0000 Subject: [PATCH] feat: render a lone zap/nutzap notification as a large activity card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A MultiSetCard bundles many notifications on a post into one compact stream of icons, which is great when there are several. But a freshly-arrived zap is usually alone in its own card (the additive feed path builds a single-item MultiSetCard, and only a full rebuild groups same-post notifications together), so it rendered as one tiny gallery icon. When a MultiSetCard carries a single lightning zap or nutzap and nothing else, render the existing large activity card (RenderLnZap / RenderNutzap) — the same big, gradient "appreciation" display already used for onchain zaps and the thread view — instead of the one-icon gallery. As soon as a rebuild groups several notifications onto the same post (size > 1), it falls back to the compact gallery as before. This is purely a rendering decision in MultiSetCompose; the card-building and grouping logic is unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01X8ZruubvykNhfmrJXJA8s7 --- .../amethyst/ui/note/MultiSetCompose.kt | 92 +++++++++++++++---- 1 file changed, 75 insertions(+), 17 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 5ad8181715..4a6c4afacf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -87,6 +87,8 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.authorRouteFor import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu +import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap +import com.vitorpamplona.amethyst.ui.note.types.RenderNutzap import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.MultiSetCard @@ -132,6 +134,39 @@ fun MultiSetCompose( val scope = rememberCoroutineScope() + // A MultiSetCard that carries a single zap or nutzap — the common case for a + // freshly-arrived notification appended at the top of the feed — is rendered + // as a large activity card, the same big display used for onchain zaps and the + // thread view, instead of a one-icon gallery. Once a full rebuild groups + // several reactions/zaps on the same post into one card (size > 1), it falls + // back to the compact gallery below. This is purely a rendering decision: the + // card-building logic is unchanged, so the additive path naturally produces the + // single-item (large) cards and a rebuild naturally produces the grouped + // (gallery) ones. + val singleZap = + remember(multiSetCard) { + multiSetCard.zapEvents + .singleOrNull() + ?.takeIf { + multiSetCard.nutzapEvents.isEmpty() && + multiSetCard.likeEvents.isEmpty() && + multiSetCard.boostEvents.isEmpty() + } + } + + val singleNutzap = + remember(multiSetCard) { + multiSetCard.nutzapEvents + .singleOrNull() + ?.takeIf { + multiSetCard.zapEvents.isEmpty() && + multiSetCard.likeEvents.isEmpty() && + multiSetCard.boostEvents.isEmpty() + } + } + + val isLargeCard = singleZap != null || singleNutzap != null + val backgroundColor = calculateBackgroundColor( createdAt = multiSetCard.maxCreatedAt, @@ -140,7 +175,7 @@ fun MultiSetCompose( ) val columnModifier = - remember(backgroundColor.value) { + remember(backgroundColor.value, isLargeCard) { Modifier .fillMaxWidth() .background(backgroundColor.value) @@ -153,31 +188,54 @@ fun MultiSetCompose( start = 12.dp, end = 12.dp, top = 10.dp, + bottom = if (isLargeCard) 10.dp else 0.dp, ) } Column(modifier = columnModifier) { - Galeries(multiSetCard, backgroundColor, accountViewModel, nav) + when { + singleZap != null -> + RenderLnZap( + note = singleZap.response, + quotesLeft = 1, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) - Row(Modifier.fillMaxWidth()) { - Spacer(modifier = WidthAuthorPictureModifierWithPadding) + singleNutzap != null -> + RenderNutzap( + note = singleNutzap, + quotesLeft = 1, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) - NoteCompose( - baseNote = baseNote, - modifier = HalfTopPadding, - routeForLastRead = null, - isBoostedNote = true, - isHiddenFeed = showHidden, - quotesLeft = 1, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) + else -> { + Galeries(multiSetCard, backgroundColor, accountViewModel, nav) - if (popupExpanded.value) { - NoteDropDownMenu(baseNote, { popupExpanded.value = false }, null, accountViewModel, nav) + Row(Modifier.fillMaxWidth()) { + Spacer(modifier = WidthAuthorPictureModifierWithPadding) + + NoteCompose( + baseNote = baseNote, + modifier = HalfTopPadding, + routeForLastRead = null, + isBoostedNote = true, + isHiddenFeed = showHidden, + quotesLeft = 1, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } } } + + if (popupExpanded.value) { + NoteDropDownMenu(baseNote, { popupExpanded.value = false }, null, accountViewModel, nav) + } } }