feat(pow): move composer PoW button before Subject; switch icon to Manufacturing

The per-post PoW button on the New Post screen now sits before the
Subject/Title toggle instead of last in the options row.

Every PoW surface (composer button, note chip, mining banner, settings tile,
relay-info minimum-PoW row) switches from the Bolt icon to Manufacturing
(gear, U+E726): Bolt is the app's zap/lightning glyph everywhere else, so a
Bolt PoW badge next to the zap-split/zapraiser/invoice buttons read as a
payment feature. Subset font regenerated for the new codepoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADb3dez9jPk6QqyQ1rTx4V
This commit is contained in:
Claude
2026-07-11 01:33:54 +00:00
parent 80ece7c40f
commit 39ffb0ed4d
8 changed files with 21 additions and 19 deletions
@@ -161,10 +161,10 @@ private fun MiningContent(
miningJobs: ImmutableList<PoWJobState>,
onCancelJob: (String) -> Unit,
) {
// one shared pulse for the bolt — mining has no measurable progress, so
// one shared pulse for the gear — mining has no measurable progress, so
// the animation is what says "the app is working right now".
val pulse = rememberInfiniteTransition(label = "miningPulse")
val boltAlpha by pulse.animateFloat(
val gearAlpha by pulse.animateFloat(
initialValue = 0.35f,
targetValue = 1f,
animationSpec =
@@ -172,7 +172,7 @@ private fun MiningContent(
animation = tween(700),
repeatMode = RepeatMode.Reverse,
),
label = "boltAlpha",
label = "gearAlpha",
)
// 1 Hz clock driving the per-job elapsed labels; only ticks while some
@@ -194,9 +194,9 @@ private fun MiningContent(
modifier = Modifier.fillMaxWidth(),
) {
Icon(
symbol = MaterialSymbols.Bolt,
symbol = MaterialSymbols.Manufacturing,
contentDescription = stringRes(R.string.pow_mining_title),
tint = MaterialTheme.colorScheme.primary.copy(alpha = boltAlpha),
tint = MaterialTheme.colorScheme.primary.copy(alpha = gearAlpha),
modifier = Modifier.size(18.dp),
)
@@ -52,9 +52,10 @@ val POW_PRESETS = listOf(16, 20, 24, 28)
/**
* Composer options-row button showing the NIP-13 difficulty this post will be
* mined at: a bolt with the difficulty as a small badge when mining is on,
* a dimmed bolt when off. Tapping opens a menu to raise/lower/disable mining
* for this post only — the account setting is untouched.
* mined at: a manufacturing gear with the difficulty as a small badge when
* mining is on, a dimmed gear when off. Tapping opens a menu to
* raise/lower/disable mining for this post only — the account setting is
* untouched.
*
* [effectiveDifficulty] is what will actually be used at send time (override
* or account default); null/0 means the post publishes without PoW.
@@ -80,7 +81,7 @@ fun PowOverrideButton(
.width(23.dp),
) {
Icon(
symbol = MaterialSymbols.Bolt,
symbol = MaterialSymbols.Manufacturing,
contentDescription = stringRes(R.string.pow_settings_title),
modifier = Modifier.size(18.dp).align(Alignment.BottomStart),
tint =
@@ -67,7 +67,7 @@ fun DisplayPoW(pow: Int) {
.padding(horizontal = 6.dp, vertical = 1.dp),
) {
Icon(
symbol = MaterialSymbols.Bolt,
symbol = MaterialSymbols.Manufacturing,
contentDescription = stringRes(R.string.pow_settings_title),
tint = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier.size(12.dp),
@@ -834,6 +834,13 @@ private fun BottomRowActions(
}
}
PowOverrideButton(
effectiveDifficulty = postViewModel.effectivePowDifficulty(),
defaultDifficulty = postViewModel.defaultPowDifficulty(),
isOverridden = postViewModel.powOverride != null,
onSelect = { postViewModel.powOverride = it },
)
// A group thread's title is required, so the field is always shown for it — no toggle.
if (postViewModel.groupThreadTarget == null) {
AddSubjectButton(postViewModel.wantsSubject) {
@@ -869,13 +876,6 @@ private fun BottomRowActions(
postViewModel.wantsInvoice = !postViewModel.wantsInvoice
}
}
PowOverrideButton(
effectiveDifficulty = postViewModel.effectivePowDifficulty(),
defaultDifficulty = postViewModel.defaultPowDifficulty(),
isOverridden = postViewModel.powOverride != null,
onSelect = { postViewModel.powOverride = it },
)
}
}
@@ -939,7 +939,7 @@ fun LimitationsCard(lim: Nip11RelayInformation.RelayInformationLimitation) {
val minPoW = lim.min_pow_difficulty
if (minPoW != null && minPoW > 0) {
InfoRow(MaterialSymbols.Bolt, stringRes(R.string.minimum_pow), stringRes(R.string.amount_in_bits, minPoW))
InfoRow(MaterialSymbols.Manufacturing, stringRes(R.string.minimum_pow), stringRes(R.string.amount_in_bits, minPoW))
} else {
lim.min_prefix?.let {
if (it > 0) {
@@ -144,7 +144,7 @@ private fun PowDifficultyTile(accountViewModel: AccountViewModel) {
.collectAsStateWithLifecycle()
SettingsBlockTile(
icon = MaterialSymbols.Bolt,
icon = MaterialSymbols.Manufacturing,
title = stringRes(R.string.pow_difficulty_title),
description = stringRes(R.string.pow_difficulty_explainer),
) {
@@ -152,6 +152,7 @@ object MaterialSymbols {
val Lock = MaterialSymbol("\uE899")
val LockOpen = MaterialSymbol("\uE898")
val Mail = MaterialSymbol("\uE159")
val Manufacturing = MaterialSymbol("\uE726")
val MenuBook = MaterialSymbol("\uEA19")
val Mic = MaterialSymbol("\uE31D")
val MicOff = MaterialSymbol("\uE02B")