Skip to content

ADR-0082: Explicit Activity state and one named settings switch row

Context

Three accessibility defects shipped together in the Warm ledger review (#373, #377, #386):

  • A hidden Activity row applied a 0.45 alpha to the whole row. At normal text size the composited identity, metadata and money fell to roughly 1.8–3.6:1 against the row surface, below the 4.5:1 normal-text floor, and opacity was the only hidden-state signal.
  • Notify on capture, Record transaction locations, Dynamic color, Biometric unlock and Device credential fallback put an interactive Switch inside a settings row that was not itself toggleable. TalkBack therefore met a second, separately focusable switch with no row name, while the row title did nothing.
  • A collapsed Activity search kept its query active but invisible; the Uncategorised filter existed only as a route sentinel; the scope chip drew a dropdown chevron although it toggles; Activity day headers and the unmatched timestamp hardcoded field order and HH:mm; the row's custom actions lived on an unfocusable wrapper; and the editor's read-only date field repeated the same date in its tap-target description.

Decision

  • Hidden rows stay readable and labeled. A hidden row keeps surfaceContainerHighest as its background but renders identity, metadata and money with their normal onSurface / onSurfaceVariant / primary roles, with no whole-row alpha. A localized Hidden pill (the shared status-pill shape and status type role) sits in the supporting line, so state is visible and announced independently of colour.
  • One named switch row. SettingsSwitchRow owns the single toggleable(value, role = Role.Switch) node; its child Switch is decorative (onCheckedChange = null) and mirrors checked/enabled. All immediate settings use it, and the row is bound to confirmed persisted state, so a cancelled permission or security flow cannot flip it.
  • Disclose Activity state. A collapsed active query renders as a removable chip beside the other active filters. The filter sheet offers Uncategorised through the existing UNCATEGORISED_CATEGORY_ID sentinel. The scope control is a constant All history filter chip (unselected = the selected month, selected = the whole ledger) with a swap icon, so its selected state never contradicts a relabeled "Month" chip and it never looks like a dropdown. The row's custom Hide/Delete actions move onto the named clickable row node.
  • Announce editor structure. CollapsibleSectionCard exposes a localized Expanded/Collapsed stateDescription and an Expand/Collapse click label. The editor's read-only date field keeps the date announcement once; its overlay names the Change date and time action instead of repeating the value.
  • Locale and device time. Day headers and timestamps resolve their pattern through android.text.format.DateFormat.getBestDateTimePattern with locale-neutral skeletons, so field order, month/weekday names and the device 12/24-hour preference are respected. Formatting stays pure java.time and is JVM-tested; the Activity month scope still omits the year and all-history still adds it.

Consequences

  • Hidden rows meet the 4.5:1 normal-text floor in the static Warm palettes and the device dynamic scheme in both light and dark (instrumented); the former 0.45 composite is asserted to fail so the fade cannot return.
  • Every immediate setting has exactly one labeled switch node and a ≥48 dp target; disabled biometrics stay announced but cannot be toggled.
  • Active queries, the uncategorised filter and row actions are reachable from the same visible/focusable surface as the list.
  • No schema, dependency, money, draft, lock or route-contract change. The debug gallery gains a Hidden transaction fixture and the SettingsSwitchRow states, and the new behaviour is covered by JVM contrast/formatting tests and instrumented semantics/layout tests.

Verification

HiddenRowContrastTest, DateTimeFormattingTest, SettingsSwitchRowTest, CaptureSettingsTest, AppearanceSectionTest, SecuritySectionTest, CollapsibleSectionCardTest, HiddenTransactionRowTest, ActivityQueryAndFilterTest, ActivityDateFormatTest, TransactionsContentTest and TransactionDetailScreenTest cover the contracts above at the exact head.