aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.js8
-rw-r--r--src/features/messages.js13
-rw-r--r--src/features/zaps.js6
-rw-r--r--src/i18n.js1
-rw-r--r--src/zap-animation.js5
5 files changed, 26 insertions, 7 deletions
diff --git a/src/app.js b/src/app.js
index 16f0595..6a8b6a6 100644
--- a/src/app.js
+++ b/src/app.js
@@ -5376,6 +5376,14 @@ const ctx = {
wallet.saveFeatureState('prefs', p);
try { wallet.saveCache(); } catch {}
},
+ // the tap's sound effect — on unless switched off from the held-⚡ screen
+ zapSound: () => !wallet.loadFeatureState || wallet.loadFeatureState('prefs', {}).zapSound !== false,
+ setZapSound: (on) => {
+ const p = wallet.loadFeatureState('prefs', {});
+ if (on) delete p.zapSound; else p.zapSound = false;
+ wallet.saveFeatureState('prefs', p);
+ try { wallet.saveCache(); } catch {}
+ },
// Per-recipient auto-pay budget: a fixed-amount LNURL/address the user
// trusts to pay without confirmation, up to this many sats. Keyed by the
// recipient's address/lnurl. Synced with the rest of prefs.
diff --git a/src/features/messages.js b/src/features/messages.js
index 24542b9..111758e 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -3119,7 +3119,7 @@ export function messagesFeature(ctx) {
if (!id || !sats) return;
const list = pendingOf(id)?.list || [];
zapPending.set(id, [...list, { sats, at: Date.now(), state: 'flying' }]);
- animateZap(sats, origin, id);
+ animateZap(sats, origin, id, zapSoundOn());
render();
// repaint when the in-flight chip would expire, so a zap nobody ever
// reported on doesn't pulse forever
@@ -3159,8 +3159,9 @@ export function messagesFeature(ctx) {
}
// The chip: a little bolt + the sats total. Absent until the first receipt
// — or until you zap it yourself, which is its own kind of receipt.
+ const zapSoundOn = () => (ctx.zapSound ? ctx.zapSound() : true);
function zapChip(id, { onClick, onHold, cls = '' } = {}) {
- if (onClick) warmZapSound();
+ if (onClick && zapSoundOn()) warmZapSound();
const z = zapTotals.get(id) || zapSeeds().get(id);
const p = pendingOf(id);
const optimistic = p ? p.sats : 0;
@@ -4348,7 +4349,7 @@ export function messagesFeature(ctx) {
// the main action, zapping this note with the new amount the second.
function openZapSettings(pk, id, origin) {
const cur = ctx.zapDefaultSat ? ctx.zapDefaultSat() : 0;
- ui.zapSetup = { pk, npub: npubOf(pk), eventId: id, amount: String(cur || 21), origin, edit: true };
+ ui.zapSetup = { pk, npub: npubOf(pk), eventId: id, amount: String(cur || 21), origin, edit: true, sound: zapSoundOn() };
render();
}
@@ -4359,6 +4360,7 @@ export function messagesFeature(ctx) {
const n = parseInt(s.amount, 10);
if (!n || n <= 0) { toast(t('enterValidAmtForN', { n: 1 })); return 0; }
ctx.setZapDefaultSat(n);
+ if (s.edit && ctx.setZapSound && s.sound !== zapSoundOn()) ctx.setZapSound(s.sound);
return n;
};
const saveAndZap = () => {
@@ -4389,6 +4391,9 @@ export function messagesFeature(ctx) {
h('div', { class: 'input-group' },
h('input', { type: 'number', min: '1', value: s.amount, onInput: (e) => { s.amount = e.target.value; } }),
h('span', { class: 'small muted', style: 'align-self:center;padding:0 8px' }, 'sats')),
+ s.edit ? h('label', { class: 'row gap6', style: 'align-items:center;cursor:pointer' },
+ h('input', { type: 'checkbox', checked: s.sound, style: 'width:18px;height:18px;accent-color:var(--accent);margin:0', onChange: (e) => { s.sound = e.target.checked; } }),
+ h('span', { class: 'small' }, t('zapSoundToggle'))) : null,
s.edit
? [h('button', { class: 'btn-primary btn-block', onClick: saveOnly }, t('save')),
h('button', { class: 'btn-ghost btn-block', onClick: saveAndZap }, t('zapSetupSave'))]
@@ -4622,7 +4627,7 @@ export function messagesFeature(ctx) {
const I_ZAP = ICON('<path d="M13 2L4 14h7l-1 8 9-12h-7l1-8z"/>', true);
function noteActions(pk, ev, { canZap }) {
- if (canZap) warmZapSound(); // the clip is decoded before the first tap
+ if (canZap && zapSoundOn()) warmZapSound(); // the clip is decoded before the first tap
const mineReact = myReactOn(ev.id);
const rm = reacts.get(ev.id);
const likeN = rm ? [...rm.values()].reduce((n, { who }) => n + who.size, 0) : 0;
diff --git a/src/features/zaps.js b/src/features/zaps.js
index 54c8f58..d6b2271 100644
--- a/src/features/zaps.js
+++ b/src/features/zaps.js
@@ -476,7 +476,11 @@ export function zapsFeature(ctx) {
style: 'text-align:right',
onInput: (e) => { const n = parseInt(e.target.value, 10); if (n > 0) ctx.setZapDefaultSat(n); } }),
h('span', { class: 'small muted', style: 'align-self:center;padding:0 6px' }, 'sats'))),
- h('div', { class: 'small faint' }, t('zapDefaultHint')))];
+ h('div', { class: 'small faint' }, t('zapDefaultHint')),
+ ctx.setZapSound ? h('label', { class: 'row gap6', style: 'align-items:center;cursor:pointer;margin-top:4px' },
+ h('input', { type: 'checkbox', checked: ctx.zapSound(), style: 'width:18px;height:18px;accent-color:var(--accent);margin:0',
+ onChange: (e) => { ctx.setZapSound(e.target.checked); } }),
+ h('span', { class: 'small' }, t('zapSoundToggle'))) : null)];
},
};
}
diff --git a/src/i18n.js b/src/i18n.js
index 84d1a5a..066ebce 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -964,6 +964,7 @@ const STR = {
zapSetupSave: 'Save and zap',
zapSettingsDesc: 'This is what one tap on ⚡ sends — no form, no confirmation. Tap again while one is still on its way to send another.',
zapAmountSaved: 'One tap now zaps {n} sats',
+ zapSoundToggle: 'Play a sound when you zap',
zapDefaultLabel: 'One-tap zap amount',
zapDefaultHint: 'What a single tap of the ⚡ button sends.',
threadNoReplies: 'No replies yet.',
diff --git a/src/zap-animation.js b/src/zap-animation.js
index 20a6b60..d03285b 100644
--- a/src/zap-animation.js
+++ b/src/zap-animation.js
@@ -37,10 +37,11 @@ function tremblePost(id) {
}
}
-export function animateZap(sats, origin, postId) {
+export function animateZap(sats, origin, postId, withSound = true) {
// Warm: play inside the tap itself (the audio context resumes on a
// gesture). Cold: play when the chunk lands, if that's still soon enough.
- if (sound) sound.playZapSound();
+ if (!withSound) {} // switched off from the held-⚡ screen: the strike stays
+ else if (sound) sound.playZapSound();
else { const tappedAt = performance.now(); warmZapSound().then((m) => { if (m) m.playZapSound(tappedAt); }); }
try { tremblePost(postId); } catch {} // Optional motion must never block a zap.
const layer = document.createElement('div');