Skip to content

fix(android): announce real minutes for spinner with minuteInterval - #1071

Open
pebblechris wants to merge 1 commit into
react-native-datetimepicker:masterfrom
pebblechris:fix/android-spinner-minute-interval-a11y
Open

pebblechris wants to merge 1 commit into
react-native-datetimepicker:masterfrom
pebblechris:fix/android-spinner-minute-interval-a11y

Conversation

@pebblechris

Copy link
Copy Markdown

Summary

On Android, when display="spinner" is used with a custom minuteInterval, TalkBack announces the wrong time. For example, with minuteInterval={15} and 12:15 selected, TalkBack announces "12:01".

Cause

To show only interval-aligned minutes, MinuteIntervalSnappableTimePickerDialog turns the minute NumberPicker into an index picker and uses setDisplayedValues() to show the labels (00, 15, 30, 45). The labels are correct, but the TimePicker's minute value is now the index.

When the time changes, the platform's TimePickerSpinnerDelegate sends TYPE_VIEW_SELECTED and builds the announcement in onPopulateAccessibilityEvent() from getHour() / getMinute(). Index 1 is therefore announced as minute 1.

Fix

In the existing spinner branch of setupPickerDialog(), set a View.AccessibilityDelegate on the TimePicker. It overrides dispatchPopulateAccessibilityEvent() and builds the announcement from getRealMinutes() (index × interval). The time is formatted the same way as the platform: DateUtils.formatDateTime() with FORMAT_SHOW_TIME and the 12/24-hour flag from is24HourView().

dispatchPopulateAccessibilityEvent() is overridden rather than onPopulateAccessibilityEvent() because TimePicker.dispatchPopulateAccessibilityEventInternal() calls its internal delegate directly, which adds the platform text without going through View.onPopulateAccessibilityEvent(). Overriding only onPopulate… still adds the wrong text. The platform's dispatch adds nothing besides that announcement, so nothing else is lost.

Impact

  • Only affects display="spinner" with a non-default minuteInterval on Android.
  • The value reported to JS is unchanged.

Test Plan

What's required for testing (prerequisites)?

  • Android device or emulator with TalkBack enabled
  • Example app: yarn start, then yarn start:android

What are the steps to reproduce (after prerequisites)?

  1. In the example app, set display prop to spinner and minute interval prop to 15.
  2. Open the time picker and change the minutes to 15.
  3. Before: TalkBack announces "12:01". After: TalkBack announces "12:15".

Verified:

  • Intervals 1, 2, 3, 4, 5, 6, 10, 12, 15, 20 and 30
  • 12-hour and 24-hour, including changing AM/PM
  • Changing the hour while minutes are non-zero
  • Interval 1 and clock display behave as before
  • Selected value returned to JS is unchanged
  • Tested on: S26 Ultra (android17) / Pixel 9 Emulator (android16) / Pixel Tablet Emulator (android15) / Pixel 4 Emulator (android12)

Compatibility

OS Implemented
iOS ❌ (not affected)
Android ✅

Checklist

  • [✅] I have tested this on a device and a simulator
  • I added the documentation in README.md (N/A)
  • I updated the typed files (TS and Flow) (N/A)
  • I added a sample use of the API in the example project (example/App.js) (N/A — existing example covers it)
  • I have added automated tests, either in JS or e2e tests, as applicable

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.

1 participant