From ede4fea1cdb150091fbb0eaa21cc8b858c9cb52e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 20:28:16 +0000 Subject: [PATCH] fix: don't render empty reaction gallery row without relays or reactions Restore a visibility guard on the reaction detail gallery so a note with neither relays nor reactions no longer paints an empty padded row when the gallery is expanded. The relay flow is now collected once via observeNoteRelays and reused both for the guard and to render the "accepted by relays" line, avoiding a second subscription. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GVPYTTnMbxwi5hUAagsKVc --- .../ui/note/AcceptedByRelaysGallery.kt | 30 ++++++++-------- .../amethyst/ui/note/ReactionsRow.kt | 36 +++++++++++-------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/AcceptedByRelaysGallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/AcceptedByRelaysGallery.kt index 01d75014c0..b0e29acd7b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/AcceptedByRelaysGallery.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/AcceptedByRelaysGallery.kt @@ -29,7 +29,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -56,18 +56,14 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.sample /** - * The "accepted by relays" line of the reaction gallery: the favicon of every relay - * the note was seen on. This is the same relay-pill set that Complete UI Mode used to - * paint below the author avatar; it now lives in the expanded reaction gallery so it - * is available to everyone, not just Complete mode. + * Observes the relays the note was seen on, throttled and de-duplicated for the + * "accepted by relays" gallery line. Exposed as State so the caller can both gate + * the gallery's visibility on it and feed it to [RenderAcceptedByRelaysGallery] + * from a single subscription. */ @OptIn(FlowPreview::class) @Composable -internal fun WatchRelaysAndRenderGallery( - baseNote: Note, - nav: INav, - accountViewModel: AccountViewModel, -) { +internal fun observeNoteRelays(baseNote: Note): State> { // Cold wrapper: `flow()` must be re-resolved on every collection start. A memory // trim destroys the NoteFlowSet while the lifecycle is stopped; a stateFlow // captured in remember would then be orphaned and never see another relay update. @@ -81,15 +77,17 @@ internal fun WatchRelaysAndRenderGallery( } val initial = remember(baseNote) { baseNote.relays.toImmutableList() } - val relays by flow.collectAsStateWithLifecycle(initial) - - if (relays.isNotEmpty()) { - RenderAcceptedByRelaysGallery(relays, nav, accountViewModel) - } + return flow.collectAsStateWithLifecycle(initial) } +/** + * The "accepted by relays" line of the reaction gallery: the favicon of every relay + * the note was seen on. This is the same relay-pill set that Complete UI Mode used to + * paint below the author avatar; it now lives in the expanded reaction gallery so it + * is available to everyone, not just Complete mode. + */ @Composable -private fun RenderAcceptedByRelaysGallery( +internal fun RenderAcceptedByRelaysGallery( relays: ImmutableList, nav: INav, accountViewModel: AccountViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index cbc89c4e63..46aaad443c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -563,22 +563,28 @@ private fun ReactionDetailGallery( val defaultBackgroundColor = MaterialTheme.colorScheme.background val backgroundColor = remember { mutableStateOf(defaultBackgroundColor) } - // Keep subscribing for reaction/zap/boost arrivals, but no longer gate the gallery - // on them: the "accepted by relays" line is (almost) always present, so once the - // user expands the gallery there is always at least that line to show. - observeNoteReferences(baseNote, accountViewModel) + val hasReactions by observeNoteReferences(baseNote, accountViewModel) + val relays by observeNoteRelays(baseNote) - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(start = 10.dp, top = 5.dp), - ) { - Column { - WatchRelaysAndRenderGallery(baseNote, nav, accountViewModel) - WatchZapAndRenderGallery(baseNote, backgroundColor, nav, accountViewModel) - WatchNutzapsAndRenderGallery(baseNote, nav, accountViewModel) - WatchOnchainZapsAndRenderGallery(baseNote, nav, accountViewModel) - WatchBoostsAndRenderGallery(baseNote, nav, accountViewModel) - WatchReactionsAndRenderGallery(baseNote, nav, accountViewModel) + // The gallery shows whenever there is anything to display: the "accepted by relays" + // line (relays are almost always present once a note is seen) or any zap/boost/ + // reaction line. Guarding here keeps the padded Row from rendering an empty strip + // for a note that has neither. + if (hasReactions || relays.isNotEmpty()) { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(start = 10.dp, top = 5.dp), + ) { + Column { + if (relays.isNotEmpty()) { + RenderAcceptedByRelaysGallery(relays, nav, accountViewModel) + } + WatchZapAndRenderGallery(baseNote, backgroundColor, nav, accountViewModel) + WatchNutzapsAndRenderGallery(baseNote, nav, accountViewModel) + WatchOnchainZapsAndRenderGallery(baseNote, nav, accountViewModel) + WatchBoostsAndRenderGallery(baseNote, nav, accountViewModel) + WatchReactionsAndRenderGallery(baseNote, nav, accountViewModel) + } } } }