diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index e22773d3c0..71492e7f99 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -186,6 +186,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFhirResource import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFundraiser import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocache import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocacheFoundLog +import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGitStatusEvent import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGoal import com.vitorpamplona.amethyst.commons.ui.note.types.RenderHighlight import com.vitorpamplona.amethyst.commons.ui.note.types.RenderInteractiveStory @@ -332,6 +333,7 @@ import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestUpdateEvent import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent +import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -1026,6 +1028,8 @@ private fun FullBleedNoteCompose( RenderGitPullRequestEvent(baseNote, makeItShort = false, canPreview = true, quotesLeft = 3, backgroundColor = backgroundColor, accountViewModel = accountViewModel, nav = nav) } else if (noteEvent is GitPullRequestUpdateEvent) { RenderGitPullRequestUpdateEvent(baseNote, makeItShort = false, canPreview = true, quotesLeft = 3, backgroundColor = backgroundColor, accountViewModel = accountViewModel, nav = nav) + } else if (noteEvent is GitStatusEvent) { + RenderGitStatusEvent(baseNote, quotesLeft = 3, backgroundColor, accountViewModel, nav) } else if (noteEvent is AppDefinitionEvent) { RenderAppDefinition(baseNote, accountViewModel, nav) } else if (noteEvent is AppRecommendationEvent) { diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt index fafde36324..2de995c8d9 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt @@ -33,7 +33,7 @@ import com.vitorpamplona.quartz.nip34Git.status.GitStatusDraftEvent import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent import com.vitorpamplona.quartz.nip34Git.status.GitStatusOpenEvent -private fun GitStatusEvent.statusKind(): StatusKind = +fun GitStatusEvent.statusKind(): StatusKind = when (this) { is GitStatusAppliedEvent -> StatusKind.APPLIED is GitStatusClosedEvent -> StatusKind.CLOSED diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt index b15ce3709b..682ee75b5a 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt @@ -194,6 +194,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFhirResource import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFundraiser import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocache import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocacheFoundLog +import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGitStatusEvent import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGoal import com.vitorpamplona.amethyst.commons.ui.note.types.RenderHighlight import com.vitorpamplona.amethyst.commons.ui.note.types.RenderInteractiveStory @@ -362,6 +363,7 @@ import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestUpdateEvent import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent +import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -1393,6 +1395,10 @@ private fun RenderNoteRow( ) } + is GitStatusEvent -> { + RenderGitStatusEvent(baseNote, quotesLeft, backgroundColor, accountViewModel, nav) + } + is EncryptedDmEvent -> { RenderPrivateMessage( baseNote, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt index 13bf7db86b..ea4c1bf0e4 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt @@ -89,7 +89,7 @@ fun RenderBolt12Zap( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) amountSats?.let { ActivityAmountRow(PlatformNumberFormatter().format(it), orange) } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt new file mode 100644 index 0000000000..1ad0f90f4d --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.ui.note.types + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import com.vitorpamplona.amethyst.commons.model.Note +import com.vitorpamplona.amethyst.commons.nip34Git.ui.statusKind +import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.GitStatusPill +import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel +import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent + +/** + * Renders a NIP-34 status event (kinds 1630-1633). These usually carry an + * empty `content`, so the text fallback drew a blank note: show the new + * status as a pill, the optional comment, and the issue/patch/PR it + * targets (linked through the event's marked-`root` `e` tag) quoted below. + */ +@Composable +fun RenderGitStatusEvent( + note: Note, + quotesLeft: Int, + backgroundColor: MutableState, + accountViewModel: AccountViewModel, + nav: INav, +) { + val event = note.event as? GitStatusEvent ?: return + val kind = remember(event) { event.statusKind() } + + GitStatusPill(kind) + + if (event.content.isNotBlank()) { + Spacer(modifier = StdVertSpacer) + Text( + text = event.content, + style = MaterialTheme.typography.bodyMedium, + ) + } + + if (note.replyTo?.lastOrNull() != null) { + Spacer(modifier = StdVertSpacer) + RenderTargetNote(note, quotesLeft, backgroundColor, accountViewModel, nav) + } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt index 53e1b2bcb0..cea03f5759 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt @@ -93,7 +93,7 @@ fun RenderNutzap( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) ActivityAmountRow(showAmount(BigDecimal(nutzapEvent.claimedSatsTotal())), orange) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt index 39c14e65c7..515b4bb5db 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt @@ -150,7 +150,7 @@ fun RenderOnchainZap( nav = nav, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) AmountRow(sats = sats, orange = orange) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt index ec85bfb015..7c004995d0 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt @@ -81,6 +81,6 @@ fun RenderReaction( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) } } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt new file mode 100644 index 0000000000..6256da07b2 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.ui.note.types + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.vitorpamplona.amethyst.commons.model.Note +import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose +import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel + +/** + * Quotes the note that [note] acts on (the last entry of its `replyTo`) as a + * short embedded card: the post a zap or reaction targets, the issue/patch/PR + * a git status changes, and so on. + */ +@Composable +fun RenderTargetNote( + note: Note, + quotesLeft: Int, + backgroundColor: MutableState, + accountViewModel: AccountViewModel, + nav: INav, +) { + note.replyTo?.lastOrNull()?.let { + NoteCompose( + it, + modifier = Modifier, + isBoostedNote = true, + makeItShort = true, + unPackReply = ReplyRenderType.NONE, + quotesLeft = quotesLeft - 1, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt index 3d46b37ce2..91fbfaf5a7 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt @@ -52,7 +52,6 @@ import com.vitorpamplona.amethyst.commons.ui.note.ActivityCardFrame import com.vitorpamplona.amethyst.commons.ui.note.ActivityHeaderRow import com.vitorpamplona.amethyst.commons.ui.note.CrossfadeToDisplayComment import com.vitorpamplona.amethyst.commons.ui.note.DisplayBlankAuthor -import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier @@ -70,33 +69,6 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -/** - * Shows the post a zap targets above the transfer card, mirroring how - * reactions and reposts embed their target. - */ -@Composable -fun RenderZappedPost( - zapNote: Note, - quotesLeft: Int, - backgroundColor: MutableState, - accountViewModel: AccountViewModel, - nav: INav, -) { - zapNote.replyTo?.lastOrNull()?.let { - NoteCompose( - it, - modifier = Modifier, - isBoostedNote = true, - makeItShort = true, - unPackReply = ReplyRenderType.NONE, - quotesLeft = quotesLeft - 1, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) - } -} - @Composable fun RenderZapReceipt( note: Note, @@ -155,7 +127,7 @@ fun RenderZapReceiptCard( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) card.amount?.let { ActivityAmountRow(it, orange) }