Skip to content

a11y: decorative/meaningful image semantics, labeled icon buttons, audible addresses - #3465

Open
sethforprivacy wants to merge 3 commits into
devfrom
a11y/image-icon-semantics
Open

a11y: decorative/meaningful image semantics, labeled icon buttons, audible addresses#3465
sethforprivacy wants to merge 3 commits into
devfrom
a11y/image-icon-semantics

Conversation

@sethforprivacy

Copy link
Copy Markdown
Contributor

Issue Number (if Applicable): Related to #3402, #3406 — Jira CW-1574

Description

Part 3 of the screen-reader accessibility remediation (VoiceOver/TalkBack): image and compact-icon primitives.

Changes

  • CakeImageWidget (186 call sites): new optional semanticsLabel, honored in all four render branches (precompiled vector asset, raster asset, network SVG, network raster). null — the default — now means genuinely decorative: the image contributes no accessibility node instead of an unnamed image node. Built-in loading spinners and the error placeholder no longer create unlabeled focus stops; caller-supplied errorWidgets keep their own semantics (several call sites render meaningful initials there).
  • FloatingIconButton: new required semanticLabel, exposed as one labeled button node. All 6 call sites migrated (paste ×4, scan QR, address book) using existing ARB keys.
  • RoundedCheckbox: previously the unchecked state rendered Offstage() — selection state was completely invisible to screen readers. It now always exposes checked: state (no visual change), with an excludeFromSemantics escape hatch for rows that announce their own selection (used by the receive address-type list PR in this series).
  • Receive QR code: the QR block is now a single labeled button node ("QR code for your receive address", hint = existing qr_fullscreen key), with the QR matrix, embedded logo, badge and wordmark excluded; the payjoin badge merges icon+text into one node.
  • AddressFormatter: both segmented-address RichText builders now carry the full address as their semantics label, so screen readers read the actual address instead of 4-character pseudo-words ("beesh onekay…") or "dot dot dot" for the truncation ellipsis. This is the security-critical fix that makes destination-address verification by ear possible.
  • 1 new English-only ARB key: qr_code_receive_address.

Notes for reviewers

  • Screen readers will read the address label as a continuous string; a character-by-character "spell out" affordance is a possible future UX improvement, but a correct continuous read is a strict improvement over chunk pseudo-words.
  • FloatingIconButton's 30×30 hit target remains below the 44/48pt platform minimums — deliberately not changed here to keep this PR visual-change-free; flagged for a follow-up.

Verification

  • flutter analyze (Flutter 3.41.9, same as CI) — zero new issues vs dev baseline. flutter_svg 2.2.x API names verified against the resolved package.
  • No visual or pointer-behavior changes; all ValueKeys preserved.
  • Widget tests land in the dedicated test PR at the end of this series; on-device VoiceOver/TalkBack verification pending.

Pull Request - Checklist

  • Initial Manual Tests Passed
  • Double check modified code and verify it with the feature/task requirements
  • Format code
  • Look for code duplication
  • Clear naming for variables and methods
  • Manual tests in accessibility mode (TalkBack on Android) passed

CakeImageWidget gains an optional semanticsLabel that is honoured in all four
render branches (precompiled asset SVG, asset raster, network SVG, network
raster). It defaults to null, which now means "decorative": the image is fully
excluded from the semantics tree instead of producing an unnamed node. The
built-in loading spinner and the error placeholder are likewise excluded so they
cannot become unnamed focus stops, while a caller-supplied errorWidget keeps its
own semantics (several call sites render the asset's initials there).

TokenImageWidget passes the same label through, decorative by default.

FloatingIconButton now requires a localized semanticLabel and exposes exactly
one button node; all six call sites are migrated to the existing paste /
scan_qr_code / address_book keys.

RoundedCheckbox exposes its checked/unchecked state (previously the unchecked
case rendered nothing at all, making selection invisible). It merges into the
enclosing row rather than adding a focus stop, and takes an excludeFromSemantics
escape hatch for rows that already announce their own selection.

The receive QR block becomes a single labelled button carrying the existing
qr_fullscreen hint; QrImage's matrix, embedded logo and badge are excluded, and
the payjoin badge reads as one node and drops out of the tree while hidden.

AddressFormatter announces the full untruncated address instead of the
four-character chunks ("pseudo-words") and the literal "...", so an address can
be verified by ear.
@sethforprivacy

Copy link
Copy Markdown
Contributor Author

Heads-up from the test PR (#3469): the RoundedCheckbox fix here is only half-effective as-is. Semantics(checked: false, child: Offstage()) produces a zero-rect node, and Flutter drops zero-rect nodes from the semantics tree — so unselected options still announce no state. The clean fix (give the unchecked branch the checked indicator's 20×20 footprint, which also removes the selection layout-jump) changes visible layout in two pickers, so it was deliberately left out pending a design call. #3469 carries a skipped test asserting the desired behavior. In the receive address-type list this is masked by the row-level selected semantics from #3467; the legacy present_receive_option_picker call site is where it shows.

// The QR matrix, the embedded logo and its badge are all purely visual: the
// data they encode is announced by the labelled control hosting this widget,
// so nothing in here should become a focus stop of its own.
return ExcludeSemantics(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't exclude semantics here, we should still say that there is a qr here (which is already handled in the QR package, and also because tapping it opens the full screen, so the user should know that there is a QR on this screen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants