feat(ui): unify note-header markers into a two-tier pill/quiet-mark system

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgEpNtwnetPhETXQSdq4b5
This commit is contained in:
Claude
2026-07-13 12:28:59 +00:00
parent 718e2cba0b
commit 2fefc3a409
14 changed files with 167 additions and 149 deletions
@@ -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,
)
}
}
@@ -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) }
},
@@ -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))
}
@@ -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,
) {
@@ -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() },
)
}
@@ -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,
) {
@@ -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),
)
},
@@ -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,
)
}
@@ -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,
@@ -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(
@@ -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,
)
}
}
}
@@ -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,
)
}
@@ -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() }
+2
View File
@@ -3041,6 +3041,8 @@
<string name="it_s_not_possible_to_react_to_a_draft_note">It\'s not possible to react a draft note</string>
<string name="it_s_not_possible_to_zap_to_a_draft_note">It\'s not possible to zap a draft note</string>
<string name="draft_note">Draft Note</string>
<string name="draft">Draft</string>
<string name="bounty_label">Bounty</string>
<string name="load_from_text">From Msg</string>