Skip to content

Commit 216a30a

Browse files
committed
draft solution partially spelling correctly
1 parent 2cc2ca1 commit 216a30a

File tree

9 files changed

+143
-750
lines changed

9 files changed

+143
-750
lines changed

ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
import android.os.Message;
1616
import android.text.Layout;
1717
import android.text.Spannable;
18+
import android.text.SpannableString;
1819
import android.text.Spanned;
1920
import android.text.style.AbsoluteSizeSpan;
2021
import android.text.style.ClickableSpan;
22+
import android.util.Log;
2123
import android.view.View;
2224
import android.view.accessibility.AccessibilityEvent;
2325
import android.widget.TextView;
@@ -44,6 +46,7 @@
4446
import com.facebook.react.uimanager.events.Event;
4547
import com.facebook.react.uimanager.events.EventDispatcher;
4648
import com.facebook.react.uimanager.util.ReactFindViewUtil;
49+
import com.facebook.react.views.text.ReactTtsSpan;
4750
import java.util.ArrayList;
4851
import java.util.HashMap;
4952
import java.util.List;
@@ -71,6 +74,8 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
7174

7275
private final View mView;
7376
private final AccessibilityLinks mAccessibilityLinks;
77+
private final AccessibilityLinks mAccessibilitySpans;
78+
// private final TtsSpan.MoneyBuilder mSpanned;
7479

7580
private Handler mHandler;
7681

@@ -216,6 +221,7 @@ public void handleMessage(Message msg) {
216221
mView.setFocusable(originalFocus);
217222
ViewCompat.setImportantForAccessibility(mView, originalImportantForAccessibility);
218223
mAccessibilityLinks = (AccessibilityLinks) mView.getTag(R.id.accessibility_links);
224+
mAccessibilitySpans = (AccessibilityLinks) mView.getTag(R.id.accessibility_spans);
219225
}
220226

221227
@Nullable View mAccessibilityLabelledBy;
@@ -228,6 +234,8 @@ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCo
228234
final String accessibilityHint = (String) host.getTag(R.id.accessibility_hint);
229235
if (accessibilityRole != null) {
230236
setRole(info, accessibilityRole, host.getContext());
237+
info.setHeading(true);
238+
info.setRoleDescription("heading");
231239
}
232240

233241
if (accessibilityHint != null) {
@@ -579,6 +587,37 @@ protected void onPopulateNodeForVirtualView(
579587
node.setBoundsInParent(getBoundsInParent(accessibleTextSpan));
580588
node.setRoleDescription(mView.getResources().getString(R.string.link_description));
581589
node.setClassName(AccessibilityRole.getValue(AccessibilityRole.BUTTON));
590+
Log.w("TESTING::ReactAccessibilityDelegate", "mAccessibilitySpans: " + (mAccessibilitySpans));
591+
if (mAccessibilitySpans == null) {
592+
return;
593+
}
594+
final AccessibilityLinks.AccessibleLink ttsSpan =
595+
mAccessibilitySpans.getLinkById(virtualViewId);
596+
Log.w("TESTING::ReactAccessibilityDelegate", "ttsSpan: " + (ttsSpan));
597+
if (ttsSpan == null) {
598+
return;
599+
}
600+
Log.w("TESTING::ReactAccessibilityDelegate", "node.getText(): " + (node.getText()));
601+
if (mView instanceof TextView) {
602+
TextView textView = (TextView) mView;
603+
Log.w("TESTING::ReactAccessibilityDelegate", "textView.getText(): " + (textView.getText()));
604+
SpannableString spannableString = new SpannableString(textView.getText());
605+
spannableString.setSpan(ttsSpan.span, 0, java.lang.Math.min(6, ttsSpan.end), 0);
606+
node.setContentDescription(spannableString);
607+
// node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
608+
// node.setRoleDescription(mView.getResources().getString(R.string.link_description));
609+
// node.setHeading(true);
610+
// node.setRoleDescription("heading");
611+
// node.setClassName(AccessibilityRole.getValue(AccessibilityRole.BUTTON));
612+
}
613+
// String string = "my test string";
614+
Log.w("TESTING::ReactAccessibilityDelegate", "ttsSpan.span: " + (ttsSpan.span));
615+
Log.w("TESTING::ReactAccessibilityDelegate", "ttsSpan.end: " + (ttsSpan.end));
616+
Log.w(
617+
"TESTING::ReactAccessibilityDelegate",
618+
"ttsSpan.span.getArgs(): " + (ttsSpan.span.getArgs()));
619+
// TtsSpan ttsSpan = new TtsSpan.Builder(TtsSpan.TYPE_VERBATIM).build();
620+
582621
}
583622

584623
private Rect getBoundsInParent(AccessibilityLinks.AccessibleLink accessibleLink) {
@@ -647,6 +686,34 @@ protected boolean onPerformActionForVirtualView(
647686
public static class AccessibilityLinks {
648687
private final List<AccessibleLink> mLinks;
649688

689+
public AccessibilityLinks(ReactTtsSpan[] spans, Spannable text) {
690+
ArrayList<AccessibleLink> links = new ArrayList<>();
691+
for (int i = 0; i < spans.length; i++) {
692+
ReactTtsSpan span = spans[i];
693+
int start = text.getSpanStart(span);
694+
int end = text.getSpanEnd(span);
695+
// zero length spans, and out of range spans should not be included.
696+
if (start == end || start < 0 || end < 0 || start > text.length() || end > text.length()) {
697+
continue;
698+
}
699+
700+
final AccessibleLink link = new AccessibleLink();
701+
link.span = span;
702+
link.start = start;
703+
link.end = end;
704+
705+
// ID is the reverse of what is expected, since the ClickableSpans are returned in reverse
706+
// order due to being added in reverse order. If we don't do this, focus will move to the
707+
// last link first and move backwards.
708+
//
709+
// If this approach becomes unreliable, we should instead look at their start position and
710+
// order them manually.
711+
link.id = spans.length - 1 - i;
712+
links.add(link);
713+
}
714+
mLinks = links;
715+
}
716+
650717
public AccessibilityLinks(ClickableSpan[] spans, Spannable text) {
651718
ArrayList<AccessibleLink> links = new ArrayList<>();
652719
for (int i = 0; i < spans.length; i++) {
@@ -703,6 +770,7 @@ public int size() {
703770

704771
private static class AccessibleLink {
705772
public String description;
773+
public ReactTtsSpan span;
706774
public int start;
707775
public int end;
708776
public int id;

ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.text.Spannable;
1616
import android.text.SpannableStringBuilder;
1717
import android.text.TextUtils;
18+
import android.util.Log;
1819
import android.view.Gravity;
1920
import androidx.annotation.Nullable;
2021
import com.facebook.infer.annotation.Assertions;
@@ -101,6 +102,7 @@ private static void buildSpannedFromShadowNode(
101102
boolean supportsInlineViews,
102103
Map<Integer, ReactShadowNode> inlineViews,
103104
int start) {
105+
Log.w("TESTING::ReactBaseTextShadowNode", "buildSpannedFromShadowNode");
104106

105107
TextAttributes textAttributes;
106108
if (parentTextAttributes != null) {
@@ -483,10 +485,12 @@ public void setColor(@Nullable Integer color) {
483485

484486
@ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color")
485487
public void setBackgroundColor(@Nullable Integer color) {
488+
Log.w("TESTING::ReactBaseTextShadowNode", "setBackgroundColor");
486489
// Background color needs to be handled here for virtual nodes so it can be incorporated into
487490
// the span. However, it doesn't need to be applied to non-virtual nodes because non-virtual
488491
// nodes get mapped to native views and native views get their background colors get set via
489492
// {@link BaseViewManager}.
493+
Log.w("TESTING::ReactBaseTextShadowNode", "isVirtual(): " + (isVirtual()));
490494
if (isVirtual()) {
491495
mIsBackgroundColorSet = (color != null);
492496
if (mIsBackgroundColorSet) {

ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ public void updateExtraData(ReactTextView view, Object extraData) {
9494
ReactClickableSpan[] clickableSpans =
9595
spannable.getSpans(0, update.getText().length(), ReactClickableSpan.class);
9696

97+
ReactTtsSpan[] ttsSpans = spannable.getSpans(0, update.getText().length(), ReactTtsSpan.class);
98+
if (ttsSpans.length > 0) {
99+
view.setTag(
100+
R.id.accessibility_spans,
101+
new ReactAccessibilityDelegate.AccessibilityLinks(ttsSpans, spannable));
102+
ReactAccessibilityDelegate.resetDelegate(
103+
view, view.isFocusable(), view.getImportantForAccessibility());
104+
}
105+
97106
if (clickableSpans.length > 0) {
98107
view.setTag(
99108
R.id.accessibility_links,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.views.text;
9+
10+
import android.os.Parcel;
11+
import android.os.PersistableBundle;
12+
import android.text.style.TtsSpan;
13+
14+
/*
15+
* Wraps {@link BackgroundColorSpan} as a {@link ReactSpan}.
16+
*/
17+
public class ReactTtsSpan extends TtsSpan implements ReactSpan {
18+
public ReactTtsSpan(String type, PersistableBundle args) {
19+
super(type, args);
20+
}
21+
22+
public ReactTtsSpan(Parcel src) {
23+
super(src);
24+
}
25+
26+
public static class Builder<C extends Builder<?>> {
27+
private final String mType;
28+
private PersistableBundle mArgs = new PersistableBundle();
29+
30+
public Builder(String type) {
31+
mType = type;
32+
}
33+
34+
public ReactTtsSpan build() {
35+
return new ReactTtsSpan(mType, mArgs);
36+
}
37+
}
38+
}

ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.text.StaticLayout;
2020
import android.text.TextPaint;
2121
import android.util.LayoutDirection;
22+
import android.util.Log;
2223
import android.util.LruCache;
2324
import android.view.View;
2425
import androidx.annotation.NonNull;
@@ -149,9 +150,19 @@ private static void buildSpannableFromFragment(
149150
start, end, new ReactForegroundColorSpan(textAttributes.mColor)));
150151
}
151152
if (textAttributes.mIsBackgroundColorSet) {
153+
/*
152154
ops.add(
153155
new SetSpanOperation(
154156
start, end, new ReactBackgroundColorSpan(textAttributes.mBackgroundColor)));
157+
*/
158+
if (Build.VERSION.SDK_INT > 21 && textAttributes.mBackgroundColor == -65536) {
159+
Log.w(
160+
"TESTING::TextLayoutManagerMapBuffer",
161+
"textAttributes.mBackgroundColor: " + (textAttributes.mBackgroundColor));
162+
ops.add(
163+
new SetSpanOperation(
164+
start, end, new ReactTtsSpan.Builder(ReactTtsSpan.TYPE_VERBATIM).build()));
165+
}
155166
}
156167
if (!Float.isNaN(textAttributes.getLetterSpacing())) {
157168
ops.add(
@@ -574,7 +585,7 @@ public static WritableArray measureLines(
574585
// TODO T31905686: This class should be private
575586
public static class SetSpanOperation {
576587
protected int start, end;
577-
protected ReactSpan what;
588+
public ReactSpan what;
578589

579590
public SetSpanOperation(int start, int end, ReactSpan what) {
580591
this.start = start;

ReactAndroid/src/main/res/views/uimanager/values/ids.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
<!-- tag is used to store accessibilityLinks tag -->
3737
<item type="id" name="accessibility_links"/>
3838

39+
<item type="id" name="accessibility_spans"/>
40+
3941
<!-- tag is used to store accessibilityLabelledBy tag -->
4042
<item type="id" name="labelled_by"/>
4143

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins { id("io.github.gradle-nexus.publish-plugin") version "1.1.0" }
99

1010
val reactAndroidProperties = java.util.Properties()
1111

12-
File("./ReactAndroid/gradle.properties").inputStream().use { reactAndroidProperties.load(it) }
12+
File("$rootDir/ReactAndroid/gradle.properties").inputStream().use { reactAndroidProperties.load(it) }
1313

1414
version =
1515
if (project.hasProperty("isNightly") &&

packages/rn-tester/js/examples/Text/TextAdjustsDynamicLayoutExample.js

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,19 @@ import {useState} from 'react';
1414

1515
export default function TextAdjustsDynamicLayoutExample(props: {}): React.Node {
1616
const [height, setHeight] = useState(20);
17-
1817
return (
1918
<>
2019
<View>
21-
<View style={[styles.subjectContainer, {height}]}>
20+
<Text fontWeight={700} accessible={true}>
21+
My number is{' '}
2222
<Text
23-
adjustsFontSizeToFit={true}
24-
numberOfLines={1}
25-
style={styles.subjectText}>
26-
This is adjusting text.
23+
accessibilityRole="link"
24+
accessible={true}
25+
style={{backgroundColor: 'red'}}>
26+
please spell this text
2727
</Text>
28-
</View>
29-
</View>
30-
<View style={styles.row}>
31-
<Button onPress={() => setHeight(20)} title="Set Height to 20" />
32-
</View>
33-
<View style={styles.row}>
34-
<Button onPress={() => setHeight(40)} title="Set Height to 40" />
35-
</View>
36-
<View style={styles.row}>
37-
<Button onPress={() => setHeight(60)} title="Set Height to 60" />
28+
.
29+
</Text>
3830
</View>
3931
</>
4032
);

0 commit comments

Comments
 (0)