Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1/n Adding Android support for Accessibility TtsSpan API - Support for accessibilitySpan and accessibilityLabel props in nested Text #35130

Closed
wants to merge 91 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
216a30a
draft solution partially spelling correctly
fabOnReact Oct 29, 2022
48a01d0
correctly spelling a span text
fabOnReact Oct 29, 2022
08d6f43
add span description
fabOnReact Oct 31, 2022
145303e
remove code duplication
fabOnReact Oct 31, 2022
f66e914
remove code duplication
fabOnReact Oct 31, 2022
3d4e662
draft java configs to trigger setAccessibilityUnit
fabOnReact Nov 1, 2022
1653d6b
Merge branch 'main' into tts-span
fabOnReact Nov 1, 2022
6def7a9
draft cpp configs - they trigger runtime
fabOnReact Nov 1, 2022
7fa58f5
Fabric cpp settings trigger runtime error
fabOnReact Nov 1, 2022
43e7c29
cpp mapbuffer configs before rebase
fabOnReact Nov 1, 2022
0afdc69
Merge branch 'main' into tts-span
fabOnReact Nov 1, 2022
0cd0ac4
avoid using key from other props
fabOnReact Nov 1, 2022
f5a3b49
avoid using key 24 and use key 99
fabOnReact Nov 1, 2022
08fc9ac
remove log messages
fabOnReact Nov 2, 2022
c2adbb8
debug - change key number
fabOnReact Nov 2, 2022
46c4fb8
Merge branch 'main' into tts-span
fabOnReact Nov 2, 2022
45ec47f
custom conversion logic for accUnit as done with accRole
fabOnReact Nov 2, 2022
0a00193
Merge branch 'main' into tts-span
fabOnReact Nov 3, 2022
72ee5a1
remove accessibilityUnit prop from cpp
fabOnReact Nov 3, 2022
81fe302
accessibilityRole verbatim correctly announces span text, but does no…
fabOnReact Nov 3, 2022
7af9bd3
Remove logic for announcing nested text
fabOnReact Nov 3, 2022
16f1f39
clean up not needed logic
fabOnReact Nov 3, 2022
d845073
removing not relevant logic
fabOnReact Nov 3, 2022
e215baa
adding TtsSpan with type date
fabOnReact Nov 3, 2022
03a125c
refactoring logic parsing accRole to TtsSpan TYPES
fabOnReact Nov 4, 2022
fe5c480
TtsSpan works with parent Text component
fabOnReact Nov 4, 2022
7e264da
adding TtsSpan to TextLayoutManager
fabOnReact Nov 4, 2022
d8823e5
removing not required logic
fabOnReact Nov 4, 2022
9d4b8a5
update description
fabOnReact Nov 4, 2022
7b85e25
adding all TtsSpan, no comp errors
fabOnReact Nov 4, 2022
aefa45f
accessibility TtsSpan examples
fabOnReact Nov 4, 2022
95d6d3b
adding accessibility ttspan examples
fabOnReact Nov 4, 2022
24c182a
manually patching the prefab-headers avoid the app from triggering a …
fabOnReact Nov 9, 2022
7b4231f
Merge branch 'main' into tts-span
fabOnReact Nov 9, 2022
4fbc54b
accessibilityUnit hours param Java test implementation
fabOnReact Nov 10, 2022
29a8104
Merge branch 'main' into tts-span
fabOnReact Nov 10, 2022
e3fd7ff
clean up diff
fabOnReact Nov 11, 2022
3e1d292
clean up diff
fabOnReact Nov 11, 2022
8b23efd
Adding prop accessibilityUnit to TextAttributesProps (fabric) (#3)
fabOnReact Nov 15, 2022
bea5af9
code-review
fabOnReact Nov 15, 2022
6fc18d4
fix circleci errors
fabOnReact Nov 15, 2022
8b592f7
The ReactTtsSpan.Builder constructor expects a string and not an Acce…
fabOnReact Nov 16, 2022
f2642f6
adding type money support
fabOnReact Nov 16, 2022
c4cd23b
adding check on accessibilityUnit
fabOnReact Nov 16, 2022
576bad6
improve logging
fabOnReact Nov 17, 2022
958ae95
Test Buck fix circular dependency
fabOnReact Nov 17, 2022
8a59592
adding final to Set
fabOnReact Nov 17, 2022
eb95e08
static interface method invocations are not supported in -source 7
fabOnReact Nov 17, 2022
5aa9f74
error: static interface method invocations are not supported in -sour…
fabOnReact Nov 17, 2022
cb52ad9
refactor example
fabOnReact Nov 17, 2022
60ef8f0
adding support for telephonegst
fabOnReact Nov 17, 2022
06096d5
adding support for measure
fabOnReact Nov 17, 2022
cc45e04
simplify ReactTtsSpan API
fabOnReact Nov 17, 2022
096019c
remove time warning
fabOnReact Nov 17, 2022
7f4a0cd
adding back check on currency code
fabOnReact Nov 17, 2022
0a42fd0
adding comments to prop
fabOnReact Nov 17, 2022
79c4aed
remove comment
fabOnReact Nov 17, 2022
dfc4063
update TA_KEY_ACCESSIBILITY_UNIT value (code review)
fabOnReact Nov 18, 2022
3b30659
adding static set SUPPORTED_TYPES_SET
fabOnReact Nov 18, 2022
fe4e1e0
introducing Set in ReactTtsSpan
fabOnReact Nov 18, 2022
ef9c59b
Merge branch 'main' into tts-span
fabOnReact Dec 13, 2022
ac79bae
update types cpp, objc typescript and js
fabOnReact Dec 13, 2022
145db13
minor changes
fabOnReact Dec 13, 2022
a892b68
fix xcode warning
fabOnReact Dec 14, 2022
61e5920
Merge branch 'main' into tts-span
fabOnReact Jan 12, 2023
f495025
adding role grid to ViewAccessibility.d.ts
fabOnReact Jan 12, 2023
753858e
adding roles to Role typegst
fabOnReact Jan 12, 2023
821a4c8
Merge branch 'main' into tts-span
fabOnReact Jan 12, 2023
4988339
Merge branch 'main' into tts-span
fabOnReact Jan 12, 2023
02af5a4
Merge branch 'main' into tts-span
fabOnReact Feb 2, 2023
dcb9f61
remove view prop (take 3)
fabOnReact Feb 3, 2023
e7cbd28
remove change from ViewAcc.d.ts
fabOnReact Feb 3, 2023
565d7ed
adding accessibilitySpan prop
fabOnReact Feb 3, 2023
a1c7e0f
remove changes to accessibilityRole
fabOnReact Feb 3, 2023
f797c20
working solution migration to accessibilitySpan with AccSpan type
fabOnReact Feb 3, 2023
8b90011
accSpan enum type
fabOnReact Feb 3, 2023
e830578
fix verbatim not working
fabOnReact Feb 3, 2023
25b212e
adding cpp AccSpan type
fabOnReact Feb 3, 2023
c058657
adding more missing cpp configs
fabOnReact Feb 3, 2023
151b37c
Verbatim and other strings are not announced.
fabOnReact Feb 6, 2023
6f68513
adding type none to AccSpan
fabOnReact Feb 6, 2023
e1c56ac
android studio and circleci fixes
fabOnReact Feb 6, 2023
ecc815b
Use accessibilityLabel instead of accessibilityUnit.
fabOnReact Feb 7, 2023
7c80bdb
removing changes to ReactAccDelegate
fabOnReact Feb 7, 2023
34ed14e
Remove not supported TYPES from PR and move them to a separate branch
fabOnReact Feb 7, 2023
23241dd
rename UNIT to LABEL
fabOnReact Feb 7, 2023
112ed6f
Merge branch 'main' into tts-span
fabOnReact Feb 8, 2023
235eda8
Review Android Studio and XCODE errors
fabOnReact Feb 8, 2023
d536cf7
update comments
fabOnReact Feb 8, 2023
c154196
update comments
fabOnReact Feb 9, 2023
76ce49a
update check on accSpan and accLabel
fabOnReact Feb 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Libraries/Components/View/ReactNativeViewAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const UIView = {
accessibilityLiveRegion: true,
accessibilityRole: true,
accessibilityState: true,
accessibilitySpan: true,
accessibilityValue: true,
accessibilityHint: true,
accessibilityLanguage: true,
Expand Down
1 change: 1 addition & 0 deletions Libraries/NativeComponent/BaseViewConfig.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ const validAttributesForNonEventProps = {
accessibilityCollection: true,
accessibilityCollectionItem: true,
accessibilityState: true,
accessibilitySpan: true,
accessibilityActions: true,
accessibilityValue: true,
importantForAccessibility: true,
Expand Down
45 changes: 45 additions & 0 deletions Libraries/Text/Text.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,51 @@ export interface TextPropsIOS {
}

export interface TextPropsAndroid {
/**
* Used for nested Text accessibility announcements.
* The nested text accessibilityLabel should set to the values of:
*
* None https://developer.android.com/reference/android/text/style/TtsSpan#TYPE_TEXT
* The default type used when accessibilitySpan prop is not set (AccessibilitySpan.NONE)
* Adds the accessibilityLabel announcement on a Nested Text.
* This span type can be used to add morphosyntactic features to the text it spans over,
* or synthesize a something else than the spanned text.
* Use the argument ARG_TEXT to set a different text.
* https://developer.android.com/reference/android/text/style/TtsSpan#ARG_TEXT
* String supplying the text to be synthesized.
* The synthesizer is free to decide how to interpret the text. Can be used with TYPE_TEXT.
*
* Ordinal and Cardinal https://developer.android.com/reference/android/text/style/TtsSpan#ARG_NUMBER
* Argument used to specify a whole number.
* The value can be a string of digits of any size optionally prefixed with a - or +.
* Can be used with TYPE_CARDINAL and TYPE_ORDINAL.
*
* Measure refer to https://developer.android.com/reference/android/text/style/TtsSpan#ARG_UNIT
* Argument used to specify the unit of a measure.
* The unit should always be specified in English singular form.
* Prefixes may be used. Engines will do their best to pronounce them correctly in the language used.
* Engines are expected to at least support the most common ones like "meter",
* "second", "degree celsius" and "degree fahrenheit" with some common prefixes like "milli" and "kilo".
* Can be used with TYPE_MEASURE.
*
* Telephone refer to https://developer.android.com/reference/android/text/style/TtsSpan#ARG_NUMBER_PARTS
* Argument used to specify the main number part of a telephone number.
* Can be a string of digits where the different parts of the telephone
* number can be separated with a space, '-', '/' or '.'.
* Can be used with TYPE_TELEPHONE.
*
* Verbatim refer to https://developer.android.com/reference/android/text/style/TtsSpan#ARG_VERBATIM
* Argument used to specify a string where the characters are read verbatim, except whitespace.
* Can be used with TYPE_VERBATIM.
*/
fabOnReact marked this conversation as resolved.
Show resolved Hide resolved
accessibilitySpan?:
| 'none'
| 'cardinal'
| 'ordinal'
| 'measure'
| 'telephone'
| 'verbatim';

/**
* Specifies the disabled state of the text view for testing purposes.
*/
Expand Down
46 changes: 46 additions & 0 deletions Libraries/Text/TextProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,52 @@ export type TextProps = $ReadOnly<{|
* Android Only
*/

/**
* Used for nested Text accessibility announcements.
* The nested text accessibilityLabel should set to the values of:
*
* None https://developer.android.com/reference/android/text/style/TtsSpan#TYPE_TEXT
* The default type used when accessibilitySpan prop is not set (AccessibilitySpan.NONE)
* Adds the accessibilityLabel announcement on a Nested Text.
* This span type can be used to add morphosyntactic features to the text it spans over,
* or synthesize a something else than the spanned text.
* Use the argument ARG_TEXT to set a different text.
* https://developer.android.com/reference/android/text/style/TtsSpan#ARG_TEXT
* String supplying the text to be synthesized.
* The synthesizer is free to decide how to interpret the text. Can be used with TYPE_TEXT.
*
* Ordinal and Cardinal https://developer.android.com/reference/android/text/style/TtsSpan#ARG_NUMBER
* Argument used to specify a whole number.
* The value can be a string of digits of any size optionally prefixed with a - or +.
* Can be used with TYPE_CARDINAL and TYPE_ORDINAL.
*
* Measure refer to https://developer.android.com/reference/android/text/style/TtsSpan#ARG_UNIT
* Argument used to specify the unit of a measure.
* The unit should always be specified in English singular form.
* Prefixes may be used. Engines will do their best to pronounce them correctly in the language used.
* Engines are expected to at least support the most common ones like "meter",
* "second", "degree celsius" and "degree fahrenheit" with some common prefixes like "milli" and "kilo".
* Can be used with TYPE_MEASURE.
*
* Telephone refer to https://developer.android.com/reference/android/text/style/TtsSpan#ARG_NUMBER_PARTS
* Argument used to specify the main number part of a telephone number.
* Can be a string of digits where the different parts of the telephone
* number can be separated with a space, '-', '/' or '.'.
* Can be used with TYPE_TELEPHONE.
*
* Verbatim refer to https://developer.android.com/reference/android/text/style/TtsSpan#ARG_VERBATIM
* Argument used to specify a string where the characters are read verbatim, except whitespace.
* Can be used with TYPE_VERBATIM.
*/
accessibilitySpan?: ?(
| 'none'
| 'cardinal'
| 'ordinal'
| 'measure'
| 'telephone'
| 'verbatim'
),

/**
* Specifies the disabled state of the text view for testing purposes.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.text;

import android.os.PersistableBundle;
import android.text.style.TtsSpan;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;

/*
* Used for nested Text accessibility announcements with
* props accessiblitySpan and accessibilityLabel.
*
* Wraps {@link TtsSpan} as a {@link ReactSpan}.
* A span that supplies additional meta-data for the associated text intended
* for text-to-speech engines. If the text is being processed by a
* text-to-speech engine, the engine may use the data in this span in addition
* to or instead of its associated text.
*
* Each instance of a TtsSpan has a type, for example {@link #TYPE_DATE}
* or {@link #TYPE_MEASURE}. And a list of arguments, provided as
* key-value pairs in a bundle.
*
* The inner classes are there for convenience and provide builders for each
* TtsSpan type.
*/
public class ReactTtsSpan extends TtsSpan implements ReactSpan {
private static final String TAG = ReactTtsSpan.class.getSimpleName();
private static final String TYPE_TELEPHONE_WARNING_MSG =
"Failed to retrieve telephone number (for example '0112123432').";
private static final String TYPE_MEASURE_WARNING_MSG =
"Failed to retrieve unit type (for ex. meter, second, milli).";

public ReactTtsSpan(String type, PersistableBundle args) {
super(type, args);
}

// https://developer.android.com/reference/android/text/style/TtsSpan
public enum AccessibilitySpan {
NONE,
CARDINAL,
ORDINAL,
MEASURE,
TELEPHONE,
VERBATIM;

public static String getValue(AccessibilitySpan accessibilitySpan) {
switch (accessibilitySpan) {
case CARDINAL:
return ReactTtsSpan.TYPE_CARDINAL;
case ORDINAL:
return ReactTtsSpan.TYPE_ORDINAL;
case MEASURE:
return ReactTtsSpan.TYPE_MEASURE;
case TELEPHONE:
return ReactTtsSpan.TYPE_TELEPHONE;
case VERBATIM:
return ReactTtsSpan.TYPE_VERBATIM;
case NONE:
return ReactTtsSpan.TYPE_TEXT;
default:
throw new IllegalArgumentException(
"Invalid accessibility span value: " + accessibilitySpan);
}
}

public static AccessibilitySpan fromValue(@Nullable String value) {
for (AccessibilitySpan accessibilitySpan : AccessibilitySpan.values()) {
if (accessibilitySpan.name().equalsIgnoreCase(value)) {
return accessibilitySpan;
}
}
throw new IllegalArgumentException("Invalid accessibility role value: " + value);
}
}

public static class Builder {
private String mType;
private final PersistableBundle mArgs = new PersistableBundle();

public Builder(String type) {
mType = type;
}
fabOnReact marked this conversation as resolved.
Show resolved Hide resolved

public Builder(AccessibilitySpan type, @Nullable String accessibilityLabel) {
mType = AccessibilitySpan.getValue(type);
String warningMessage = "";
if (accessibilityLabel == null) {
return;
}
try {
/*
* The default type used when accessibilitySpan prop is not set (AccessibilitySpan.NONE)
* Adds the accessibilityLabel announcement on a Nested Text.
*
* https://developer.android.com/reference/android/text/style/TtsSpan#TYPE_TEXT
* This span type can be used to add morphosyntactic features to the text it spans over,
* or synthesize a something else than the spanned text.
* Use the argument ARG_TEXT to set a different text.
*
* https://developer.android.com/reference/android/text/style/TtsSpan#ARG_TEXT
* String supplying the text to be synthesized.
* The synthesizer is free to decide how to interpret the text. Can be used with TYPE_TEXT.
*/
if (mType.equals(TYPE_TEXT)) {
setStringArgument(ARG_TEXT, accessibilityLabel);
}
/*
* <p>Telephone refer to
* https://developer.android.com/reference/android/text/style/TtsSpan#ARG_NUMBER_PARTS
*
* <p>Argument used to specify the main number part of a telephone number. Can be a string of
* digits where the different parts of the telephone number can be separated with a space, '-',
* '/' or '.'. Can be used with TYPE_TELEPHONE.
*/
if (mType.equals(TYPE_TELEPHONE)) {
warningMessage = TYPE_TELEPHONE_WARNING_MSG;
setStringArgument(ARG_NUMBER_PARTS, accessibilityLabel);
}
/*
* <p>Measure refer to
* https://developer.android.com/reference/android/text/style/TtsSpan#ARG_UNIT
*
* <p>Argument used to specify the unit of a measure. The unit should always be specified in
* English singular form. Prefixes may be used. Engines will do their best to pronounce them
* correctly in the language used. Engines are expected to at least support the most common ones
* like "meter", "second", "degree celsius" and "degree fahrenheit" with some common prefixes
* like "milli" and "kilo". Can be used with TYPE_MEASURE.
*/
if (mType.equals(TYPE_MEASURE)) {
warningMessage = TYPE_MEASURE_WARNING_MSG;
setStringArgument(ARG_UNIT, accessibilityLabel);
}
/*
* <p>Ordinal and Cardinal
* https://developer.android.com/reference/android/text/style/TtsSpan#ARG_NUMBER
*
* <p>Argument used to specify a whole number. The value can be a string of digits of any size
* optionally prefixed with a - or +. Can be used with TYPE_CARDINAL and TYPE_ORDINAL.
*/
if (mType.equals(TYPE_CARDINAL) || mType.equals(TYPE_ORDINAL)) {
setStringArgument(ARG_NUMBER, accessibilityLabel);
}
} catch (Exception e) {
// fallback and use accessibilityLabel as text
if (mType != TYPE_TEXT) {
mType = TYPE_TEXT;
setStringArgument(ARG_TEXT, accessibilityLabel);
}
FLog.w(
TAG,
"Failed to create Builder with params type: "
+ type
+ " and accessibilityLabel: "
+ accessibilityLabel
+ " "
+ warningMessage
+ "Error: "
+ e);
}
}

public ReactTtsSpan build() {
return new ReactTtsSpan(mType, mArgs);
}

public void setStringArgument(String arg, String value) {
mArgs.putString(arg, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.facebook.react.uimanager.ReactAccessibilityDelegate;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.views.text.ReactTtsSpan.AccessibilitySpan;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -53,6 +54,8 @@ public class TextAttributeProps {
public static final short TA_KEY_IS_HIGHLIGHTED = 20;
public static final short TA_KEY_LAYOUT_DIRECTION = 21;
public static final short TA_KEY_ACCESSIBILITY_ROLE = 22;
public static final short TA_KEY_ACCESSIBILITY_SPAN = 24;
public static final short TA_KEY_ACCESSIBILITY_LABEL = 25;

public static final int UNSET = -1;

Expand Down Expand Up @@ -102,6 +105,8 @@ public class TextAttributeProps {

protected @Nullable ReactAccessibilityDelegate.AccessibilityRole mAccessibilityRole = null;
protected boolean mIsAccessibilityRoleSet = false;
protected AccessibilitySpan mAccessibilitySpan = AccessibilitySpan.NONE;
protected @Nullable String mAccessibilityLabel = null;
protected boolean mIsAccessibilityLink = false;

protected int mFontStyle = UNSET;
Expand Down Expand Up @@ -205,6 +210,12 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) {
case TA_KEY_ACCESSIBILITY_ROLE:
result.setAccessibilityRole(entry.getStringValue());
break;
case TA_KEY_ACCESSIBILITY_SPAN:
result.setAccessibilitySpan(entry.getStringValue());
break;
case TA_KEY_ACCESSIBILITY_LABEL:
result.setAccessibilityLabel(entry.getStringValue());
break;
}
}

Expand Down Expand Up @@ -609,6 +620,18 @@ private void setAccessibilityRole(@Nullable String accessibilityRole) {
}
}

private void setAccessibilitySpan(@Nullable String accessibilitySpan) {
if (accessibilitySpan != null) {
mAccessibilitySpan = AccessibilitySpan.fromValue(accessibilitySpan);
}
}

private void setAccessibilityLabel(@Nullable String accessibilityLabel) {
if (accessibilityLabel != null) {
mAccessibilityLabel = accessibilityLabel;
}
}

public static int getTextBreakStrategy(@Nullable String textBreakStrategy) {
int androidTextBreakStrategy = DEFAULT_BREAK_STRATEGY;
if (textBreakStrategy != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.views.text.ReactTtsSpan.AccessibilitySpan;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureOutput;
Expand Down Expand Up @@ -143,6 +144,17 @@ private static void buildSpannableFromFragment(
if (textAttributes.mIsAccessibilityLink) {
ops.add(new SetSpanOperation(start, end, new ReactClickableSpan(reactTag)));
}
boolean hasAccessibilitySpan =
textAttributes.mAccessibilitySpan != null
&& textAttributes.mAccessibilitySpan != AccessibilitySpan.NONE;
boolean hasAccessibilityLabel =
textAttributes.mAccessibilitySpan != null && textAttributes.mAccessibilityLabel != null;
if (hasAccessibilitySpan || hasAccessibilityLabel) {
ReactTtsSpan.Builder builder =
new ReactTtsSpan.Builder(
textAttributes.mAccessibilitySpan, textAttributes.mAccessibilityLabel);
ops.add(new SetSpanOperation(start, end, builder.build()));
}
if (textAttributes.mIsColorSet) {
ops.add(
new SetSpanOperation(
Expand Down
Loading