feat(ui): tap explainers for the PoW, private-rumor and expiration markers

Following the OTS pill's pattern, tapping now explains what the marker
means: PoW describes the mining difficulty as an anti-spam stamp
(pow_info_description), the private-rumor lock explains the unsigned
private delivery and its deniability (private_rumor_info_*), and the
expiration pill shows the request to delete plus the exact expiry
date/time (expiration_info_description).

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 14:31:23 +00:00
parent 500de62841
commit e684cb02a3
5 changed files with 51 additions and 14 deletions
@@ -355,6 +355,8 @@ import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Date
@Composable
fun NoteCompose(
@@ -1761,7 +1763,10 @@ fun SecondUserInfoRow(
}
@Composable
fun DisplayExpiration(expirationDate: Long) {
fun DisplayExpiration(
expirationDate: Long,
accountViewModel: AccountViewModel,
) {
val context = LocalContext.current
// Beyond a year timeAheadNoDot switches to a full date, which is too wide
// for the pill: clamp it.
@@ -1775,6 +1780,13 @@ fun DisplayExpiration(expirationDate: Long) {
symbol = MaterialSymbols.Timer,
text = text,
contentDescription = stringRes(R.string.expiration_date_label),
onClick = {
accountViewModel.toastManager.toast(
R.string.expiration_date_label,
R.string.expiration_info_description,
SimpleDateFormat.getDateTimeInstance().format(Date(expirationDate * 1000)),
)
},
)
}
@@ -1886,13 +1898,13 @@ fun FirstUserInfoRow(
val pow = remember(noteEvent) { noteEvent?.strongPoWOrNull() }
if (pow != null) {
DisplayPoW(pow)
DisplayPoW(pow, accountViewModel)
}
Expiration(baseNote)
Expiration(baseNote, accountViewModel)
if (baseNote.isPrivateRumor()) {
PrivateRumorMark()
PrivateRumorMark(accountViewModel)
}
if (isPinned) {
@@ -1924,10 +1936,16 @@ fun PinnedMark() {
}
@Composable
fun PrivateRumorMark() {
fun PrivateRumorMark(accountViewModel: AccountViewModel) {
QuietMark(
symbol = MaterialSymbols.Lock,
contentDescription = stringRes(R.string.private_rumor_mark),
onClick = {
accountViewModel.toastManager.toast(
R.string.private_rumor_info_title,
R.string.private_rumor_info_description,
)
},
)
}
@@ -1968,12 +1986,15 @@ fun JumpToParentReplyButton(
}
@Composable
fun Expiration(note: Note) {
fun Expiration(
note: Note,
accountViewModel: AccountViewModel,
) {
val event = note.event
if (event != null) {
val expires = remember(event) { event.expiration() }
if (expires != null) {
DisplayExpiration(expires)
DisplayExpiration(expires, accountViewModel)
}
}
}
@@ -25,6 +25,8 @@ import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.ui.note.HeaderPill
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
@@ -32,20 +34,30 @@ import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
@Preview
fun DisplayPoWPreview() {
ThemeComparisonColumn(
toPreview = { DisplayPoW(pow = 24) },
toPreview = { DisplayPoW(pow = 24, accountViewModel = mockAccountViewModel()) },
)
}
/**
* Compact pill showing the proof of work a received note carries: a gear plus
* the difficulty in leading zero bits. Sits inline in note headers, so it
* stays at text height.
* the difficulty in leading zero bits. Tapping it explains what the number
* means. Sits inline in note headers, so it stays at text height.
*/
@Composable
fun DisplayPoW(pow: Int) {
fun DisplayPoW(
pow: Int,
accountViewModel: AccountViewModel,
) {
HeaderPill(
symbol = MaterialSymbols.Manufacturing,
text = pow.toString(),
contentDescription = stringRes(R.string.pow_settings_title),
onClick = {
accountViewModel.toastManager.toast(
R.string.pow_settings_title,
R.string.pow_info_description,
pow.toString(),
)
},
)
}
@@ -289,7 +289,7 @@ fun NormalChatNote(
val pow = remember(note) { note.event?.strongPoWOrNull() }
if (pow != null) {
Spacer(StdHorzSpacer)
DisplayPoW(pow)
DisplayPoW(pow, accountViewModel)
}
} else {
DisplayDraftChat()
@@ -740,7 +740,7 @@ private fun FullBleedNoteCompose(
CheckAndDisplayEditStatus(editState)
Expiration(baseNote)
Expiration(baseNote, accountViewModel)
// The dotted TimeAgo carries its own " • " separator and the
// options button has slack around its icon: keep the pair unspaced.
@@ -781,7 +781,7 @@ private fun FullBleedNoteCompose(
val pow = remember(noteEvent) { noteEvent.strongPoWOrNull() }
if (pow != null) {
DisplayPoW(pow)
DisplayPoW(pow, accountViewModel)
}
if (isDraft) {
+4
View File
@@ -1234,6 +1234,8 @@
<string name="public_bookmark_presence_indicator">is a public bookmark here</string>
<string name="private_bookmark_presence_indicator">is a private bookmark here</string>
<string name="private_rumor_mark">Private — only visible to tagged participants</string>
<string name="private_rumor_info_title">Private Note</string>
<string name="private_rumor_info_description">This note was delivered privately and is not signed. Only the tagged participants can see it, and nobody can prove to outsiders who wrote it.</string>
<string name="private_note">Make private: gift-wrap the note to the notified users only</string>
<string name="disable_private_note">Make public</string>
<string name="private_note_locked">Replies to a private note always stay private</string>
@@ -2822,6 +2824,7 @@
<string name="add_expiration_date">Add expiration date</string>
<string name="remove_expiration_date">Remove expiration date</string>
<string name="expiration_date_label">Expiration Date</string>
<string name="expiration_info_description">The author asked relays to delete this note after %1$s. It will disappear from feeds once it expires.</string>
<string name="expiration_date_explainer">Post will be hidden by clients after this date (NIP-40)</string>
<string name="expiration_date_select">Select expiration date and time</string>
<string name="expiration_expires_in">Expires in %1$s</string>
@@ -3776,6 +3779,7 @@
<string name="compose_signature_setting_description">Added at the end of the message when opening a new post, reply, quote, or article. Leave empty to disable.</string>
<string name="compose_signature_setting_hint">Your signature</string>
<string name="pow_settings_title">Proof of Work</string>
<string name="pow_info_description">The author spent computing power to mine this note to difficulty %1$s — a proof-of-work stamp that makes spamming expensive.</string>
<string name="pow_difficulty_title">Difficulty</string>
<string name="pow_difficulty_explainer">Mines a NIP-13 proof of work into your posts before publishing so relays and readers can weigh them against spam. Higher values take exponentially longer to mine. Posts publish in the background once mined.</string>
<string name="pow_difficulty_off">Off</string>