From 2fefc3a409dcd3a7d14faca73953e4baed87546a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 12:28:59 +0000 Subject: [PATCH] feat(ui): unify note-header markers into a two-tier pill/quiet-mark system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tier 1 (HeaderPill): tappable or verifiable metadata now renders as the squared secondary-container chip — location, PoW, OTS (already pills), plus expiration and the bounty reward (bolt icon + amount, tap opens the pledge dialog; icon turns orange once the user has pledged). Tier 2 (QuietMark, new): passive state markers — Boosted, Draft, Edited/ Original, pinned, private rumor — share one spec: 12sp medium gray text with an optional 12dp icon. Draft is no longer a hardcoded English literal (new R.string.draft). Cross-cutting cleanups: - Header rows (NoteCompose First/SecondUserInfoRow, ThreadFeedView) own spacing via Arrangement.spacedBy(5dp) instead of per-marker paddings. - Hashtag/community/fork links use the existing lessImportantLink theme color instead of ad-hoc primary.copy(alpha = 0.52f) (fork was full primary, louder than everything else on the line). - NIP-05 badge drops hardcoded Color.Yellow/Color.Red for theme-aware placeholderText/error. - Note-header timestamps switch to the Short style at 12sp; other TimeAgo callers keep the dotted default. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AgEpNtwnetPhETXQSdq4b5 --- .../ui/note/NIP05VerificationDisplay.kt | 4 +- .../amethyst/ui/note/NoteCompose.kt | 78 ++++++------------ .../amethyst/ui/note/elements/BoostedMark.kt | 15 +--- .../ui/note/elements/DisplayCommunity.kt | 6 +- .../ui/note/elements/DisplayEditStatus.kt | 40 +++------- .../ui/note/elements/DisplayHashtags.kt | 3 +- .../amethyst/ui/note/elements/DisplayOts.kt | 3 - .../ui/note/elements/DisplayReward.kt | 45 +++-------- .../amethyst/ui/note/elements/ForkInfo.kt | 3 +- .../amethyst/ui/note/elements/HeaderPill.kt | 4 +- .../amethyst/ui/note/elements/QuietMark.kt | 80 +++++++++++++++++++ .../amethyst/ui/note/elements/TimeAgo.kt | 17 +++- .../loggedIn/threadview/ThreadFeedView.kt | 16 +++- amethyst/src/main/res/values/strings.xml | 2 + 14 files changed, 167 insertions(+), 149 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/QuietMark.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index 6bcb102918..b87ed48c44 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -472,7 +472,7 @@ fun RenderNIP05VerifiedSymbol( symbol = MaterialSymbols.Downloading, contentDescription = stringRes(id = R.string.nip05_checking), modifier = modifier, - tint = Color.Yellow, + tint = MaterialTheme.colorScheme.placeholderText, ) } @@ -492,7 +492,7 @@ fun RenderNIP05VerifiedSymbol( symbol = MaterialSymbols.Report, contentDescription = stringRes(id = R.string.nip05_failed), modifier = modifier, - tint = Color.Red, + tint = MaterialTheme.colorScheme.error, ) } } 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 6286a1a484..39ef85a00c 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 @@ -25,6 +25,7 @@ package com.vitorpamplona.amethyst.ui.note import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -32,7 +33,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton @@ -53,7 +53,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R @@ -85,11 +84,14 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayLocation import com.vitorpamplona.amethyst.ui.note.elements.DisplayOts import com.vitorpamplona.amethyst.ui.note.elements.DisplayPoW import com.vitorpamplona.amethyst.ui.note.elements.DisplayReward +import com.vitorpamplona.amethyst.ui.note.elements.HeaderPill import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton +import com.vitorpamplona.amethyst.ui.note.elements.QuietMark import com.vitorpamplona.amethyst.ui.note.elements.Reward import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation import com.vitorpamplona.amethyst.ui.note.elements.StaleRelayHint import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo +import com.vitorpamplona.amethyst.ui.note.elements.TimeAgoStyle import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.types.DisplayBlockedRelayList import com.vitorpamplona.amethyst.ui.note.types.DisplayBroadcastRelayList @@ -207,8 +209,6 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.Font12SP import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer -import com.vitorpamplona.amethyst.ui.theme.HalfPadding -import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size25dp @@ -216,7 +216,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size30dp import com.vitorpamplona.amethyst.ui.theme.Size34dp import com.vitorpamplona.amethyst.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.ui.theme.Size55dp -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.UserNameMaxRowHeight import com.vitorpamplona.amethyst.ui.theme.UserNameRowHeight import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier @@ -1743,6 +1743,7 @@ fun SecondUserInfoRow( Row( verticalAlignment = CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Size5dp), modifier = UserNameMaxRowHeight, ) { Column(modifier = Modifier.weight(1f)) { @@ -1755,32 +1756,19 @@ fun SecondUserInfoRow( val baseReward = remember(noteEvent) { noteEvent.bountyBaseReward()?.let { Reward(it) } } if (baseReward != null) { - Spacer(StdHorzSpacer) - DisplayReward(baseReward, note, accountViewModel, nav) + DisplayReward(baseReward, note, accountViewModel) } } } @Composable fun DisplayExpiration(expirationDate: Long) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - symbol = MaterialSymbols.Timer, - contentDescription = stringRes(R.string.expiration_date_label), - modifier = Modifier.padding(start = 5.dp).size(12.dp), - tint = MaterialTheme.colorScheme.placeholderText, - ) - val context = LocalContext.current - Text( - text = timeAheadNoDot(expirationDate, context), - color = MaterialTheme.colorScheme.placeholderText, - fontSize = Font12SP, - maxLines = 1, - modifier = Modifier.padding(start = 3.dp), - ) - } + val context = LocalContext.current + HeaderPill( + symbol = MaterialSymbols.Timer, + text = timeAheadNoDot(expirationDate, context), + contentDescription = stringRes(R.string.expiration_date_label), + ) } @Composable @@ -1800,26 +1788,12 @@ fun DisplayOtsIfInOriginal( @Composable fun DisplayDraft() { - Text( - "Draft", - fontWeight = FontWeight.Bold, - fontSize = Font12SP, - color = MaterialTheme.colorScheme.placeholderText, - maxLines = 1, - modifier = HalfStartPadding, - ) + QuietMark(text = stringRes(R.string.draft)) } @Composable fun DisplayDraftChat() { - Text( - "Draft", - color = MaterialTheme.colorScheme.placeholderText, - modifier = Modifier, - fontWeight = FontWeight.Bold, - fontSize = Font12SP, - maxLines = 1, - ) + QuietMark(text = stringRes(R.string.draft)) } @Composable @@ -1832,7 +1806,11 @@ fun FirstUserInfoRow( nav: INav, moreOptions: (@Composable () -> Unit)? = null, ) { - Row(verticalAlignment = CenterVertically, modifier = UserNameRowHeight) { + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Size5dp), + modifier = UserNameRowHeight, + ) { val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent val isDraft = baseNote.isDraft() val textColor = if (isRepost) MaterialTheme.colorScheme.grayText else Color.Unspecified @@ -1849,12 +1827,10 @@ fun FirstUserInfoRow( if (zapSender != null) { if (showAuthorPicture) { UserPicture(zapSender, Size25dp, accountViewModel = accountViewModel, nav = nav) - Spacer(HalfPadding) } UsernameDisplay(zapSender, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) } else if (showAuthorPicture) { NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav) - Spacer(HalfPadding) NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) } else { NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) @@ -1908,13 +1884,11 @@ fun FirstUserInfoRow( val geo = remember(noteEvent) { noteEvent?.geoHashOrScope() } if (geo != null) { - Spacer(StdHorzSpacer) DisplayLocation(geo, accountViewModel, nav) } val pow = remember(noteEvent) { noteEvent?.strongPoWOrNull() } if (pow != null) { - Spacer(StdHorzSpacer) DisplayPoW(pow) } @@ -1924,7 +1898,7 @@ fun FirstUserInfoRow( JumpToParentReplyButton(baseNote, accountViewModel, nav) - TimeAgo(baseNote) + TimeAgo(baseNote, style = TimeAgoStyle.Short, fontSize = Font12SP) if (moreOptions == null) { MoreOptionsButton(baseNote, editState, accountViewModel, nav) @@ -1936,21 +1910,17 @@ fun FirstUserInfoRow( @Composable fun PinnedMark() { - Icon( + QuietMark( symbol = MaterialSymbols.PushPin, contentDescription = stringRes(R.string.pinned_notes), - modifier = Modifier.padding(start = 5.dp).size(14.dp), - tint = MaterialTheme.colorScheme.placeholderText, ) } @Composable fun PrivateRumorMark() { - Icon( + QuietMark( symbol = MaterialSymbols.Lock, contentDescription = stringRes(R.string.private_rumor_mark), - modifier = Modifier.padding(start = 5.dp).size(14.dp), - tint = MaterialTheme.colorScheme.placeholderText, ) } @@ -1976,7 +1946,7 @@ fun JumpToParentReplyButton( } ?: return ClickableBox( - modifier = Modifier.padding(start = 5.dp).size(20.dp), + modifier = Modifier.size(20.dp), onClick = { nav.nav { routeFor(parentNote, accountViewModel.account) } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt index d214b3391c..7b81088adc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt @@ -20,24 +20,11 @@ */ package com.vitorpamplona.amethyst.ui.note.elements -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.text.font.FontWeight import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Font12SP -import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding -import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable fun BoostedMark() { - Text( - stringRes(id = R.string.boosted), - fontWeight = FontWeight.Bold, - fontSize = Font12SP, - color = MaterialTheme.colorScheme.placeholderText, - maxLines = 1, - modifier = HalfStartPadding, - ) + QuietMark(text = stringRes(id = R.string.boosted)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt index 404688cc03..64b27c68a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt @@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.Font12SP -import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding +import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent @@ -47,7 +47,7 @@ fun DisplayFollowingCommunityInPost( accountViewModel: AccountViewModel, nav: INav, ) { - Column(HalfStartPadding) { + Column { Row(verticalAlignment = Alignment.CenterVertically) { DisplayCommunity(baseNote, accountViewModel, nav) } } } @@ -67,7 +67,7 @@ private fun DisplayCommunity( ClickableTextColor( label, style = LocalTextStyle.current.copy(fontSize = Font12SP), - linkColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.52f), + linkColor = MaterialTheme.colorScheme.lessImportantLink, overflow = TextOverflow.Ellipsis, maxLines = 1, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt index ee31e07dc3..48a2e6b60f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt @@ -20,40 +20,24 @@ */ package com.vitorpamplona.amethyst.ui.note.elements -import androidx.compose.foundation.clickable -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.types.EditState import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Font12SP -import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding -import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable fun DisplayEditStatus(editState: EditState) { - Text( - text = - buildAnnotatedString { - if (editState.showingVersion.value == editState.originalVersionId()) { - append(stringRes(id = R.string.original)) - } else if (editState.showingVersion.value == editState.lastVersionId()) { - append(stringRes(id = R.string.edited)) - } else { - append(stringRes(id = R.string.edited_number, editState.versionId())) - } - }, - style = - LocalTextStyle.current.copy( - color = MaterialTheme.colorScheme.placeholderText, - fontWeight = FontWeight.Bold, - fontSize = Font12SP, - ), - maxLines = 1, - modifier = HalfStartPadding.clickable { editState.nextModification() }, + val label = + if (editState.showingVersion.value == editState.originalVersionId()) { + stringRes(id = R.string.original) + } else if (editState.showingVersion.value == editState.lastVersionId()) { + stringRes(id = R.string.edited) + } else { + stringRes(id = R.string.edited_number, editState.versionId()) + } + + QuietMark( + text = label, + onClick = { editState.nextModification() }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayHashtags.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayHashtags.kt index b9f73c3367..cc48d6d08e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayHashtags.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayHashtags.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.Font12SP +import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.quartz.nip01Core.tags.hashtags.firstIsTaggedHashes import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId @@ -85,7 +86,7 @@ private fun DisplayTagList( ClickableTextColor( "#$firstTag", style = LocalTextStyle.current.copy(fontSize = Font12SP), - linkColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.52f), + linkColor = MaterialTheme.colorScheme.lessImportantLink, overflow = TextOverflow.Ellipsis, maxLines = 1, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt index fdf5f6890c..c5cf453138 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt @@ -32,7 +32,6 @@ import com.vitorpamplona.amethyst.ui.note.LoadOts import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding import java.text.SimpleDateFormat import java.util.Date @@ -63,7 +62,6 @@ fun DisplayOts( HeaderPill( symbol = MaterialSymbols.CheckCircle, text = stringRes(R.string.existed_since, timeStr), - modifier = HalfStartPadding, contentDescription = stringRes(R.string.ots_info_title), onClick = { accountViewModel.toastManager.toast( @@ -78,7 +76,6 @@ fun DisplayOts( HeaderPill( symbol = MaterialSymbols.Schedule, text = stringRes(id = R.string.timestamp_pending_short), - modifier = HalfStartPadding, contentDescription = stringRes(R.string.ots_info_title), ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt index b392b6a50c..0b36780bd1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.elements -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -55,20 +54,16 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.commons.ui.components.ClickableTextColor +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteReplies -import com.vitorpamplona.amethyst.ui.navigation.navs.INav -import com.vitorpamplona.amethyst.ui.navigation.routes.Route -import com.vitorpamplona.amethyst.ui.note.ZapIcon -import com.vitorpamplona.amethyst.ui.note.ZappedIcon import com.vitorpamplona.amethyst.ui.note.buttons.CloseButton import com.vitorpamplona.amethyst.ui.note.buttons.PostButton import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Size20Modifier +import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -83,28 +78,13 @@ fun DisplayReward( baseReward: Reward, baseNote: Note, accountViewModel: AccountViewModel, - nav: INav, ) { var popupExpanded by remember { mutableStateOf(false) } - Column { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.clickable { popupExpanded = true }, - ) { - ClickableTextColor( - "#bounty", - linkColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.52f), - ) { - nav.nav(Route.Hashtag("bounty")) - } + RenderPledgeAmount(baseNote, baseReward, accountViewModel) { popupExpanded = true } - RenderPledgeAmount(baseNote, baseReward, accountViewModel) - } - - if (popupExpanded) { - AddBountyAmountDialog(baseNote, accountViewModel) { popupExpanded = false } - } + if (popupExpanded) { + AddBountyAmountDialog(baseNote, accountViewModel) { popupExpanded = false } } } @@ -113,6 +93,7 @@ private fun RenderPledgeAmount( baseNote: Note, baseReward: Reward, accountViewModel: AccountViewModel, + onClick: () -> Unit, ) { val repliesState by observeNoteReplies(baseNote, accountViewModel) var reward by remember { @@ -142,16 +123,12 @@ private fun RenderPledgeAmount( } } - if (hasPledge) { - ZappedIcon(modifier = Size20Modifier) - } else { - ZapIcon(modifier = Size20Modifier, MaterialTheme.colorScheme.placeholderText) - } - - Text( + HeaderPill( + symbol = MaterialSymbols.Bolt, text = reward, - color = MaterialTheme.colorScheme.placeholderText, - maxLines = 1, + contentDescription = stringRes(R.string.bounty_label), + iconTint = if (hasPledge) BitcoinOrange else null, + onClick = onClick, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt index e39303df66..7b6486bfd9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font14SP +import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.quartz.experimental.forks.IForkableEvent @Composable @@ -85,7 +86,7 @@ fun ForkInformationRowLightColor( clickablePart = stringRes(id = R.string.forked_from) + " " + (userState?.info?.bestName() ?: author.pubkeyDisplayHex()), maxLines = 1, route = route, - overrideColor = MaterialTheme.colorScheme.primary, + overrideColor = MaterialTheme.colorScheme.lessImportantLink, fontSize = Font14SP, nav = nav, tags = userState?.tags, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/HeaderPill.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/HeaderPill.kt index 2e70a1f398..6661391c8b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/HeaderPill.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/HeaderPill.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon @@ -49,6 +50,7 @@ fun HeaderPill( text: String, modifier: Modifier = Modifier, contentDescription: String? = null, + iconTint: Color? = null, onClick: (() -> Unit)? = null, ) { Surface( @@ -64,7 +66,7 @@ fun HeaderPill( Icon( symbol = symbol, contentDescription = contentDescription, - tint = MaterialTheme.colorScheme.onSecondaryContainer, + tint = iconTint ?: MaterialTheme.colorScheme.onSecondaryContainer, modifier = Modifier.size(11.dp), ) Text( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/QuietMark.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/QuietMark.kt new file mode 100644 index 0000000000..acd339471e --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/QuietMark.kt @@ -0,0 +1,80 @@ +/* + * 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.ui.note.elements + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.commons.icons.symbols.Icon +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol +import com.vitorpamplona.amethyst.ui.theme.Font12SP +import com.vitorpamplona.amethyst.ui.theme.placeholderText + +/** + * Quiet passive-state marker for note headers: Boosted, Draft, Edited, pinned, + * private rumor, ... One spec for all of them so the header reads as a single + * system: 12sp medium gray text with an optional 12dp icon. Contrast with + * [HeaderPill], which is the loud tier for tappable/verifiable metadata. + * + * Spacing between markers is owned by the parent row (`Arrangement.spacedBy`), + * not baked in here. + */ +@Composable +fun QuietMark( + text: String? = null, + symbol: MaterialSymbol? = null, + contentDescription: String? = null, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(3.dp), + modifier = if (onClick != null) modifier.clickable(onClick = onClick) else modifier, + ) { + if (symbol != null) { + Icon( + symbol = symbol, + contentDescription = contentDescription, + tint = MaterialTheme.colorScheme.placeholderText, + modifier = Modifier.size(12.dp), + ) + } + if (text != null) { + Text( + text = text, + color = MaterialTheme.colorScheme.placeholderText, + fontSize = Font12SP, + lineHeight = Font12SP, + fontWeight = FontWeight.Medium, + maxLines = 1, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt index c23fd2d41c..af77ed2b82 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/TimeAgo.kt @@ -125,16 +125,25 @@ fun ToggleableTimeAgoText( } @Composable -fun TimeAgo(note: Note) { +fun TimeAgo( + note: Note, + style: TimeAgoStyle = TimeAgoStyle.Dotted, + fontSize: TextUnit = TextUnit.Unspecified, +) { val time = note.createdAt() ?: return - TimeAgo(time) + TimeAgo(time, style, fontSize) } @Composable -fun TimeAgo(time: Long) { +fun TimeAgo( + time: Long, + style: TimeAgoStyle = TimeAgoStyle.Dotted, + fontSize: TextUnit = TextUnit.Unspecified, +) { ToggleableTimeAgoText( timestamp = time, - style = TimeAgoStyle.Dotted, + style = style, + fontSize = fontSize, color = MaterialTheme.colorScheme.placeholderText, ) } 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 a2ccc498b7..11c461fee7 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 @@ -127,6 +127,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.ui.note.elements.Reward import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo +import com.vitorpamplona.amethyst.ui.note.elements.TimeAgoStyle import com.vitorpamplona.amethyst.ui.note.observeEdits import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.types.AudioHeader @@ -231,6 +232,7 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier import com.vitorpamplona.amethyst.ui.theme.FeedPadding +import com.vitorpamplona.amethyst.ui.theme.Font12SP import com.vitorpamplona.amethyst.ui.theme.PaddingHorizontal12Modifier import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size40dp @@ -703,7 +705,10 @@ private fun FullBleedNoteCompose( } Column(modifier = Modifier.padding(start = 10.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Size5dp), + ) { if (zapSender != null) { UsernameDisplay(zapSender, Modifier.weight(1f), accountViewModel = accountViewModel) } else { @@ -738,12 +743,15 @@ private fun FullBleedNoteCompose( Expiration(baseNote) - TimeAgo(note = baseNote) + TimeAgo(note = baseNote, style = TimeAgoStyle.Short, fontSize = Font12SP) MoreOptionsButton(baseNote, editState, accountViewModel, nav) } - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Size5dp), + ) { Column( Modifier.weight(1f), ) { @@ -765,7 +773,7 @@ private fun FullBleedNoteCompose( val baseReward = remember { noteEvent.bountyBaseReward()?.let { Reward(it) } } if (baseReward != null) { - DisplayReward(baseReward, baseNote, accountViewModel, nav) + DisplayReward(baseReward, baseNote, accountViewModel) } val pow = remember(noteEvent) { noteEvent.strongPoWOrNull() } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 994f57d2a0..a3ef6e6b7d 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -3041,6 +3041,8 @@ It\'s not possible to react a draft note It\'s not possible to zap a draft note Draft Note + Draft + Bounty From Msg