From 77a5182bbe9e4ff6001beee645977222c10950bd Mon Sep 17 00:00:00 2001 From: The Daniel Date: Sat, 25 Apr 2026 23:59:24 -0400 Subject: [PATCH] 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. --- .../com/wisp/app/ui/component/GalleryCard.kt | 2 + .../com/wisp/app/ui/component/PostCard.kt | 10 +- .../com/wisp/app/ui/component/RichContent.kt | 111 ++++++++++-------- 3 files changed, 71 insertions(+), 52 deletions(-) diff --git a/app/src/main/kotlin/com/wisp/app/ui/component/GalleryCard.kt b/app/src/main/kotlin/com/wisp/app/ui/component/GalleryCard.kt index e94e72a..a7959e0 100644 --- a/app/src/main/kotlin/com/wisp/app/ui/component/GalleryCard.kt +++ b/app/src/main/kotlin/com/wisp/app/ui/component/GalleryCard.kt @@ -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 ) diff --git a/app/src/main/kotlin/com/wisp/app/ui/component/PostCard.kt b/app/src/main/kotlin/com/wisp/app/ui/component/PostCard.kt index f020d35..5839372 100644 --- a/app/src/main/kotlin/com/wisp/app/ui/component/PostCard.kt +++ b/app/src/main/kotlin/com/wisp/app/ui/component/PostCard.kt @@ -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 ) } } diff --git a/app/src/main/kotlin/com/wisp/app/ui/component/RichContent.kt b/app/src/main/kotlin/com/wisp/app/ui/component/RichContent.kt index b08d4e4..978fb59 100644 --- a/app/src/main/kotlin/com/wisp/app/ui/component/RichContent.kt +++ b/app/src/main/kotlin/com/wisp/app/ui/component/RichContent.kt @@ -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 = 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