mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-07-30 19:46:17 +00:00
refactor(commons): extract CodeSnippet + EcashMint renderers to commons
First pilot of the note-ui-commons extraction plan (commons/plans/2026-07-16-note-ui-commons-extraction.md, Tier 0). These two event-kind renderers take plain quartz events with no AccountViewModel, INav, or R.string, so they move to commons/ui/note/ almost verbatim: - EcashMintCard: RenderCashuMint / RenderFedimint / RenderMintRecommendation move wholesale (already past the seam — the NoteCompose dispatcher hands them a decoded event). NoteCompose + ThreadFeedView imports re-pointed. - CodeSnippetCard: the feed preview and thread header layouts move to commons; the Android app keeps a thin RenderCodeSnippetEvent(Note) entry that decodes the Note and calls the shared card. - NoteBorders: shared QuoteBorder/SmallBorder/StdHorzSpacer/StdVertSpacer/ subtleBorder/replyModifier theme primitives in commons so the migrated cards share one visual vocabulary. Mirrors the Android ui/theme values; the Android copies are deleted once every renderer that uses them moves. Desktop now renders the identical cards, so the two front ends can't drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gmrt3jwYPDJ38MJGJ6GaNr
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
+241
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
+71
@@ -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)
|
||||
Reference in New Issue
Block a user