-
Notifications
You must be signed in to change notification settings - Fork 24.9k
1/n Adding Android support for Accessibility TtsSpan API - Support for accessibilitySpan and accessibilityLabel props in nested Text #35130
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
Changes from 57 commits
216a30a
48a01d0
08d6f43
145303e
f66e914
3d4e662
1653d6b
6def7a9
7fa58f5
43e7c29
0afdc69
0cd0ac4
f5a3b49
08fc9ac
c2adbb8
46c4fb8
45ec47f
0a00193
72ee5a1
81fe302
7af9bd3
16f1f39
d845073
e215baa
03a125c
fe5c480
7e264da
d8823e5
9d4b8a5
7b85e25
aefa45f
95d6d3b
24c182a
7b4231f
4fbc54b
29a8104
e3fd7ff
3e1d292
8b23efd
bea5af9
6fc18d4
8b592f7
f2642f6
c4cd23b
576bad6
958ae95
8a59592
eb95e08
5aa9f74
cb52ad9
60ef8f0
06096d5
cc45e04
096019c
7f4a0cd
0a42fd0
79c4aed
dfc4063
3b30659
fe4e1e0
ef9c59b
ac79bae
145db13
a892b68
61e5920
f495025
753858e
821a4c8
4988339
02af5a4
dcb9f61
e7cbd28
565d7ed
a1c7e0f
f797c20
8b90011
e830578
25b212e
c058657
151b37c
6f68513
e1c56ac
ecc815b
7c80bdb
34ed14e
23241dd
112ed6f
235eda8
d536cf7
c154196
76ce49a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| /* | ||
| * 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.Parcel; | ||
| import android.os.PersistableBundle; | ||
| import android.text.style.TtsSpan; | ||
| import com.facebook.common.logging.FLog; | ||
| import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; | ||
| import java.util.Arrays; | ||
| import java.util.Currency; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /* | ||
| * 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_MONEY_WARNING_MSG = | ||
| "The accessibilityUnit format may not be compatible" | ||
| + " with the format supported ISO 4217 (for example 'USD')."; | ||
| 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); | ||
| } | ||
|
|
||
| public ReactTtsSpan(Parcel src) { | ||
| super(src); | ||
| } | ||
|
|
||
| public static class Builder<C extends Builder<?>> { | ||
| private final String mType; | ||
| private PersistableBundle mArgs = new PersistableBundle(); | ||
|
|
||
| public Builder(String type) { | ||
| mType = type; | ||
| } | ||
|
|
||
| public Builder(AccessibilityRole type, @Nullable String accessibilityUnit) { | ||
| String typeConvertedToString = AccessibilityRole.getValue(type); | ||
| mType = typeConvertedToString; | ||
| String roleClassName = AccessibilityRole.getValue(type); | ||
| String warningMessage = ""; | ||
| Set<String> supportedTypes = new HashSet<String>(); | ||
| supportedTypes.addAll( | ||
| Arrays.asList( | ||
| new String[] {TtsSpan.TYPE_MONEY, TtsSpan.TYPE_TELEPHONE, TtsSpan.TYPE_MEASURE})); | ||
| if (accessibilityUnit == null || !supportedTypes.contains(roleClassName)) { | ||
| return; | ||
| } | ||
| try { | ||
| if (roleClassName == ReactTtsSpan.TYPE_MONEY) { | ||
| warningMessage = ReactTtsSpan.TYPE_MONEY_WARNING_MSG; | ||
| Currency.getInstance(accessibilityUnit); | ||
| setStringArgument(ReactTtsSpan.ARG_INTEGER_PART, ""); | ||
| setStringArgument(ReactTtsSpan.ARG_CURRENCY, accessibilityUnit); | ||
| } | ||
| if (roleClassName == ReactTtsSpan.TYPE_TELEPHONE) { | ||
| warningMessage = ReactTtsSpan.TYPE_TELEPHONE_WARNING_MSG; | ||
| setStringArgument(ReactTtsSpan.ARG_NUMBER_PARTS, accessibilityUnit); | ||
| } | ||
| // https://developer.android.com/reference/android/text/style/TtsSpan#ARG_UNIT | ||
| if (roleClassName == ReactTtsSpan.TYPE_MEASURE) { | ||
| warningMessage = ReactTtsSpan.TYPE_MEASURE_WARNING_MSG; | ||
| setStringArgument(ReactTtsSpan.ARG_UNIT, accessibilityUnit); | ||
| } | ||
| } catch (Exception e) { | ||
| FLog.e( | ||
| TAG, | ||
| "Failed to create ReactTtsSpan.Builder with params type: " | ||
| + type | ||
| + " and accessibilityUnit: " | ||
| + accessibilityUnit | ||
| + " " | ||
| + warningMessage | ||
| + "Error: " | ||
| + e); | ||
| } | ||
| } | ||
|
|
||
| public ReactTtsSpan build() { | ||
| return new ReactTtsSpan(mType, mArgs); | ||
| } | ||
|
|
||
| public C setIntArgument(String arg, int value) { | ||
| mArgs.putInt(arg, value); | ||
| return (C) this; | ||
| } | ||
|
|
||
| public C setStringArgument(String arg, String value) { | ||
| mArgs.putString(arg, value); | ||
| return (C) this; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import android.os.Build; | ||
| import android.text.Layout; | ||
| import android.text.TextUtils; | ||
| import android.text.style.TtsSpan; | ||
| import android.util.LayoutDirection; | ||
| import android.view.Gravity; | ||
| import androidx.annotation.Nullable; | ||
|
|
@@ -18,12 +19,15 @@ | |
| import com.facebook.react.bridge.ReadableMap; | ||
| import com.facebook.react.common.mapbuffer.MapBuffer; | ||
| import com.facebook.react.uimanager.PixelUtil; | ||
| import com.facebook.react.uimanager.ReactAccessibilityDelegate; | ||
| import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; | ||
| import com.facebook.react.uimanager.ReactStylesDiffMap; | ||
| import com.facebook.react.uimanager.ViewProps; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.HashSet; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| // TODO: T63643819 refactor naming of TextAttributeProps to make explicit that this represents | ||
| // TextAttributes and not TextProps. As part of this refactor extract methods that don't belong to | ||
|
|
@@ -53,6 +57,7 @@ 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_UNIT = 47; | ||
|
|
||
| public static final int UNSET = -1; | ||
|
|
||
|
|
@@ -100,9 +105,11 @@ public class TextAttributeProps { | |
| protected boolean mIsLineThroughTextDecorationSet = false; | ||
| protected boolean mIncludeFontPadding = true; | ||
|
|
||
| protected @Nullable ReactAccessibilityDelegate.AccessibilityRole mAccessibilityRole = null; | ||
| protected @Nullable AccessibilityRole mAccessibilityRole = null; | ||
| protected boolean mIsAccessibilityRoleSet = false; | ||
| protected @Nullable String mAccessibilityUnit = null; | ||
| protected boolean mIsAccessibilityLink = false; | ||
| protected boolean mIsAccessibilityTtsSpan = false; | ||
|
|
||
| protected int mFontStyle = UNSET; | ||
| protected int mFontWeight = UNSET; | ||
|
|
@@ -205,6 +212,9 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) { | |
| case TA_KEY_ACCESSIBILITY_ROLE: | ||
| result.setAccessibilityRole(entry.getStringValue()); | ||
| break; | ||
| case TA_KEY_ACCESSIBILITY_UNIT: | ||
| result.setAccessibilityUnit(entry.getStringValue()); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -602,10 +612,34 @@ private void setTextTransform(@Nullable String textTransform) { | |
| private void setAccessibilityRole(@Nullable String accessibilityRole) { | ||
| if (accessibilityRole != null) { | ||
| mIsAccessibilityRoleSet = true; | ||
| mAccessibilityRole = | ||
| ReactAccessibilityDelegate.AccessibilityRole.fromValue(accessibilityRole); | ||
| mIsAccessibilityLink = | ||
| mAccessibilityRole.equals(ReactAccessibilityDelegate.AccessibilityRole.LINK); | ||
| mAccessibilityRole = AccessibilityRole.fromValue(accessibilityRole); | ||
| mIsAccessibilityLink = mAccessibilityRole.equals(AccessibilityRole.LINK); | ||
| String roleClassName = | ||
| AccessibilityRole.getValue(AccessibilityRole.fromValue(accessibilityRole)); | ||
| Set<String> supportedTypes = new HashSet<String>(); | ||
|
||
| supportedTypes.addAll( | ||
| Arrays.asList( | ||
| new String[] { | ||
| TtsSpan.TYPE_ORDINAL, | ||
| TtsSpan.TYPE_DECIMAL, | ||
| TtsSpan.TYPE_FRACTION, | ||
| TtsSpan.TYPE_MEASURE, | ||
| TtsSpan.TYPE_TIME, | ||
| TtsSpan.TYPE_DATE, | ||
| TtsSpan.TYPE_TELEPHONE, | ||
| TtsSpan.TYPE_ELECTRONIC, | ||
| TtsSpan.TYPE_MONEY, | ||
| TtsSpan.TYPE_DIGITS, | ||
| TtsSpan.TYPE_VERBATIM, | ||
| })); | ||
| mIsAccessibilityTtsSpan = | ||
| supportedTypes.contains(roleClassName) && Build.VERSION.SDK_INT >= 21; | ||
| } | ||
| } | ||
|
|
||
| private void setAccessibilityUnit(@Nullable String accessibilityUnit) { | ||
| if (accessibilityUnit != null) { | ||
| mAccessibilityUnit = accessibilityUnit; | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.