feat(podcasts): surface NIP-22 comments on episode list rows

Episode comments already work end to end — replying to an episode routes to
the NIP-22 GenericCommentPost composer (kind 1111 scoped to the episode
address), the thread datasource fetches them via #a/#A, and the thread view
renders them with a full reactions row. The only gap was discoverability on
the podcast-specific surfaces.

Add a comments chip (comment glyph + live reply count via
observeNoteReplyCount) to PodcastEpisodeListItem. It reads "N comments" (or
"Comment" when empty) and opens the episode's thread, where the discussion
lives and new comments are composed. Reuses the existing thread/composer
machinery — no new event plumbing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGa1EM5KWyDo1o5Yr6sS18
This commit is contained in:
Claude
2026-07-01 15:19:39 +00:00
parent 69f47f2351
commit 9d4cb99398
2 changed files with 51 additions and 0 deletions
@@ -23,27 +23,34 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteReplyCount
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.note.CommentIcon
import com.vitorpamplona.amethyst.ui.note.timeAgo
import com.vitorpamplona.amethyst.ui.note.types.PodcastEpisodeAudioPlayer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.quartz.podcasts.PodcastEpisode
@@ -132,5 +139,44 @@ fun PodcastEpisodeListItem(
accountViewModel = accountViewModel,
)
}
EpisodeCommentsChip(note, accountViewModel, nav)
}
}
/**
* A "comments" affordance for an episode list row: the NIP-22 (kind 1111) reply count plus a comment
* glyph, opening the episode's thread where the discussion lives and new comments are composed.
* Everything downstream — fetching `#a`/`#A` comments, the reply composer, the count — already works
* through the standard thread; this just surfaces it on the podcast-specific list.
*/
@Composable
private fun EpisodeCommentsChip(
note: Note,
accountViewModel: AccountViewModel,
nav: INav,
) {
val commentCount by observeNoteReplyCount(note, accountViewModel)
Row(
modifier =
Modifier
.clip(RoundedCornerShape(8.dp))
.clickable { routeFor(note, accountViewModel.account)?.let { nav.nav(it) } }
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
CommentIcon(Size18Modifier, MaterialTheme.colorScheme.grayText)
Text(
text =
if (commentCount == 0) {
stringRes(R.string.podcast_comment_action)
} else {
pluralStringResource(R.plurals.podcast_comment_count, commentCount, commentCount)
},
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.grayText,
)
}
}
+5
View File
@@ -1011,6 +1011,11 @@
<string name="podcast_value_for_value">Value-for-Value</string>
<string name="podcast_value_split_percent">%1$d%%</string>
<string name="podcast_value_zap_split_hint">Zaps to this are split between:</string>
<string name="podcast_comment_action">Comment</string>
<plurals name="podcast_comment_count">
<item quantity="one">%1$d comment</item>
<item quantity="other">%1$d comments</item>
</plurals>
<string name="podcast_role_host">Host</string>
<string name="podcast_role_cohost">Co-host</string>
<string name="podcast_role_editor">Editor</string>