fix: cap quoted note nesting to prevent squashed action bars

Add quoteDepth parameter to RichContent, QuotedNote, PostCard, and
GalleryCard. At depth >= 1 (already inside a quote), force compact
preview rendering (avatar + name + snippet) instead of full PostCard
with action bar. Prevents recursive nesting from crushing layouts.
This commit is contained in:
The Daniel
2026-04-25 23:59:24 -04:00
parent 18232c3e8f
commit 77a5182bbe
3 changed files with 71 additions and 52 deletions
@@ -152,6 +152,7 @@ fun GalleryCard(
onPin: () -> Unit = {},
isPinned: Boolean = false,
onDelete: () -> Unit = {},
quoteDepth: Int = 0,
modifier: Modifier = Modifier,
showDivider: Boolean = true
) {
@@ -220,6 +221,7 @@ fun GalleryCard(
onPin = onPin,
isPinned = isPinned,
onDelete = onDelete,
quoteDepth = quoteDepth,
modifier = modifier,
showDivider = showDivider
)
@@ -163,6 +163,7 @@ fun PostCard(
onZapPollVote: (Int) -> Unit = {},
translationState: TranslationState = TranslationState(),
onTranslate: () -> Unit = {},
quoteDepth: Int = 0,
modifier: Modifier = Modifier,
showDivider: Boolean = true
) {
@@ -570,7 +571,8 @@ fun PostCard(
onProfileClick = onNavigateToProfile,
onNoteClick = onQuotedNoteClick,
noteActions = noteActions,
authorPubkey = event.pubkey
authorPubkey = event.pubkey,
quoteDepth = quoteDepth
)
} else {
// Collapsible content with max height (~1 viewport)
@@ -603,7 +605,8 @@ fun PostCard(
eventRepo = eventRepo,
onProfileClick = onNavigateToProfile,
onNoteClick = onQuotedNoteClick,
noteActions = noteActions
noteActions = noteActions,
quoteDepth = quoteDepth
)
}
@@ -704,7 +707,8 @@ fun PostCard(
eventRepo = eventRepo,
onProfileClick = onNavigateToProfile,
onNoteClick = onQuotedNoteClick,
noteActions = noteActions
noteActions = noteActions,
quoteDepth = quoteDepth
)
}
}
@@ -525,7 +525,9 @@ private fun LightningInvoiceCard(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(com.wisp.app.R.drawable.ic_bolt),
painter = painterResource(
if (com.wisp.app.ui.util.isFiatMode()) com.wisp.app.R.drawable.ic_coin_stack else com.wisp.app.R.drawable.ic_bolt
),
contentDescription = null,
tint = primary,
modifier = Modifier.size(20.dp)
@@ -608,6 +610,7 @@ fun RichContent(
onLiveStreamClick: ((String, String, String?) -> Unit)? = null,
noteActions: NoteActions? = null,
authorPubkey: String? = null,
quoteDepth: Int = 0,
modifier: Modifier = Modifier
) {
val segments = remember(content, emojiMap, imetaMap, plainLinks) { parseContent(content.trimEnd('\n', '\r'), emojiMap, imetaMap, trimBlankLines = !plainLinks) }
@@ -839,7 +842,8 @@ fun RichContent(
eventRepo = eventRepo,
relayHints = segment.relayHints,
onNoteClick = onNoteClick,
noteActions = noteActions
noteActions = noteActions,
quoteDepth = quoteDepth
)
} else {
Text(
@@ -863,6 +867,7 @@ fun RichContent(
onNoteClick = onNoteClick,
onProfileClick = onProfileClick,
noteActions = noteActions,
quoteDepth = quoteDepth,
style = style
)
} else {
@@ -953,7 +958,8 @@ fun QuotedNote(
eventRepo: EventRepository,
relayHints: List<String> = emptyList(),
onNoteClick: ((String) -> Unit)? = null,
noteActions: NoteActions? = null
noteActions: NoteActions? = null,
quoteDepth: Int = 0
) {
// Observe versions so we recompose when data arrives from relays
val version by eventRepo.quotedEventVersion.collectAsState()
@@ -967,7 +973,10 @@ fun QuotedNote(
}
}
val effectiveNoteClick = noteActions?.onNoteClick ?: onNoteClick
// Cap nesting: depth >= 1 means we're already inside a quoted note,
// so force compact preview to avoid squashed action bars
val effectiveActions = if (quoteDepth >= 1) null else noteActions
val effectiveNoteClick = effectiveActions?.onNoteClick ?: noteActions?.onNoteClick ?: onNoteClick
// Fetch poll votes when quoted event is a poll
LaunchedEffect(event?.id, event?.kind) {
@@ -979,7 +988,7 @@ fun QuotedNote(
}
}
if (event != null && noteActions != null) {
if (event != null && effectiveActions != null) {
// Full rendering with all interactive features
val reactionVersion by eventRepo.reactionVersion.collectAsState()
val zapVersion by eventRepo.zapVersion.collectAsState()
@@ -992,8 +1001,8 @@ fun QuotedNote(
val zapSats = remember(zapVersion, eventId) { eventRepo.getZapSats(eventId) }
val repostCount = remember(repostVersion, eventId) { eventRepo.getRepostCount(eventId) }
val repostPubkeys = remember(repostVersion, eventId) { eventRepo.getReposterPubkeys(eventId) }
val userEmojis = remember(reactionVersion, eventId, noteActions.userPubkey) {
noteActions.userPubkey?.let { eventRepo.getUserReactionEmojis(eventId, it) } ?: emptySet()
val userEmojis = remember(reactionVersion, eventId, effectiveActions.userPubkey) {
effectiveActions.userPubkey?.let { eventRepo.getUserReactionEmojis(eventId, it) } ?: emptySet()
}
val hasUserReposted = remember(repostVersion, eventId) { eventRepo.hasUserReposted(eventId) }
val hasUserZapped = remember(zapVersion, eventId) { eventRepo.hasUserZapped(eventId) }
@@ -1035,17 +1044,17 @@ fun QuotedNote(
GalleryCard(
event = event,
profile = profile,
onReply = { noteActions.onReply(event) },
onProfileClick = { noteActions.onProfileClick(event.pubkey) },
onNavigateToProfile = noteActions.onProfileClick,
onReply = { effectiveActions.onReply(event) },
onProfileClick = { effectiveActions.onProfileClick(event.pubkey) },
onNavigateToProfile = effectiveActions.onProfileClick,
onNoteClick = { effectiveNoteClick?.invoke(eventId) },
onReact = { emoji -> noteActions.onReact(event, emoji) },
onReact = { emoji -> effectiveActions.onReact(event, emoji) },
userReactionEmojis = userEmojis,
onRepost = { noteActions.onRepost(event) },
onQuote = { noteActions.onQuote(event) },
onRepost = { effectiveActions.onRepost(event) },
onQuote = { effectiveActions.onQuote(event) },
hasUserReposted = hasUserReposted,
repostCount = repostCount,
onZap = { noteActions.onZap(event) },
onZap = { effectiveActions.onZap(event) },
hasUserZapped = hasUserZapped,
likeCount = likeCount,
replyCount = replyCount,
@@ -1054,37 +1063,38 @@ fun QuotedNote(
reactionDetails = reactionDetails,
zapDetails = zapDetails,
repostDetails = repostPubkeys,
onNavigateToProfileFromDetails = noteActions.onProfileClick,
onFollowAuthor = { noteActions.onFollowAuthor(event.pubkey) },
onBlockAuthor = { noteActions.onBlockAuthor(event.pubkey) },
isFollowingAuthor = noteActions.isFollowing(event.pubkey),
isOwnEvent = event.pubkey == noteActions.userPubkey,
nip05Repo = noteActions.nip05Repo,
onAddToList = { noteActions.onAddToList(eventId) },
onPin = { noteActions.onPin(eventId) },
onNavigateToProfileFromDetails = effectiveActions.onProfileClick,
onFollowAuthor = { effectiveActions.onFollowAuthor(event.pubkey) },
onBlockAuthor = { effectiveActions.onBlockAuthor(event.pubkey) },
isFollowingAuthor = effectiveActions.isFollowing(event.pubkey),
isOwnEvent = event.pubkey == effectiveActions.userPubkey,
nip05Repo = effectiveActions.nip05Repo,
onAddToList = { effectiveActions.onAddToList(eventId) },
onPin = { effectiveActions.onPin(eventId) },
onQuotedNoteClick = effectiveNoteClick,
noteActions = noteActions,
noteActions = effectiveActions,
reactionEmojiUrls = reactionEmojiUrls,
resolvedEmojis = noteActions.resolvedEmojisProvider(),
unicodeEmojis = noteActions.unicodeEmojisProvider(),
onOpenEmojiLibrary = noteActions.onOpenEmojiLibrary,
showDivider = false
resolvedEmojis = effectiveActions.resolvedEmojisProvider(),
unicodeEmojis = effectiveActions.unicodeEmojisProvider(),
onOpenEmojiLibrary = effectiveActions.onOpenEmojiLibrary,
showDivider = false,
quoteDepth = quoteDepth + 1
)
} else {
PostCard(
event = event,
profile = profile,
onReply = { noteActions.onReply(event) },
onProfileClick = { noteActions.onProfileClick(event.pubkey) },
onNavigateToProfile = noteActions.onProfileClick,
onReply = { effectiveActions.onReply(event) },
onProfileClick = { effectiveActions.onProfileClick(event.pubkey) },
onNavigateToProfile = effectiveActions.onProfileClick,
onNoteClick = { effectiveNoteClick?.invoke(eventId) },
onReact = { emoji -> noteActions.onReact(event, emoji) },
onReact = { emoji -> effectiveActions.onReact(event, emoji) },
userReactionEmojis = userEmojis,
onRepost = { noteActions.onRepost(event) },
onQuote = { noteActions.onQuote(event) },
onRepost = { effectiveActions.onRepost(event) },
onQuote = { effectiveActions.onQuote(event) },
hasUserReposted = hasUserReposted,
repostCount = repostCount,
onZap = { noteActions.onZap(event) },
onZap = { effectiveActions.onZap(event) },
hasUserZapped = hasUserZapped,
likeCount = likeCount,
replyCount = replyCount,
@@ -1093,28 +1103,29 @@ fun QuotedNote(
reactionDetails = reactionDetails,
zapDetails = zapDetails,
repostDetails = repostPubkeys,
onNavigateToProfileFromDetails = noteActions.onProfileClick,
onFollowAuthor = { noteActions.onFollowAuthor(event.pubkey) },
onBlockAuthor = { noteActions.onBlockAuthor(event.pubkey) },
isFollowingAuthor = noteActions.isFollowing(event.pubkey),
isOwnEvent = event.pubkey == noteActions.userPubkey,
nip05Repo = noteActions.nip05Repo,
onAddToList = { noteActions.onAddToList(eventId) },
onPin = { noteActions.onPin(eventId) },
onNavigateToProfileFromDetails = effectiveActions.onProfileClick,
onFollowAuthor = { effectiveActions.onFollowAuthor(event.pubkey) },
onBlockAuthor = { effectiveActions.onBlockAuthor(event.pubkey) },
isFollowingAuthor = effectiveActions.isFollowing(event.pubkey),
isOwnEvent = event.pubkey == effectiveActions.userPubkey,
nip05Repo = effectiveActions.nip05Repo,
onAddToList = { effectiveActions.onAddToList(eventId) },
onPin = { effectiveActions.onPin(eventId) },
onQuotedNoteClick = effectiveNoteClick,
noteActions = noteActions,
noteActions = effectiveActions,
pollVoteCounts = pollVoteCounts,
pollTotalVotes = pollTotalVotes,
userPollVotes = userPollVotes,
onPollVote = { optionIds -> noteActions.onPollVote(eventId, optionIds) },
onPollVote = { optionIds -> effectiveActions.onPollVote(eventId, optionIds) },
zapPollSatsCounts = zapPollSatsCounts,
zapPollTotalSats = zapPollTotalSats,
userZapPollVote = userZapPollVote,
reactionEmojiUrls = reactionEmojiUrls,
resolvedEmojis = noteActions.resolvedEmojisProvider(),
unicodeEmojis = noteActions.unicodeEmojisProvider(),
onOpenEmojiLibrary = noteActions.onOpenEmojiLibrary,
showDivider = false
resolvedEmojis = effectiveActions.resolvedEmojisProvider(),
unicodeEmojis = effectiveActions.unicodeEmojisProvider(),
onOpenEmojiLibrary = effectiveActions.onOpenEmojiLibrary,
showDivider = false,
quoteDepth = quoteDepth + 1
)
}
}
@@ -1223,6 +1234,7 @@ private fun QuotedAddressableNote(
onNoteClick: ((String) -> Unit)?,
onProfileClick: ((String) -> Unit)?,
noteActions: NoteActions?,
quoteDepth: Int = 0,
style: TextStyle
) {
val version by eventRepo.quotedEventVersion.collectAsState()
@@ -1241,7 +1253,8 @@ private fun QuotedAddressableNote(
eventId = event.id,
eventRepo = eventRepo,
onNoteClick = onNoteClick,
noteActions = noteActions
noteActions = noteActions,
quoteDepth = quoteDepth
)
} else {
// Loading state