From 5038b6bf4a0790343afa6f07f42f2cb0ba24d679 Mon Sep 17 00:00:00 2001 From: Jencir CJ <38936017+JencirJamal@users.noreply.github.com> Date: Tue, 4 May 2021 20:33:35 +0300 Subject: [PATCH] Feature - Add accessibility support for android date picker (#314) * Implemented accessibility support for android date picker. * Created new function to return accessible text. Changed access modifier of few functions to public. * removed duplicate contentDescription for accessibility to work correct. * Added finnish and swedish localizations for accessible items in datepicker. * Added localisation english texts. * Implemented localisations for accessible selections made inside android datepicker. * Added getLocaleStringResource function for getting localised string resource. * Added required imports for getLocaleStringResource function. * Content description set based on localised strings from resources initially. * Implemented meaningful content description for dateTime picker. Added new keys for time prefix. Content description set when screen reader is focused on picker wheels. * Updated correct finnish and swedish translations. * refactor: move accessibility to its own class Co-authored-by: Jencir CJ Co-authored-by: Henning Hall --- .../henninghall/date_picker/LocaleUtils.java | 30 +++++++ .../henninghall/date_picker/PickerView.java | 4 + .../com/henninghall/date_picker/Utils.java | 6 ++ .../date_picker/ui/Accessibility.java | 80 +++++++++++++++++++ .../henninghall/date_picker/ui/UIManager.java | 11 ++- .../ui/WheelChangeListenerImpl.java | 2 + .../henninghall/date_picker/ui/Wheels.java | 17 +++- android/src/main/res/layout/ios_clone.xml | 24 +++++- android/src/main/res/layout/native_picker.xml | 35 ++++++-- android/src/main/res/values-fi/strings.xml | 22 +++++ android/src/main/res/values-sv/strings.xml | 22 +++++ android/src/main/res/values/strings.xml | 20 ++++- 12 files changed, 260 insertions(+), 13 deletions(-) create mode 100644 android/src/main/java/com/henninghall/date_picker/ui/Accessibility.java create mode 100644 android/src/main/res/values-fi/strings.xml create mode 100644 android/src/main/res/values-sv/strings.xml diff --git a/android/src/main/java/com/henninghall/date_picker/LocaleUtils.java b/android/src/main/java/com/henninghall/date_picker/LocaleUtils.java index b71b99a..66b3437 100644 --- a/android/src/main/java/com/henninghall/date_picker/LocaleUtils.java +++ b/android/src/main/java/com/henninghall/date_picker/LocaleUtils.java @@ -1,5 +1,10 @@ package com.henninghall.date_picker; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; + import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Locale; @@ -61,4 +66,29 @@ public class LocaleUtils { return df instanceof SimpleDateFormat && ((SimpleDateFormat) df).toPattern().contains("a"); } + public static String getLocaleStringResource(Locale requestedLocale, int resourceId, Context context) { + String result; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { // use latest api + Configuration config = new Configuration(context.getResources().getConfiguration()); + config.setLocale(requestedLocale); + result = context.createConfigurationContext(config).getText(resourceId).toString(); + } + else { // support older android versions + Resources resources = context.getResources(); + Configuration conf = resources.getConfiguration(); + Locale savedLocale = conf.locale; + conf.locale = requestedLocale; + resources.updateConfiguration(conf, null); + + // retrieve resources from desired locale + result = resources.getString(resourceId); + + // restore original locale + conf.locale = savedLocale; + resources.updateConfiguration(conf, null); + } + + return result; + } + } diff --git a/android/src/main/java/com/henninghall/date_picker/PickerView.java b/android/src/main/java/com/henninghall/date_picker/PickerView.java index 53ee357..f71038a 100644 --- a/android/src/main/java/com/henninghall/date_picker/PickerView.java +++ b/android/src/main/java/com/henninghall/date_picker/PickerView.java @@ -72,6 +72,10 @@ public class PickerView extends RelativeLayout { uiManager.updateDisplayValues(); } + if (didUpdate(ModeProp.name, LocaleProp.name)) { + uiManager.updateAccessibilityValues(); + } + uiManager.setWheelsToDate(); updatedProps = new ArrayList<>(); diff --git a/android/src/main/java/com/henninghall/date_picker/Utils.java b/android/src/main/java/com/henninghall/date_picker/Utils.java index d6d8242..d39a9c9 100644 --- a/android/src/main/java/com/henninghall/date_picker/Utils.java +++ b/android/src/main/java/com/henninghall/date_picker/Utils.java @@ -98,4 +98,10 @@ public class Utils { if (from + option1 < 0) return option2; return option1; } + + public static String getLocalisedStringFromResources(Locale locale, String tagName) { + int selectedKey = DatePickerManager.context.getResources().getIdentifier(tagName,"string",DatePickerManager.context.getPackageName()); + String localisedText = LocaleUtils.getLocaleStringResource(locale, selectedKey, DatePickerManager.context); + return localisedText; + } } diff --git a/android/src/main/java/com/henninghall/date_picker/ui/Accessibility.java b/android/src/main/java/com/henninghall/date_picker/ui/Accessibility.java new file mode 100644 index 0000000..489b09c --- /dev/null +++ b/android/src/main/java/com/henninghall/date_picker/ui/Accessibility.java @@ -0,0 +1,80 @@ +package com.henninghall.date_picker.ui; + +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import com.henninghall.date_picker.State; +import com.henninghall.date_picker.Utils; +import com.henninghall.date_picker.wheelFunctions.WheelFunction; +import com.henninghall.date_picker.wheels.Wheel; + +import java.util.Locale; + +public class Accessibility { + + public static class SetAccessibilityDelegate implements WheelFunction { + + private final Locale locale; + + public SetAccessibilityDelegate(Locale locale) { + this.locale = locale; + } + + @Override + public void apply(Wheel wheel) { + final View view = wheel.picker.getView(); + view.setAccessibilityDelegate( + new View.AccessibilityDelegate(){ + @Override + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + super.onPopulateAccessibilityEvent(host, event); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { + String resourceKey = view.getTag().toString()+"_description"; + String localeTag = Utils.getLocalisedStringFromResources(locale, resourceKey); + // Screen reader reads the content description when focused on each picker wheel + view.setContentDescription(localeTag); + } + } + } + ); + } + } + + private final State state; + private final Wheels wheels; + + public Accessibility(State state, Wheels wheels){ + this.state = state; + this.wheels = wheels; + } + + public void update(Wheel picker){ + String tagName = picker.picker.getView().getTag().toString(); + String selectedDateString = getAccessibleTextForSelectedDate(); + String descriptionPrefix = Utils.getLocalisedStringFromResources(state.getLocale(), "selected_"+tagName+"_description"); + String descriptionPostFix = Utils.getLocalisedStringFromResources(state.getLocale(), "selected_value_description"); + + picker.picker.getView().setContentDescription(descriptionPrefix + ", "+ descriptionPostFix + " "+ selectedDateString); + } + + private String getAccessibleTextForSelectedDate() { + String accessibleText; + switch(state.getMode()) { + case date: + accessibleText = wheels.getDateString(); + break; + case time: + accessibleText = wheels.getTimeString(); + break; + default: + // default is dateTime + String timePrefix = Utils.getLocalisedStringFromResources(state.getLocale(), "time_tag"); + String hourPrefix = Utils.getLocalisedStringFromResources(state.getLocale(), "hour_tag"); + String minutesPrefix = Utils.getLocalisedStringFromResources(state.getLocale(), "minutes_tag"); + accessibleText = wheels.getAccessibleDateTimeString(timePrefix, hourPrefix, minutesPrefix); + break; + } + return accessibleText; + } + +} diff --git a/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java b/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java index 49f943e..0ab1c11 100644 --- a/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java +++ b/android/src/main/java/com/henninghall/date_picker/ui/UIManager.java @@ -7,7 +7,6 @@ import com.henninghall.date_picker.wheelFunctions.AddOnChangeListener; import com.henninghall.date_picker.wheelFunctions.AnimateToDate; import com.henninghall.date_picker.wheelFunctions.Refresh; import com.henninghall.date_picker.wheelFunctions.SetDate; -import com.henninghall.date_picker.wheelFunctions.SetDividerHeight; import com.henninghall.date_picker.wheelFunctions.TextColor; import com.henninghall.date_picker.wheelFunctions.UpdateVisibility; import com.henninghall.date_picker.wheelFunctions.HorizontalPadding; @@ -22,11 +21,13 @@ public class UIManager { private Wheels wheels; private FadingOverlay fadingOverlay; private WheelScroller wheelScroller = new WheelScroller(); + private Accessibility accessibility; public UIManager(State state, View rootView){ this.state = state; this.rootView = rootView; wheels = new Wheels(state, rootView); + accessibility = new Accessibility(state, wheels); addOnChangeListener(); } @@ -90,4 +91,12 @@ public class UIManager { public void updateWheelPadding() { wheels.applyOnVisible(new HorizontalPadding()); } + + public void updateContentDescription(Wheel picker) { + accessibility.update(picker); + } + + public void updateAccessibilityValues() { + wheels.applyOnAll(new Accessibility.SetAccessibilityDelegate(state.getLocale())); + } } diff --git a/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java b/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java index acc0a89..8e69e6e 100644 --- a/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java +++ b/android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java @@ -63,6 +63,8 @@ public class WheelChangeListenerImpl implements WheelChangeListener { return; } + uiManager.updateContentDescription(picker); + emitDateChangeEvent(selectedDate); } diff --git a/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java b/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java index db1c832..62b3247 100644 --- a/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java +++ b/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java @@ -62,7 +62,7 @@ public class Wheels { changeAmPmWhenPassingMidnightOrNoon(); } - private Picker getPickerWithId(int id){ + private Picker getPickerWithId(final int id){ return (Picker) rootView.findViewById(id); } @@ -136,7 +136,7 @@ public class Wheels { return dayWheel.getValue(); } - private String getTimeString(){ + String getTimeString(){ return hourWheel.getValue() + " " + minutesWheel.getValue() + ampmWheel.getValue(); @@ -146,6 +146,19 @@ public class Wheels { return getDateTimeString(0); } + String getDateString() { + return getDateString(0); + } + + String getAccessibleDateTimeString(String timePrefix, String hourTag, String minutesTag) { + String date = getDateString(); + String hour = hourWheel.getValue(); + String minutes = minutesWheel.getValue(); + String ampm = ampmWheel.getValue(); + String time = timePrefix+ " "+ hour + hourTag + minutes + minutesTag + ampm; + return date+", "+ time; + } + String getDisplayValue() { StringBuilder sb = new StringBuilder(); for (Wheel wheel: getOrderedVisibleWheels()) { diff --git a/android/src/main/res/layout/ios_clone.xml b/android/src/main/res/layout/ios_clone.xml index feb88e9..396888d 100644 --- a/android/src/main/res/layout/ios_clone.xml +++ b/android/src/main/res/layout/ios_clone.xml @@ -34,7 +34,10 @@ custom:npv_TextSizeSelected="21dp" custom:npv_TextColorSelected="#000000" custom:npv_TextColorNormal="#aaaaaa" - custom:npv_DividerColor="#cccccc" /> + custom:npv_DividerColor="#cccccc" + android:contentDescription="@string/year_description" + android:focusable="true" + android:focusableInTouchMode="true" /> diff --git a/android/src/main/res/layout/native_picker.xml b/android/src/main/res/layout/native_picker.xml index 76a2218..4a212b6 100644 --- a/android/src/main/res/layout/native_picker.xml +++ b/android/src/main/res/layout/native_picker.xml @@ -17,43 +17,64 @@ android:id="@+id/year" android:theme="@style/android_native_theme" style="@style/android_native" - android:tag="year" /> + android:tag="year" + android:contentDescription="@string/year_description" + android:focusable="true" + android:focusableInTouchMode="true" /> + android:tag="month" + android:contentDescription="@string/month_description" + android:focusable="true" + android:focusableInTouchMode="true" /> + android:tag="date" + android:contentDescription="@string/date_description" + android:focusable="true" + android:focusableInTouchMode="true" /> + android:tag="day" + android:contentDescription="@string/day_description" + android:focusable="true" + android:focusableInTouchMode="true" /> + android:tag="hour" + android:contentDescription="@string/hour_description" + android:focusable="true" + android:focusableInTouchMode="true" /> + android:tag="minutes" + android:contentDescription="@string/minutes_description" + android:focusable="true" + android:focusableInTouchMode="true" /> + android:tag="ampm" + android:contentDescription="@string/ampm_description" + android:focusable="true" + android:focusableInTouchMode="true" /> diff --git a/android/src/main/res/values-fi/strings.xml b/android/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000..0c3445f --- /dev/null +++ b/android/src/main/res/values-fi/strings.xml @@ -0,0 +1,22 @@ + + + Päivämääränvalitsimen päällekkäin + Valitse Vuosi + Valitse Kuukausi + Valitse Päivämäärä + Valitse Päivä + Valitse Tunti + Valitse Minuutit + Valitse am/pm + Valittu Vuosi + Valittu Kuukausi + Valittu Päivämäärä + Valittu Päivä + Valittu Tunti + Valitut Minuutit + Valittu am/pm + Arvo on + Kello on + Tunti: + Minuutit + diff --git a/android/src/main/res/values-sv/strings.xml b/android/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000..421755e --- /dev/null +++ b/android/src/main/res/values-sv/strings.xml @@ -0,0 +1,22 @@ + + + Datumväljare på + Välj ett år + Välj en månad + Välj ett datum + Välj en dag + Välj en timme + Välj minuter + Välj am/pm + Valt år + Valt månad + Valt datum + Vald dag + Vald timme + Valda minuter + Vald am/pm + Värdet är + Tiden är + Timme: + Minuter + diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index cbff19c..dbadfa8 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -1,4 +1,22 @@ react-native-date-picker - overlay + Date Picker Overlay + Select Year + Select Month + Select Date + Select Day + Select Hour + Select Minutes + Select AM/PM + Selected Year + Selected Month + Selected Date + Selected Day + Selected Hour + Selected Minutes + Selected AM/PM + Value is + Time is + Hour : + Minutes