Skip to content

fix(a11y): restore TalkBack tab states, native toggle/checkbox/radio roles, in-locale Connect strings#45

Merged
iampedii merged 9 commits into
WhiteDNS:mainfrom
Danialsamadi:accessibility-fixes
May 20, 2026
Merged

fix(a11y): restore TalkBack tab states, native toggle/checkbox/radio roles, in-locale Connect strings#45
iampedii merged 9 commits into
WhiteDNS:mainfrom
Danialsamadi:accessibility-fixes

Conversation

@Danialsamadi

@Danialsamadi Danialsamadi commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Restores the screen-reader behaviors that regressed after the recent bottom-nav and Parallel Test changes, and removes the custom contentDescription strings in favor of native Compose roles so TalkBack announces state ("selected", "checked", "on/off") automatically — and in the user's chosen in-app language.

Addresses user-reported issues:

  • Bottom tabs no longer announced "selected"; instead read the navigation hint three times.
  • "Connect" button and "Parallel Test / تست موازی" were read in English regardless of the in-app locale.
  • Toggles/checkboxes spoke custom "tap to enable/disable" strings instead of native state.

What changed

  • Bottom nav tabs and profile tab switch now use Modifier.selectable(role = Role.Tab) with mergeDescendants, so TalkBack announces "<Tab name>, tab, selected" once.
  • ToggleRow uses Modifier.toggleable(role = Role.Switch); inner Switch has onCheckedChange = null + clearAndSetSemantics {} so the row owns the gesture and TalkBack reads "<label>, switch, on/off".
  • ParallelTestConfigRow uses Modifier.toggleable(role = Role.Checkbox); inner indicator is non-semantic.
  • Language / Theme / Connection-mode segmented controls use Modifier.selectable(role = Role.RadioButton) with mergeDescendants so each option announces its own selected/unselected state.
  • Connect button screen-reader strings moved into the in-app WhiteDnsStrings CompositionLocal (English + Persian) so they follow the in-app language picker, not the system locale.
  • Custom cd_* content-description resources superseded by native semantics are removed.

Test plan

  • ./gradlew :app:compileDebugKotlin clean
  • TalkBack on a physical/emulated device: tab between bottom nav items → hear "<name>, tab, selected" exactly once per stop
  • TalkBack on ToggleRow / ParallelTest / segmented controls → hear native state (on/off, checked/unchecked, selected/not selected)
  • Switch in-app language to Persian → focus Connect button → hear Persian string (e.g. "دکمه اتصال - برای شروع VPN لمس کنید")

Danialsamadi and others added 9 commits May 19, 2026 22:37
Captures the four TalkBack regressions reported by blind users and
the plan to fix them using native Role.Tab/Role.Switch/Role.Checkbox
semantics plus in-app-locale Connect-button strings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restores 'selected' state announcement and eliminates the triple
read of each tab label by merging descendants and dropping the
Icon's redundant contentDescription.
Drops custom cd_profile_tab_selected/unselected strings in favor of
the platform's native Tab role announcement.
Drops custom cd_toggle_row_on/off strings. The native Switch role
makes TalkBack announce on/off naturally, fulfilling the user's
'use native controls' request.
Single focus stop per config, native checked/not-checked announcement,
honors the row's enabled parameter for the screen reader.
Migrates cd_connect_button_* into WhiteDnsStrings / WhiteDnsL10n so the
button's accessibility description follows the in-app language picker,
not the device system locale. Adds merged semantics so the button is a
single focus stop.
These keys are no longer referenced from Kotlin code now that bottom
nav, profile tabs, ToggleRow, ParallelTestConfigRow, and the Connect
button use native Role-based semantics or the in-app WhiteDnsL10n
pipeline.
Language, Theme, and Connection mode segmented controls each had
.clickable(enabled = !selected) on the option boxes, which:
- gave the selected option no Role, so TalkBack never announced
  "selected" when the language (or theme/mode) changed
- made the selected option non-focusable for the screen reader

Switching to .selectable(selected, role = Role.RadioButton, onClick)
+ .semantics(mergeDescendants = true) {} fixes both: TalkBack now
announces "<label>, radio button, selected/not selected" with a
single focus stop per option. The ConnectionMode control keeps its
outer enabled flag wired through to selectable.enabled.
@iampedii iampedii merged commit ea3537b into WhiteDNS:main May 20, 2026
1 check passed
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