fix(ui): restore full-size quiet marks, tighten the dotted timestamp, drop Boosted

Quiet marks go back to the row's regular text size in bold with 16dp
icons; the hashtag/community soft links lose their 12sp override too
(the smaller tier read as too small). A new TimeAgoStyle.DottedTight
renders "• 5m" without the leading space for rows whose spacedBy
already provides the gap, removing the double space before the
timestamp. The OTS pending pill shrinks to the stamp icon plus an
ellipsis (the words move to the content description). The Boosted mark
is removed entirely — from the Android header, the commons component,
and the desktop feed — since the repost context is already visible.

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-14 13:30:38 +00:00
parent 41ef1d89b3
commit 170bc7c121
12 changed files with 31 additions and 111 deletions
@@ -78,7 +78,6 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.DisplayZapSplits
import com.vitorpamplona.amethyst.ui.note.elements.BoostedMark
import com.vitorpamplona.amethyst.ui.note.elements.DisplayEditStatus
import com.vitorpamplona.amethyst.ui.note.elements.DisplayFollowingCommunityInPost
import com.vitorpamplona.amethyst.ui.note.elements.DisplayFollowingHashtagsInPost
@@ -91,6 +90,7 @@ 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
@@ -1834,10 +1834,6 @@ fun FirstUserInfoRow(
NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel)
}
if (isRepost) {
BoostedMark()
}
CheckAndDisplayEditStatus(editState)
if (isDraft) {
@@ -1899,7 +1895,7 @@ fun FirstUserInfoRow(
// The dotted TimeAgo carries its own " • " separator and the options
// button has slack around its icon, so this pair stays unspaced.
Row(verticalAlignment = CenterVertically) {
TimeAgo(baseNote)
TimeAgo(baseNote, style = TimeAgoStyle.DottedTight)
if (moreOptions == null) {
MoreOptionsButton(baseNote, editState, accountViewModel, nav)
@@ -1,31 +0,0 @@
/*
* 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.runtime.Composable
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ui.note.QuietMark
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
fun BoostedMark() {
QuietMark(text = stringRes(id = R.string.boosted))
}
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note.elements
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -35,7 +34,6 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNo
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.core.Address
import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress
@@ -66,7 +64,6 @@ private fun DisplayCommunity(
ClickableTextColor(
label,
style = LocalTextStyle.current.copy(fontSize = Font12SP),
linkColor = MaterialTheme.colorScheme.lessImportantLink,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.note.elements
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -39,7 +38,6 @@ import com.vitorpamplona.amethyst.model.Note
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
@@ -85,7 +83,6 @@ private fun DisplayTagList(
) {
ClickableTextColor(
"#$firstTag",
style = LocalTextStyle.current.copy(fontSize = Font12SP),
linkColor = MaterialTheme.colorScheme.lessImportantLink,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
@@ -74,10 +74,12 @@ fun DisplayOts(
)
},
whenPending = {
// The stamp icon plus an ellipsis: attestation requested, not yet
// anchored. The content description carries the words.
HeaderPill(
symbol = MaterialSymbols.OpenTimestamps,
text = stringRes(id = R.string.pending),
contentDescription = stringRes(R.string.ots_info_title),
text = "…",
contentDescription = stringRes(R.string.timestamp_pending_short),
)
},
)
@@ -29,7 +29,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.model.nip18Reposts.RepostAction.repost
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.LocalCache
@@ -44,7 +43,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.Dispatchers
@@ -59,7 +57,6 @@ private val AUTHOR_METADATA_ID = "e1".repeat(32)
private val LONG_NAME_METADATA_ID = "e2".repeat(32)
private val PLAIN_ID = "1".repeat(64)
private val REPOST_ID = "2".repeat(64)
private val RUMOR_ID = "3".repeat(64)
private val EDITED_ID = "4".repeat(64)
private val EDIT_VERSION_ID = "5".repeat(64)
@@ -108,7 +105,6 @@ fun NoteHeaderFirstRowDensityPreview() {
val draftEvent = DraftWrapEvent(DRAFT_ID, AUTHOR, now - 300, arrayOf(arrayOf("d", "preview-draft")), "", "x")
val plain: Note = LocalCache.getOrCreateNote(PLAIN_ID)
val repost: Note = LocalCache.getOrCreateNote(REPOST_ID)
val rumorPinned: Note = LocalCache.getOrCreateNote(RUMOR_ID)
val edited: Note = LocalCache.getOrCreateNote(EDITED_ID)
val editVersion: Note = LocalCache.getOrCreateNote(EDIT_VERSION_ID)
@@ -132,7 +128,6 @@ fun NoteHeaderFirstRowDensityPreview() {
LocalCache.justConsume(plainEvent, null, true)
LocalCache.justConsume(TextNoteEvent(EDIT_VERSION_ID, AUTHOR, now - 60, emptyArray(), "GM! (fixed typo)", "x"), null, true)
LocalCache.justConsume(RepostEvent(REPOST_ID, AUTHOR, now - 300, arrayOf(arrayOf("e", PLAIN_ID)), plainEvent.toJson(), "x"), null, true)
// An empty sig is what marks a note as a private rumor.
LocalCache.justConsume(TextNoteEvent(RUMOR_ID, AUTHOR, now - 300, emptyArray(), "just between us", ""), null, true)
LocalCache.justConsume(TextNoteEvent(EDITED_ID, AUTHOR, now - 300, emptyArray(), "GM!", "x"), null, true)
@@ -197,9 +192,6 @@ fun NoteHeaderFirstRowDensityPreview() {
// Bare minimum: username + time + options
HeaderRowSample(plain, accountViewModel)
// One quiet mark: repost
HeaderRowSample(repost, accountViewModel)
// Quiet icon marks: private rumor + pinned
HeaderRowSample(rumorPinned, accountViewModel, isPinned = true)
@@ -53,6 +53,12 @@ enum class TimeAgoStyle {
/** "• 5m", uses [timeAgo]. Used by note timestamps and chatroom row last-message time. */
Dotted,
/**
* "• 5m" without the leading space — for rows whose `Arrangement.spacedBy`
* already provides the gap before the timestamp (the note-header first row).
*/
DottedTight,
/** "5m" (no dot, no leading space), uses [timeAgoShort]. Used by chat bubbles and channel headers. */
Short,
}
@@ -96,6 +102,7 @@ fun ToggleableTimeAgoText(
if (showAbsolute) {
when (style) {
TimeAgoStyle.Dotted -> timeAbsolute(timestamp, context)
TimeAgoStyle.DottedTight -> timeAbsolute(timestamp, context).trimStart()
TimeAgoStyle.Short -> timeAbsoluteNoDot(timestamp, context)
}
} else {
@@ -104,6 +111,7 @@ fun ToggleableTimeAgoText(
nowState.value
when (style) {
TimeAgoStyle.Dotted -> timeAgo(timestamp, context)
TimeAgoStyle.DottedTight -> timeAgo(timestamp, context).trimStart()
TimeAgoStyle.Short -> timeAgoShort(timestamp, nowStr)
}
}
@@ -125,16 +133,22 @@ fun ToggleableTimeAgoText(
}
@Composable
fun TimeAgo(note: Note) {
fun TimeAgo(
note: Note,
style: TimeAgoStyle = TimeAgoStyle.Dotted,
) {
val time = note.createdAt() ?: return
TimeAgo(time)
TimeAgo(time, style)
}
@Composable
fun TimeAgo(time: Long) {
fun TimeAgo(
time: Long,
style: TimeAgoStyle = TimeAgoStyle.Dotted,
) {
ToggleableTimeAgoText(
timestamp = time,
style = TimeAgoStyle.Dotted,
style = style,
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
@@ -744,7 +745,7 @@ private fun FullBleedNoteCompose(
// The dotted TimeAgo carries its own " • " separator and the
// options button has slack around its icon: keep the pair unspaced.
Row(verticalAlignment = Alignment.CenterVertically) {
TimeAgo(note = baseNote)
TimeAgo(note = baseNote, style = TimeAgoStyle.DottedTight)
MoreOptionsButton(baseNote, editState, accountViewModel, nav)
}
-1
View File
@@ -45,7 +45,6 @@
<string name="timestamp_it">Timestamp it</string>
<string name="timestamp_pending">Timestamp: Pending Confirmations</string>
<string name="timestamp_pending_short">OTS: Pending</string>
<string name="pending">Pending</string>
<string name="request_deletion">Request Deletion</string>
<string name="block_report">Block / Report</string>
<string name="block_hide_user"><![CDATA[Block & Hide User]]></string>
@@ -1,40 +0,0 @@
/*
* 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.elements
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun BoostedMark() {
Text(
"Boosted",
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
modifier = Modifier.padding(start = 5.dp),
)
}
@@ -31,18 +31,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
private val QuietMarkFontSize = 12.sp
/**
* Quiet passive-state marker for note headers: Boosted, Draft, Edited, pinned,
* Quiet passive-state marker for note headers: 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.
* system: bold gray text at the row's text size with an optional 16dp icon.
* Contrast with [HeaderPill], the chip tier for tappable/verifiable metadata.
*
* Spacing between markers is owned by the parent row (`Arrangement.spacedBy`),
* not baked in here.
@@ -65,16 +62,14 @@ fun QuietMark(
symbol = symbol,
contentDescription = contentDescription,
tint = MaterialTheme.colorScheme.placeholderText,
modifier = Modifier.size(12.dp),
modifier = Modifier.size(16.dp),
)
}
if (text != null) {
Text(
text = text,
color = MaterialTheme.colorScheme.placeholderText,
fontSize = QuietMarkFontSize,
lineHeight = QuietMarkFontSize,
fontWeight = FontWeight.Medium,
fontWeight = FontWeight.Bold,
maxLines = 1,
)
}
@@ -99,7 +99,6 @@ import com.vitorpamplona.amethyst.commons.search.QuerySerializer
import com.vitorpamplona.amethyst.commons.search.SearchResultFilter
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.components.LoadingState
import com.vitorpamplona.amethyst.commons.ui.elements.BoostedMark
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
import com.vitorpamplona.amethyst.commons.ui.feeds.NewPostsChip
import com.vitorpamplona.amethyst.commons.ui.feeds.StickToTopOnPrepend
@@ -304,7 +303,6 @@ private fun FeedNoteCardBody(
)
},
)
BoostedMark()
}
// Original note content with actions inside card