Skip to content

fix: nameless-account parity on the You and Chats tabs, and add-money from a held balance - #1315

Merged
bmc08gt merged 4 commits into
code/cashfrom
fix/nameless-account-and-add-money-gate
Aug 24, 2026
Merged

fix: nameless-account parity on the You and Chats tabs, and add-money from a held balance#1315
bmc08gt merged 4 commits into
code/cashfrom
fix/nameless-account-and-add-money-gate

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Three iOS→Android parity fixes for an account that hasn't set a display name, plus an
add-money milestone that never completed.

fix(balance): complete the add-money milestone from a held balance

The "Add Money" milestone only cleared on a deposit event, so an account that arrived
with a balance already held — restored on another device, or funded before the milestone
existed — was stuck being told to add money it already had. It now completes off the held
balance itself, matching iOS.

fix(tipping): keep the Chats tab on the list for a nameless account

The Chats tab was hidden until the account had a display name, which is backwards: a
nameless account can still receive DMs and had no way to read them. iOS shows the tab
unconditionally. The name gate stays on tipping itself, where it belongs.

fix(menu): prompt a nameless account to claim its tip card on the You tab

The You tab modelled the tip card as a nullable, so tipCard == null meant both "still
resolving" and "this account has no display name" — YouHeader opened with
if (card == null) return and drew nothing for either. A nameless account got a page with
no card, no prompt, and no way to claim one from the tab that owns it.

Replaced with an Unknown / Unclaimed / Claimed tri-state. Unclaimed draws the card
the account would have, frosted out behind the claim prompt and its CTA into display-name
entry, mirroring iOS YouScreen.setupPrompt:

  • The stand-in is the account's real scannable payload with the card's own fill turned off,
    so the 8% ground is what gets blurred. A placeholder name stands in for the empty one so
    the blurred name line has the width a real card's would (iOS placeholderName).
  • Frosted with haze's foreground blur rather than Modifier.blur. Haze draws the blur
    as a layer in front of its source, so the effect carries an opaque backgroundColor
    the flattened card ground — or the sharp code would read straight through its own
    frosting. Below API 31 (minSdk is 29) haze can't blur, and its scrim fallback draws
    nothing unless told to, so a fallbackColorEffect tint covers the same ground. Film
    grain is off: over a scannable figure it reads as noise in the code, and iOS frosts
    with a plain blur(radius: 12).
  • The tip link and the Share / Download tiles are absent while unclaimed, expansion is
    disabled, and the Sharesheet preview is no longer warmed for a card that can't be shared.
  • Resolution is gated on AuthState.Ready, so a named account — which restores its cached
    profile before auth completes — never flashes the prompt.
  • Spacing follows iOS's conditional gap: 48dp between the unclaimed stand-in and the first
    settings row versus the claimed card's 19dp
    (.padding(.top, displayName == nil ? 48 : 19)). The claimed card pays part of its
    clearance in the Share / Download tiles the unclaimed state doesn't draw. Row insets
    already match — ListItem pads grid.x5 = 25dp, the same as iOS's rowInsets.

test(menu): preview the unclaimed tip card's API 29/30 blur fallback

Haze only blurs on API 31+, and minSdk is 29 — below that it falls through to a scrim
delegate that draws nothing unless given a colour effect. That makes the stand-in's
fallbackColorEffect the only thing between an API 29/30 device and a fully legible tip
code behind the claim prompt, and nothing exercised the branch.

Haze's own blurEnabled gate is now a parameter on UnclaimedTipCardPrompt, defaulted to
HazeBlurDefaults.isBlurEnabledByDefault() so app code is unchanged, with a preview pair:
the blurred API 31+ rendering and the scrim fallback an older device draws. The fallback
preview is the one that matters — if the tint ever comes off, the code shows through it.

The "Add Money" onboarding milestone read only `MessageDao.hasEverReceivedMoney()`
— an EXISTS over the *local* message cache. An account funded before this install,
or on another device, has money but no local row to prove it, so the wallet tab
told it to add money it already had and kept the action tiles gated behind that
same flag.

Feed the milestone `hasEverReceivedMoney || holdsBalance`, sourced from a new
`TokenCoordinator.hasAnyBalance`. That flow uses `Fiat.isPositive` rather than
`hasDisplayableValue` — the question is whether the account holds anything at all,
so a dust balance that rounds away in the UI still counts.

A held balance also short-circuits `isAwaitingActivity`: the balance is a live read
of the account, not of the cache, so there is nothing left to mistake for a new
account and no reason to keep spinning on the first sync.
The tipping flow doubles as the v2 "Chats" root tab, and it opened on TipStep.Intro
whenever the account had no display name. As a sheet step that was fine; as a root
tab it replaced the whole tab — the NoChatsYet empty state was unreachable for a
nameless account, and TipInfoScreen's Close had no sheet to dismiss.

Under NewUi the flow always opens on TipStep.Tips, whatever the profile or tip-card
state looks like. The claim-your-tip-card prompt belongs on the You tab. The v1
sheet keeps Intro and the post-setup TipCard handoff unchanged.

Extracts the step derivation to `stepsFor` so both chromes are covered by tests.
… tab

The You tab modelled the tip card as a nullable, so `tipCard == null` meant both
"still resolving" and "this account has no display name". YouHeader opened with
`if (card == null) return`, which drew nothing for either — a nameless account got
a page with no card, no prompt, and no way to claim one from the tab that owns it.

Replaces the nullable with a Unknown / Unclaimed / Claimed tri-state. Unclaimed
draws the card the account *would* have, blurred out behind the claim prompt and
its CTA into display-name entry, mirroring iOS `YouScreen.setupPrompt`. The
stand-in is the account's real scannable payload over a placeholder name, built
locally, so an account whose profile the server has never returned still gets it —
with the card's own fill turned off so the 8% ground is what's blurred. Below API
31 the ground and outline stand alone rather than falling back to a sharp code.

The tip link and the Share / Download tiles are absent while unclaimed, expansion
is disabled, and the Sharesheet preview is no longer warmed for a card that can't
be shared. Resolution is gated on AuthState.Ready so a named account, which
restores its cached profile before auth completes, never flashes the prompt.
@github-actions github-actions Bot added area: tokens Token accounts, balances, token info type: fix Bug fix labels Aug 24, 2026
Haze only blurs on API 31+, and minSdk is 29. Below that it falls through to a
scrim delegate that draws nothing unless given a colour effect — so the stand-in's
fallbackColorEffect is the only thing standing between an API 29/30 device and a
fully legible tip code behind the claim prompt. Nothing exercised that branch.

Surfaces haze's own blurEnabled gate as a parameter on UnclaimedTipCardPrompt,
defaulted to HazeBlurDefaults.isBlurEnabledByDefault() so app code is unchanged,
and adds a preview pair: the blurred API 31+ rendering, and the scrim fallback an
older device draws. The fallback preview is the one that matters — if the tint ever
comes off, the code shows through it.
@bmc08gt
bmc08gt merged commit 2cae02d into code/cash Aug 24, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the fix/nameless-account-and-add-money-gate branch August 24, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: tokens Token accounts, balances, token info type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant