diff options
| author | Adam Soltys <[email protected]> | 2026-08-03 08:48:18 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-03 08:48:18 -0700 |
| commit | ff144b0b057e8ac76a628adfa02c556d5c6214c9 (patch) | |
| tree | dcf8ebe9a5cca0fd354d373d04fb9b1a01b3b556 /src/app.js | |
| parent | 59d0991115465106d960d1983fdd1b7c94aef7e6 (diff) | |
| download | coinosv3-ff144b0b057e8ac76a628adfa02c556d5c6214c9.tar.gz coinosv3-ff144b0b057e8ac76a628adfa02c556d5c6214c9.zip | |
Balance card: one account at a time, swipe or dots to flip
Replaces the segmented toggle and the always-visible second balance.
The card now shows the account you're in at full size with a plain-word
descriptor ("Instant · free to send" / "On-chain · mining fees apply"),
two dots to flip, and a sideways slide when it changes. A swipe that
starts on the card switches account instead of tab; swipes anywhere
else still move between Receive/Send/History.
Fixes a real mobile bug found while testing: a touch sequence still
fires a compatibility click at the release point, so every swipe was
ALSO pressing whatever sat under the finger (which is what kept
bouncing the test wallet back to the unlock screen). One click is now
swallowed after a recognized swipe.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
Diffstat (limited to 'src/app.js')
| -rw-r--r-- | src/app.js | 76 |
1 files changed, 50 insertions, 26 deletions
@@ -2254,7 +2254,10 @@ function accountSel() { } return ui.account; } -function setAccountSel(a) { +let _accDir = null; // which way the balance face should slide in +function setAccountSel(a, dir) { + if (ui.account === a) return; + _accDir = dir || (a === 'savings' ? 'left' : 'right'); ui.account = a; try { localStorage.setItem(ACCOUNT_KEY, a); } catch {} render(); @@ -2276,36 +2279,39 @@ function balanceCard() { const saving = wallet.spendable; const hasArk = !!featureHook('arkReady'); const sel = hasArk ? accountSel() : 'savings'; - return h( - 'div', - { class: 'card balance' }, - // The account toggle IS the headline label: you're always "in" Spending - // or Savings, and everything (receive, gifts) follows. No total — the two - // are different kinds of money, and adding them up says nothing useful. - hasArk - ? h('div', { class: 'seg balance-seg' }, - h('button', { class: sel === 'spending' ? 'active' : '', onClick: () => setAccountSel('spending') }, t('spendingLabel')), - h('button', { class: sel === 'savings' ? 'active' : '', onClick: () => setAccountSel('savings') }, t('savingLabel'))) - : h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.05em' }, t('balance')), + const isSpending = sel === 'spending'; + // One balance at a time, full size: the card IS the account you're in, and + // everything (receive, gifts) follows it. Swipe or tap the dots to flip. + const dtAcc = animWindow('acct', sel, 300); + const face = h('div', { class: 'balance-face' }, + h('div', { class: 'row between', style: 'align-items:center' }, + h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.06em' }, + hasArk ? (isSpending ? t('spendingLabel') : t('savingLabel')) : t('balance')), + hasArk + ? h('div', { class: 'balance-dots' }, + h('button', { class: 'bdot' + (isSpending ? ' on' : ''), title: t('spendingLabel'), onClick: () => setAccountSel('spending') }), + h('button', { class: 'bdot' + (!isSpending ? ' on' : ''), title: t('savingLabel'), onClick: () => setAccountSel('savings') })) + : null), h('div', { class: 'amt', style: firstLoad ? 'opacity:.3' : '' }, firstLoad ? h('span', { class: 'spinner sm', style: 'margin-right:8px' }) : null, - animatedAmount('bal:' + sel, sel === 'spending' ? spending : saving), ' ', unitTag('unit')), - hasArk || pending > 0 || featLines.length - ? h( - 'div', - { class: 'split' }, - hasArk - ? h('div', {}, - h('div', { class: 'k' }, sel === 'spending' ? t('savingLabel') : t('spendingLabel')), - h('div', { class: 'v' }, animatedAmount('bal2:' + sel, sel === 'spending' ? saving : spending), ' ', unitTag())) - : null, + animatedAmount('bal:' + sel, isSpending ? spending : saving), ' ', unitTag('unit')), + hasArk + ? h('div', { class: 'small muted balance-note' }, isSpending ? t('balanceNoteSpending') : t('balanceNoteSavings')) + : null, + pending > 0 || featLines.length + ? h('div', { class: 'split' }, pending > 0 ? h('div', {}, h('div', { class: 'k' }, t('pending')), h('div', { class: 'v pending' }, fmtAmount(pending), ' ', unitTag())) : null, ...featLines.map((l) => - h('div', {}, h('div', { class: 'k' }, l.label), h('div', { class: 'v' }, fmtAmount(l.sat), ' ', unitTag()))) - ) - : null, + h('div', {}, h('div', { class: 'k' }, l.label), h('div', { class: 'v' }, fmtAmount(l.sat), ' ', unitTag())))) + : null); + applyAnim(face, 'anim-tab-' + (_accDir || 'left'), dtAcc); + _accDir = null; + return h( + 'div', + { class: 'card balance' }, + face, // "Move money" and friends: the only door between the two balances. ...featureAll('balanceActions').map((a2) => h('button', { class: 'btn-sm', style: 'margin-top:10px', onClick: a2.onClick }, a2.label)), @@ -3387,11 +3393,18 @@ applyDir(); // inside chat, profiles, takeovers, or the (offline-forced) settings tab. (() => { const ORDER = ['receive', 'send', 'history']; - let sx = 0, sy = 0, t0 = 0, live = false; + let sx = 0, sy = 0, t0 = 0, live = false, onBalance = false, swallowClick = false; + window.addEventListener('click', (e) => { + if (!swallowClick) return; + swallowClick = false; + e.stopPropagation(); + e.preventDefault(); + }, true); window.addEventListener('touchstart', (e) => { live = e.touches.length === 1 && ui.screen === 'wallet' && !ui.chatOpen && !ui.profilePk && !ui.arkExitPage && ORDER.includes(ui.tab); if (!live) return; + onBalance = !!(e.target.closest && e.target.closest('.balance')); sx = e.touches[0].clientX; sy = e.touches[0].clientY; t0 = Date.now(); @@ -3403,6 +3416,17 @@ applyDir(); const t = e.changedTouches[0]; const dx = t.clientX - sx, dy = t.clientY - sy; if (Date.now() - t0 > 600 || Math.abs(dx) < 60 || Math.abs(dx) < 1.8 * Math.abs(dy)) return; + // A touch sequence still fires a compatibility click at the release + // point; after a swipe that would ALSO press whatever is under the + // finger. Swallow exactly one click. + swallowClick = true; + setTimeout(() => { swallowClick = false; }, 400); + // a swipe that began on the balance card flips the account instead + if (onBalance) { + const want = dx < 0 ? 'savings' : 'spending'; + if (featureHook('arkReady')) setAccountSel(want, dx < 0 ? 'left' : 'right'); + return; + } const next = ORDER[ORDER.indexOf(ui.tab) + (dx < 0 ? 1 : -1)]; if (!next) return; _swipeDir = dx < 0 ? 'left' : 'right'; |
