Skip to content

Commit

Permalink
Adding prop accessibilityUnit to TextAttributesProps (fabric) (#3)
Browse files Browse the repository at this point in the history
## Summary

Adds the accessibilityUnit to fabric CPP API  (part of PR facebook#35130 Adding support for Android Accessibility TtsSpan API)

The TtsSpan API now supports verbatim, date, and fraction but may not work with other span types like phone numbers, currency, time, measure, and money.

An explanation of the use case is as follows:
- Time is spelled 1 0 3 0 instead of ten thirty
- The developer does not have the option to specify if 10m stands for 10 meters or 10 miles, or 10 minutes
- Phone numbers, for example, 33312344, are not yet supported

Implementing the above functionality requires adding a new prop to the react-native API responsible for managing attributes of nested Text. 

- Java and CPP TextAttributes manage the functionality.
- Nested Text does not correspond to a Widget on Android but an Android TextView Span.  

The spans are updated based on a caching mechanism that re-renders the Android TextView only if specific attributes are updated.
The updates are triggered only if the Text or Paragraph Text attributes change.
- An example of Text attributes is accessibilityRole, font-weight, and backgroundColor.
- An example of Paragraph attributes is textBreakstrategy. They are updated with different mechanisms because they may change the paragraph layout.
These mappings are configured in Java, CPP, and Javascript.

As this and PR [facebook#33468](facebook#33468) require changes to the text attributes, the below work was needed:

- issue documented in PR [facebook#33468 (comment)](facebook#33468 (comment)) and PR [facebook#35130 (comment)](facebook#35130 (comment)). The CPP changes for facebook#35130 are moved to a separate PR ([fabriziobertoglio1987#2](#2)). The RNTester App would crash without a clear stack trace on startup after making changes to CPP. This was later fixed by adding the CPP headers to the prefab library rrc_view (see this [comment](facebook#35130 (comment))).
- Adding the CPP configurations (see [fabriziobertoglio1987#2](https://github.com/fabriziobertoglio1987/react-- native/pull/2)) required extensive troubleshooting and analysis

Related: PR #4 - separating accessibilityUnit from accessibilityMinutes and accessibilityHours
  • Loading branch information
fabOnReact authored Nov 15, 2022
1 parent 3e1d292 commit 8b23efd
Show file tree
Hide file tree
Showing 19 changed files with 61 additions and 99 deletions.
4 changes: 0 additions & 4 deletions Libraries/Components/View/ViewAccessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,6 @@ export type AccessibilityState = {
...
};

export type AccessibilityUnit = {
hours?: number,
};

export type AccessibilityValue = $ReadOnly<{|
/**
* The minimum value of this component's range. (should be an integer)
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ export type ViewProps = $ReadOnly<{|
* Indicates to accessibility services that UI Component is in a specific State.
*/
accessibilityState?: ?AccessibilityState,
accessibilityUnit?: ?AccessibilityUnit,
accessibilityUnit?: ?Stringish,
accessibilityValue?: ?AccessibilityValue,

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,6 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta
}
}

@Override
@ReactProp(name = ViewProps.ACCESSIBILITY_UNIT)
public void setAccessibilityUnit(@NonNull T view, @Nullable ReadableMap accessibilityUnit) {
// do nothing
}

private void updateViewContentDescription(@NonNull T view) {
final String accessibilityLabel = (String) view.getTag(R.id.accessibility_label);
final ReadableMap accessibilityState = (ReadableMap) view.getTag(R.id.accessibility_state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ public void setAccessibilityCollectionItem(
@Override
public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) {}

@Override
public void setAccessibilityUnit(@NonNull T view, @Nullable ReadableMap accessibilityUnit) {}

@Override
public void setBackgroundColor(@NonNull T view, int backgroundColor) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ public interface BaseViewManagerInterface<T extends View> {

void setViewState(T view, @Nullable ReadableMap accessibilityState);

void setAccessibilityUnit(T view, @Nullable ReadableMap accessibilityUnit);

void setBackgroundColor(T view, int backgroundColor);

void setBorderRadius(T view, float borderRadius);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,6 @@ private static void buildSpannedFromShadowNode(
ops.add(
new SetSpanOperation(start, end, new ReactClickableSpan(textShadowNode.getReactTag())));
}
if (textShadowNode.mAccessibilityUnit != null && Build.VERSION.SDK_INT >= 21) {
/*
ops.add(
new SetSpanOperation(
start, end, new ReactTtsSpan.Builder(textShadowNode.mAccessibilityUnit).build()));
*/
}
float effectiveLetterSpacing = textAttributes.getEffectiveLetterSpacing();
if (!Float.isNaN(effectiveLetterSpacing)
&& (parentTextAttributes == null
Expand Down Expand Up @@ -517,13 +510,6 @@ public void setIsAccessibilityLink(@Nullable String accessibilityRole) {
}
}

@ReactProp(name = "accessibilityUnit")
public void setAccessibilityUnit(@Nullable String accessibilityUnit) {
if (isVirtual()) {
markUpdated();
}
}

@ReactProp(name = ViewProps.FONT_FAMILY)
public void setFontFamily(@Nullable String fontFamily) {
mFontFamily = fontFamily;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
import android.text.Spannable;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.Spacing;
Expand Down Expand Up @@ -51,13 +48,6 @@ public void setAccessible(ReactTextView view, boolean accessible) {
view.setFocusable(accessible);
}

@Override
@ReactProp(name = ViewProps.ACCESSIBILITY_UNIT)
public void setAccessibilityUnit(@NonNull T view, @Nullable ReadableMap accessibilityUnit) {
Log.w("TESTING::ReactTextAnchorViewManager", "setAccessibilityUnit");
Log.w("TESTING::ReactTextAnchorViewManager", "accessibilityUnit: " + (accessibilityUnit));
}

// maxLines can only be set in master view (block), doesn't really make sense to set in a span
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES)
public void setNumberOfLines(ReactTextView view, int numberOfLines) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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;

Expand Down Expand Up @@ -104,6 +105,7 @@ public class TextAttributeProps {
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;
Expand Down Expand Up @@ -206,6 +208,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;
}
}

Expand Down Expand Up @@ -607,13 +612,15 @@ private void setAccessibilityRole(@Nullable String accessibilityRole) {
mIsAccessibilityLink = mAccessibilityRole.equals(AccessibilityRole.LINK);
String roleClassName =
AccessibilityRole.getValue(AccessibilityRole.fromValue(accessibilityRole));
mAccessibilityUnit =
ReactTtsSpan.SUPPORTED_UNIT_TYPES.contains(roleClassName) ? roleClassName : null;
mIsAccessibilityTtsSpan =
ReactTtsSpan.SUPPORTED_UNIT_TYPES.contains(roleClassName) && Build.VERSION.SDK_INT >= 21;
}
}

private void setAccessibilityUnit(@Nullable String accessibilityUnit) {
// not yet implemented
if (accessibilityUnit != null) {
mAccessibilityUnit = accessibilityUnit;
}
}

public static int getTextBreakStrategy(@Nullable String textBreakStrategy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,22 @@ private static void buildSpannableFromFragment(
if (textAttributes.mIsAccessibilityLink) {
ops.add(new SetSpanOperation(start, end, new ReactClickableSpan(reactTag)));
}
if (textAttributes.mAccessibilityUnit != null && Build.VERSION.SDK_INT >= 21) {
if (textAttributes.mIsAccessibilityTtsSpan) {
// check that textAttributes.mAccessibilityRole == "time"
ReactTtsSpan.Builder builder = new ReactTtsSpan.Builder(ReactTtsSpan.TYPE_TIME);
builder.setIntArgument(ReactTtsSpan.ARG_HOURS, 10);
builder.setIntArgument(ReactTtsSpan.ARG_MINUTES, 30);
if (textAttributes.mAccessibilityUnit != null) {
String[] time = textAttributes.mAccessibilityUnit.split(":");
if (time[0] != null && time[1] != null) {
try {
Integer hours = Integer.parseInt(time[0]);
Integer minutes = Integer.parseInt(time[1]);
builder.setIntArgument(ReactTtsSpan.ARG_HOURS, hours);
builder.setIntArgument(ReactTtsSpan.ARG_MINUTES, minutes);
} catch (Exception e) {
FLog.w(TAG, "TtsSpan with type Time failed to set hours and minutes. Error: " + e);
}
}
}
ops.add(new SetSpanOperation(start, end, builder.build()));
}
if (textAttributes.mIsColorSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

package com.facebook.react.views.view
import android.util.Log;

import android.graphics.Color
import android.graphics.Rect
import androidx.core.view.ViewCompat
Expand Down Expand Up @@ -64,7 +64,6 @@ object ReactMapBufferPropSetter {
private const val VP_POINTER_OVER_CAPTURE = 44
private const val VP_BORDER_CURVES = 45 // iOS only
private const val VP_FG_COLOR = 46 // iOS only?
private const val VP_ACCESSIBILITY_UNIT = 47

// Yoga values
private const val YG_BORDER_WIDTH = 100
Expand Down Expand Up @@ -108,7 +107,6 @@ object ReactMapBufferPropSetter {
private const val UNDEF_COLOR = Int.MAX_VALUE

fun setProps(view: ReactViewGroup, viewManager: ReactViewManager, props: MapBuffer) {
Log.w("TESTING::ReactMapBufferPropSetter", "props: " + ( props ));
for (entry in props) {
when (entry.key) {
VP_ACCESSIBILITY_ACTIONS -> {
Expand All @@ -132,9 +130,6 @@ object ReactMapBufferPropSetter {
VP_ACCESSIBILITY_STATE -> {
viewManager.accessibilityState(view, entry.mapBufferValue)
}
VP_ACCESSIBILITY_UNIT -> {
// viewManager.accessibilityUnit(view, entry.mapBufferValue)
}
VP_ACCESSIBILITY_VALUE -> {
viewManager.accessibilityValue(view, entry.stringValue)
}
Expand Down Expand Up @@ -283,14 +278,6 @@ object ReactMapBufferPropSetter {
ViewCompat.setAccessibilityLiveRegion(this, mode)
}

private fun ReactViewManager.accessibilityUnit(view: ReactViewGroup, value: MapBuffer) {
Log.w("TESTING::ReactMapBufferPropSetter", "accessibilityUnit");
val accessibilityUnit = JavaOnlyMap()
accessibilityUnit.putString("hours", "10")

setAccessibilityUnit(view, accessibilityUnit)
}

private fun ReactViewManager.accessibilityState(view: ReactViewGroup, value: MapBuffer) {
val accessibilityState = JavaOnlyMap()
accessibilityState.putBoolean("selected", value.getBoolean(ACCESSIBILITY_STATE_SELECTED))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ void TextAttributes::apply(TextAttributes textAttributes) {
accessibilityRole = textAttributes.accessibilityRole.has_value()
? textAttributes.accessibilityRole
: accessibilityRole;
accessibilityUnit = !textAttributes.accessibilityUnit.empty() ? textAttributes.accessibilityUnit
: accessibilityUnit;
}

#pragma mark - Operators
Expand All @@ -122,6 +124,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
isHighlighted,
layoutDirection,
accessibilityRole,
accessibilityUnit,
textTransform) ==
std::tie(
rhs.foregroundColor,
Expand All @@ -142,6 +145,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
rhs.isHighlighted,
rhs.layoutDirection,
rhs.accessibilityRole,
rhs.accessibilityUnit,
rhs.textTransform) &&
floatEquality(opacity, rhs.opacity) &&
floatEquality(fontSize, rhs.fontSize) &&
Expand Down Expand Up @@ -209,6 +213,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
debugStringConvertibleItem("isHighlighted", isHighlighted),
debugStringConvertibleItem("layoutDirection", layoutDirection),
debugStringConvertibleItem("accessibilityRole", accessibilityRole),
debugStringConvertibleItem("accessibilityUnit", accessibilityUnit),
};
}
#endif
Expand Down
4 changes: 3 additions & 1 deletion ReactCommon/react/renderer/attributedstring/TextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class TextAttributes : public DebugStringConvertible {
// construction.
std::optional<LayoutDirection> layoutDirection{};
std::optional<AccessibilityRole> accessibilityRole{};
std::string accessibilityUnit{};

#pragma mark - Operations

Expand Down Expand Up @@ -131,7 +132,8 @@ struct hash<facebook::react::TextAttributes> {
textAttributes.textShadowColor,
textAttributes.isHighlighted,
textAttributes.layoutDirection,
textAttributes.accessibilityRole);
textAttributes.accessibilityRole,
textAttributes.accessibilityUnit);
}
};
} // namespace std
9 changes: 9 additions & 0 deletions ReactCommon/react/renderer/attributedstring/conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,10 @@ inline folly::dynamic toDynamic(const TextAttributes &textAttributes) {
_textAttributes(
"accessibilityRole", toString(*textAttributes.accessibilityRole));
}
if (!textAttributes.accessibilityUnit.empty()) {
_textAttributes(
"accessibilityUnit", textAttributes.accessibilityUnit);
}
return _textAttributes;
}

Expand Down Expand Up @@ -1083,6 +1087,7 @@ constexpr static MapBuffer::Key TA_KEY_IS_HIGHLIGHTED = 20;
constexpr static MapBuffer::Key TA_KEY_LAYOUT_DIRECTION = 21;
constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_ROLE = 22;
constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 23;
constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_UNIT = 47;

// constants for ParagraphAttributes serialization
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
Expand Down Expand Up @@ -1229,6 +1234,10 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) {
builder.putString(
TA_KEY_ACCESSIBILITY_ROLE, toString(*textAttributes.accessibilityRole));
}
if (!textAttributes.accessibilityUnit.empty()) {
builder.putString(
TA_KEY_ACCESSIBILITY_UNIT, textAttributes.accessibilityUnit);
}
return builder.build();
}

Expand Down
13 changes: 13 additions & 0 deletions ReactCommon/react/renderer/components/text/BaseTextProps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ static TextAttributes convertRawProp(
sourceTextAttributes.accessibilityRole,
defaultTextAttributes.accessibilityRole);

textAttributes.accessibilityUnit = convertRawProp(
context,
rawProps,
"accessibilityUnit",
sourceTextAttributes.accessibilityUnit,
defaultTextAttributes.accessibilityUnit);

// Color (accessed in this order by ViewProps)
textAttributes.opacity = convertRawProp(
context,
Expand Down Expand Up @@ -287,6 +294,12 @@ void BaseTextProps::setProp(
textAttributes,
accessibilityRole,
"accessibilityRole");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
accessibilityUnit,
"accessibilityUnit");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, opacity, "opacity");
REBUILD_FIELD_SWITCH_CASE(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,6 @@ constexpr bool operator!=(
return !(rhs == lhs);
}

struct AccessibilityUnit {
std::optional<std::string> hours;
};

constexpr bool operator==(
AccessibilityUnit const &lhs,
AccessibilityUnit const &rhs) {
return lhs.hours == rhs.hours;
}

constexpr bool operator!=(
AccessibilityUnit const &lhs,
AccessibilityUnit const &rhs) {
return !(rhs == lhs);
}

struct AccessibilityLabelledBy {
std::vector<std::string> value{};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class AccessibilityProps {

bool accessible{false};
AccessibilityState accessibilityState;
AccessibilityUnit accessibilityUnit;
std::string accessibilityUnit;
std::string accessibilityLabel{""};
AccessibilityLabelledBy accessibilityLabelledBy{};
AccessibilityLiveRegion accessibilityLiveRegion{
Expand Down
Loading

0 comments on commit 8b23efd

Please sign in to comment.