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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVPYTTnMbxwi5hUAagsKVc
This commit is contained in:
Claude
2026-07-17 20:28:16 +00:00
parent 2f3c09c41d
commit ede4fea1cd
2 changed files with 35 additions and 31 deletions
@@ -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<ImmutableList<NormalizedRelayUrl>> {
// 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<NormalizedRelayUrl>,
nav: INav,
accountViewModel: AccountViewModel,
@@ -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)
}
}
}
}