diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 4058010da9..854554860e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -63,6 +63,9 @@ import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChann import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable import com.vitorpamplona.amethyst.commons.ui.note.HeaderPill import com.vitorpamplona.amethyst.commons.ui.note.QuietMark +import com.vitorpamplona.amethyst.commons.ui.note.RenderCashuMint +import com.vitorpamplona.amethyst.commons.ui.note.RenderFedimint +import com.vitorpamplona.amethyst.commons.ui.note.RenderMintRecommendation import com.vitorpamplona.amethyst.commons.ui.state.produceCachedStateAsync import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache @@ -127,7 +130,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarCollectionEvent import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarDateSlotEvent import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarRSVPEvent import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarTimeSlotEvent -import com.vitorpamplona.amethyst.ui.note.types.RenderCashuMint import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage import com.vitorpamplona.amethyst.ui.note.types.RenderChat import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessage @@ -137,7 +139,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderClassifieds import com.vitorpamplona.amethyst.ui.note.types.RenderCodeSnippetEvent import com.vitorpamplona.amethyst.ui.note.types.RenderCommunity import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack -import com.vitorpamplona.amethyst.ui.note.types.RenderFedimint import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource import com.vitorpamplona.amethyst.ui.note.types.RenderFundraiser import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent @@ -156,7 +157,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingSpaceEvent -import com.vitorpamplona.amethyst.ui.note.types.RenderMintRecommendation import com.vitorpamplona.amethyst.ui.note.types.RenderMusicPlaylist import com.vitorpamplona.amethyst.ui.note.types.RenderMusicTrack import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CodeSnippet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CodeSnippet.kt index 2ae040eb92..3a500dbfa6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CodeSnippet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CodeSnippet.kt @@ -20,223 +20,19 @@ */ package com.vitorpamplona.amethyst.ui.note.types -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -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.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.commons.ui.note.CodeSnippetCard import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.theme.QuoteBorder -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer -import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.nipC0CodeSnippets.CodeSnippetEvent +/** + * Entry point for a NIP-C0 code snippet in the feed: decodes the [Note] and hands the + * event to the shared [CodeSnippetCard] in commons. The thread header renders through + * commons' `RenderCodeSnippetHeaderForThread` directly. + */ @Composable fun RenderCodeSnippetEvent(baseNote: Note) { val noteEvent = baseNote.event as? CodeSnippetEvent ?: return - val name = - remember(noteEvent) { - noteEvent.snippetName() - ?: noteEvent.language()?.let { "$it snippet" } - ?: "Code Snippet" - } - val language = remember(noteEvent) { noteEvent.language() ?: noteEvent.extension() } - val description = remember(noteEvent) { noteEvent.snippetDescription() } - val codePreview = - remember(noteEvent) { - noteEvent.content - .lines() - .take(5) - .joinToString("\n") - } - - Column(MaterialTheme.colorScheme.replyModifier) { - Row( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = name, - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.weight(1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - language?.let { - Spacer(modifier = StdHorzSpacer) - Box( - modifier = - Modifier - .clip(QuoteBorder) - .background(MaterialTheme.colorScheme.surfaceVariant) - .padding(horizontal = 6.dp, vertical = 2.dp), - ) { - Text( - text = it, - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - } - - description?.ifBlank { null }?.let { - Text( - text = it, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - Spacer(modifier = StdVertSpacer) - } - - Box( - modifier = - Modifier - .fillMaxWidth() - .padding(10.dp) - .clip(QuoteBorder) - .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f)) - .border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f), QuoteBorder) - .padding(10.dp), - ) { - Text( - text = codePreview, - style = - TextStyle( - fontFamily = FontFamily.Monospace, - fontSize = 12.sp, - ), - maxLines = 5, - overflow = TextOverflow.Ellipsis, - ) - } - } -} - -@Composable -fun RenderCodeSnippetHeaderForThread(noteEvent: CodeSnippetEvent) { - val name = - remember(noteEvent) { - noteEvent.snippetName() - ?: noteEvent.language()?.let { "$it snippet" } - ?: "Code Snippet" - } - val language = remember(noteEvent) { noteEvent.language() ?: noteEvent.extension() } - val description = remember(noteEvent) { noteEvent.snippetDescription() } - val license = remember(noteEvent) { noteEvent.license() } - val runtime = remember(noteEvent) { noteEvent.runtime() } - val deps = remember(noteEvent) { noteEvent.deps() } - - Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = name, - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.weight(1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - language?.let { - Spacer(modifier = StdHorzSpacer) - Box( - modifier = - Modifier - .clip(QuoteBorder) - .background(MaterialTheme.colorScheme.surfaceVariant) - .padding(horizontal = 8.dp, vertical = 3.dp), - ) { - Text( - text = it, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - } - - description?.ifBlank { null }?.let { - Spacer(modifier = StdVertSpacer) - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.fillMaxWidth(), - ) - } - - Spacer(modifier = StdVertSpacer) - - Box( - modifier = - Modifier - .fillMaxWidth() - .clip(QuoteBorder) - .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f)) - .border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f), QuoteBorder) - .padding(12.dp), - ) { - Text( - text = noteEvent.content, - style = - TextStyle( - fontFamily = FontFamily.Monospace, - fontSize = 13.sp, - ), - ) - } - - license?.let { - Spacer(modifier = StdVertSpacer) - Text( - text = "License: $it", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - runtime?.let { - Text( - text = "Runtime: $it", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - if (deps.isNotEmpty()) { - Text( - text = "Deps: ${deps.joinToString(", ")}", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } + CodeSnippetCard(noteEvent) } 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 e0e96e33f3..2f92fbe65f 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 @@ -80,6 +80,10 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding +import com.vitorpamplona.amethyst.commons.ui.note.RenderCashuMint +import com.vitorpamplona.amethyst.commons.ui.note.RenderCodeSnippetHeaderForThread +import com.vitorpamplona.amethyst.commons.ui.note.RenderFedimint +import com.vitorpamplona.amethyst.commons.ui.note.RenderMintRecommendation import com.vitorpamplona.amethyst.commons.ui.thread.drawReplyLevel import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -160,13 +164,10 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderBirdDetection import com.vitorpamplona.amethyst.ui.note.types.RenderBirdex import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarDateSlotEvent import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarTimeSlotEvent -import com.vitorpamplona.amethyst.ui.note.types.RenderCashuMint import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage import com.vitorpamplona.amethyst.ui.note.types.RenderChat import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessageEncryptedFile -import com.vitorpamplona.amethyst.ui.note.types.RenderCodeSnippetHeaderForThread import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack -import com.vitorpamplona.amethyst.ui.note.types.RenderFedimint import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource import com.vitorpamplona.amethyst.ui.note.types.RenderFundraiser import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent @@ -181,7 +182,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingSpaceEvent -import com.vitorpamplona.amethyst.ui.note.types.RenderMintRecommendation import com.vitorpamplona.amethyst.ui.note.types.RenderMusicPlaylist import com.vitorpamplona.amethyst.ui.note.types.RenderMusicTrack import com.vitorpamplona.amethyst.ui.note.types.RenderNamedSiteEvent diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CodeSnippetCard.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CodeSnippetCard.kt new file mode 100644 index 0000000000..cc1e7d305b --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CodeSnippetCard.kt @@ -0,0 +1,241 @@ +/* + * 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 + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier +import com.vitorpamplona.quartz.nipC0CodeSnippets.CodeSnippetEvent + +/** Compact feed preview for a NIP-C0 code snippet: name, language pill, description, first lines. */ +@Composable +fun CodeSnippetCard(noteEvent: CodeSnippetEvent) { + val name = + remember(noteEvent) { + noteEvent.snippetName() + ?: noteEvent.language()?.let { "$it snippet" } + ?: "Code Snippet" + } + val language = remember(noteEvent) { noteEvent.language() ?: noteEvent.extension() } + val description = remember(noteEvent) { noteEvent.snippetDescription() } + val codePreview = + remember(noteEvent) { + noteEvent.content + .lines() + .take(5) + .joinToString("\n") + } + + Column(MaterialTheme.colorScheme.replyModifier) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = name, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + language?.let { + Spacer(modifier = StdHorzSpacer) + Box( + modifier = + Modifier + .clip(QuoteBorder) + .background(MaterialTheme.colorScheme.surfaceVariant) + .padding(horizontal = 6.dp, vertical = 2.dp), + ) { + Text( + text = it, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + + description?.ifBlank { null }?.let { + Text( + text = it, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 10.dp), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + Spacer(modifier = StdVertSpacer) + } + + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(10.dp) + .clip(QuoteBorder) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f)) + .border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f), QuoteBorder) + .padding(10.dp), + ) { + Text( + text = codePreview, + style = + TextStyle( + fontFamily = FontFamily.Monospace, + fontSize = 12.sp, + ), + maxLines = 5, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + +/** Full snippet header shown at the top of a thread: full code plus license/runtime/deps metadata. */ +@Composable +fun RenderCodeSnippetHeaderForThread(noteEvent: CodeSnippetEvent) { + val name = + remember(noteEvent) { + noteEvent.snippetName() + ?: noteEvent.language()?.let { "$it snippet" } + ?: "Code Snippet" + } + val language = remember(noteEvent) { noteEvent.language() ?: noteEvent.extension() } + val description = remember(noteEvent) { noteEvent.snippetDescription() } + val license = remember(noteEvent) { noteEvent.license() } + val runtime = remember(noteEvent) { noteEvent.runtime() } + val deps = remember(noteEvent) { noteEvent.deps() } + + Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = name, + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + language?.let { + Spacer(modifier = StdHorzSpacer) + Box( + modifier = + Modifier + .clip(QuoteBorder) + .background(MaterialTheme.colorScheme.surfaceVariant) + .padding(horizontal = 8.dp, vertical = 3.dp), + ) { + Text( + text = it, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + + description?.ifBlank { null }?.let { + Spacer(modifier = StdVertSpacer) + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.fillMaxWidth(), + ) + } + + Spacer(modifier = StdVertSpacer) + + Box( + modifier = + Modifier + .fillMaxWidth() + .clip(QuoteBorder) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f)) + .border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f), QuoteBorder) + .padding(12.dp), + ) { + Text( + text = noteEvent.content, + style = + TextStyle( + fontFamily = FontFamily.Monospace, + fontSize = 13.sp, + ), + ) + } + + license?.let { + Spacer(modifier = StdVertSpacer) + Text( + text = "License: $it", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + runtime?.let { + Text( + text = "Runtime: $it", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + if (deps.isNotEmpty()) { + Text( + text = "Deps: ${deps.joinToString(", ")}", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/EcashMint.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/EcashMintCard.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/EcashMint.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/EcashMintCard.kt index c822a41379..04b2fcbb22 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/EcashMint.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/EcashMintCard.kt @@ -18,7 +18,7 @@ * 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.ui.note.types +package com.vitorpamplona.amethyst.commons.ui.note import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -41,9 +41,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.ui.theme.QuoteBorder -import com.vitorpamplona.amethyst.ui.theme.SmallBorder -import com.vitorpamplona.amethyst.ui.theme.subtleBorder +import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.commons.ui.theme.SmallBorder +import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent import com.vitorpamplona.quartz.nip87Ecash.recommendation.MintRecommendationEvent diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/theme/NoteBorders.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/theme/NoteBorders.kt new file mode 100644 index 0000000000..1793796e16 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/theme/NoteBorders.kt @@ -0,0 +1,71 @@ +/* + * 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.theme + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ColorScheme +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +// Shared border geometry and spacing for the event-kind renderers that are being +// pulled down from `amethyst/ui/note` into commons. These mirror the values in the +// Android app's `ui/theme/Shape.kt` so the two front ends can't drift; the Android +// copies become dead code as each renderer moves and are deleted at the end of the +// migration. + +/** Rounded corner used for quoted/embedded note bodies (15dp). */ +val QuoteBorder = RoundedCornerShape(15.dp) + +/** Rounded corner used for small inline pills/chips (7dp). */ +val SmallBorder = RoundedCornerShape(7.dp) + +/** 5dp horizontal spacer used between inline elements. */ +val StdHorzSpacer = Modifier.width(5.dp) + +/** 5dp vertical spacer used between stacked elements. */ +val StdVertSpacer = Modifier.height(5.dp) + +/** + * Faint hairline border color for cards and quoted bodies. Matches the Android app's + * `subtleBorder`: onSurface at 5% (light) / 12% (dark) alpha. + */ +val ColorScheme.subtleBorder: Color + get() = onSurface.copy(alpha = if (isLight) 0.05f else 0.12f) + +/** + * The wrapping modifier for a quoted/reply body: top padding, full width, clipped to + * [QuoteBorder] with a [subtleBorder] hairline. Mirrors the Android `replyModifier`. + */ +@Suppress("ModifierFactoryExtensionFunction") +val ColorScheme.replyModifier: Modifier + get() = + Modifier + .padding(top = 5.dp) + .fillMaxWidth() + .clip(QuoteBorder) + .border(1.dp, subtleBorder, QuoteBorder)