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

Role and item announcement in Flatlist #31666

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
30 changes: 27 additions & 3 deletions Libraries/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,15 @@ class VirtualizedList extends React.PureComponent<Props, State> {

_defaultRenderScrollComponent = props => {
const onRefresh = props.onRefresh;
const accessibilityCollectionProps = {
accessibilityRole: 'list',
accessibilityCollectionInfo: {
rowCount: this.props.getItemCount(this.props.data),
columnCount: 1,
hierarchical: false,
},
};

if (this._isNestedWithSameOrientation()) {
// $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors
return <View {...props} />;
Expand All @@ -1246,9 +1255,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
JSON.stringify(props.refreshing ?? 'undefined') +
'`',
);

return (
// $FlowFixMe[prop-missing] Invalid prop usage
<ScrollView
{...accessibilityCollectionProps}
{...props}
refreshControl={
props.refreshControl == null ? (
Expand All @@ -1265,7 +1276,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
);
} else {
// $FlowFixMe[prop-missing] Invalid prop usage
return <ScrollView {...props} />;
return <ScrollView {...accessibilityCollectionProps} {...props} />;
}
};

Expand Down Expand Up @@ -2064,19 +2075,32 @@ class CellRenderer extends React.Component<
: horizontal
? [styles.row, inversionStyle]
: inversionStyle;

const accessibilityCollectionItemInfo = {
rowIndex: index,
rowSpan: 1,
columnIndex: 1,
columnSpan: 1,
heading: false,
};

const result = !CellRendererComponent ? (
/* $FlowFixMe[incompatible-type-arg] (>=0.89.0 site=react_native_fb) *
This comment suppresses an error found when Flow v0.89 was deployed. *
To see the error, delete this comment and run Flow. */
<View style={cellStyle} onLayout={onLayout}>
<View
style={cellStyle}
onLayout={onLayout}
accessibilityCollectionItemInfo={accessibilityCollectionItemInfo}>
{element}
{itemSeparator}
</View>
) : (
<CellRendererComponent
{...this.props}
style={cellStyle}
onLayout={onLayout}>
onLayout={onLayout}
accessibilityCollectionItemInfo={accessibilityCollectionItemInfo}>
{element}
{itemSeparator}
</CellRendererComponent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public void setAccessibilityHint(@NonNull T view, @Nullable String accessibility
updateViewContentDescription(view);
}


@Override
@ReactProp(name = ViewProps.ACCESSIBILITY_ROLE)
public void setAccessibilityRole(@NonNull T view, @Nullable String accessibilityRole) {
Expand All @@ -160,6 +161,19 @@ public void setAccessibilityRole(@NonNull T view, @Nullable String accessibility
view.setTag(R.id.accessibility_role, AccessibilityRole.fromValue(accessibilityRole));
}


@Override
@ReactProp(name = ViewProps.ACCESSIBILITY_COLLECTION_INFO)
public void setAccessibilityCollectionInfo(@NonNull T view, @Nullable ReadableMap accessibilityCollectionInfo) {
view.setTag(R.id.accessibility_collection_info, accessibilityCollectionInfo);
}

@Override
@ReactProp(name = ViewProps.ACCESSIBILITY_COLLECTION_ITEM_INFO)
public void setAccessibilityCollectionItemInfo(@NonNull T view, @Nullable ReadableMap accessibilityCollectionItemInfo) {
view.setTag(R.id.accessibility_collection_item_info, accessibilityCollectionItemInfo);
}

@Override
@ReactProp(name = ViewProps.ACCESSIBILITY_STATE)
public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public void setAccessibilityLiveRegion(@NonNull T view, @Nullable String liveReg
@Override
public void setAccessibilityRole(@NonNull T view, @Nullable String accessibilityRole) {}

@Override
public void setAccessibilityCollectionInfo(@NonNull T view, @Nullable ReadableMap accessibilityCollectionInfo) {}

@Override
public void setAccessibilityCollectionItemInfo(@NonNull T view, @Nullable ReadableMap accessibilityCollectionItemInfo) {}

@Override
public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
package com.facebook.react.uimanager;

import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.SpannableString;
import android.text.style.URLSpan;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.Nullable;
import androidx.core.view.AccessibilityDelegateCompat;
Expand Down Expand Up @@ -107,6 +109,7 @@ public enum AccessibilityRole {
TAB,
TABLIST,
TIMER,
LIST,
TOOLBAR;

public static String getValue(AccessibilityRole role) {
Expand Down Expand Up @@ -135,6 +138,8 @@ public static String getValue(AccessibilityRole role) {
return "android.widget.SpinButton";
case SWITCH:
return "android.widget.Switch";
case LIST:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow, I didn't realize we didn't have a role for list! Good catch!

return "android.widget.AbsListView";
case NONE:
case LINK:
case SUMMARY:
Expand Down Expand Up @@ -204,6 +209,20 @@ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCo
}
final ReadableArray accessibilityActions =
(ReadableArray) host.getTag(R.id.accessibility_actions);
final ReadableMap accessibilityCollectionInfo =
(ReadableMap) host.getTag(R.id.accessibility_collection_info);


if (accessibilityCollectionInfo != null) {
int rowCount = accessibilityCollectionInfo.getInt("rowCount");
int columnCount = accessibilityCollectionInfo.getInt("columnCount");
boolean hierarchical = accessibilityCollectionInfo.getBoolean("hierarchical");

AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfoCompat = AccessibilityNodeInfoCompat.CollectionInfoCompat.obtain(rowCount, columnCount, hierarchical);
info.setCollectionInfo(collectionInfoCompat);
}


if (accessibilityActions != null) {
for (int i = 0; i < accessibilityActions.size(); i++) {
final ReadableMap action = accessibilityActions.getMap(i);
Expand Down Expand Up @@ -259,12 +278,54 @@ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCo
}
}

private boolean isViewVisible(View scrollView, View view) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you may be able to use AccessibilityNodeInfoCompat's isVisibleToUser method rather than calculating this by hand.

https://developer.android.com/reference/androidx/core/view/accessibility/AccessibilityNodeInfoCompat#isVisibleToUser()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rect scrollBounds = new Rect();
scrollView.getDrawingRect(scrollBounds);
float viewHeight = view.getHeight();
// Verify View is half visible
float top = view.getY() + viewHeight / 2;
float bottom = top + view.getHeight() - viewHeight / 2;

if (scrollBounds.top < top && scrollBounds.bottom > bottom) {
return true;
} else {
return false;
}
}

@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
// Set item count and current item index on accessibility events for adjustable
// in order to make Talkback announce the value of the adjustable
final ReadableMap accessibilityValue = (ReadableMap) host.getTag(R.id.accessibility_value);
final ReadableMap accessibilityCollectionInfo = (ReadableMap) host.getTag(R.id.accessibility_collection_info);
if (accessibilityCollectionInfo != null) {
event.setItemCount(accessibilityCollectionInfo.getInt("rowCount"));

View contentView = ((ViewGroup) host).getChildAt(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to check if host is an instance of ViewGroup before casting it as such, otherwise you could end up with a ClassCastException here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


ReadableMap firstVisible = null;
ReadableMap lastVisible = null;

for(int index = 0; index < ((ViewGroup) contentView).getChildCount(); index++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same casting comment here as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View nextChild = ((ViewGroup) contentView).getChildAt(index);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

Copy link
Contributor

@fabOnReact fabOnReact Feb 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boolean isVisible = isViewVisible(host, nextChild);
if (isVisible == true) {
if(firstVisible == null) {
firstVisible = (ReadableMap) nextChild.getTag(R.id.accessibility_collection_item_info);
}
lastVisible = (ReadableMap) nextChild.getTag(R.id.accessibility_collection_item_info);
}


if (firstVisible != null && lastVisible != null) {
event.setFromIndex(firstVisible.getInt("rowIndex"));
event.setToIndex(lastVisible.getInt("rowIndex"));
}
}
}

if (accessibilityValue != null
&& accessibilityValue.hasKey("min")
&& accessibilityValue.hasKey("now")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public void setProperty(T view, String propName, @Nullable Object value) {
case ViewProps.ACCESSIBILITY_STATE:
mViewManager.setViewState(view, (ReadableMap) value);
break;
case ViewProps.ACCESSIBILITY_COLLECTION_INFO:
mViewManager.setAccessibilityCollectionInfo(view, (ReadableMap) value);
break;
case ViewProps.ACCESSIBILITY_COLLECTION_ITEM_INFO:
mViewManager.setAccessibilityCollectionItemInfo(view, (ReadableMap) value);
break;
case ViewProps.BACKGROUND_COLOR:
mViewManager.setBackgroundColor(
view, value == null ? 0 : ColorPropConverter.getColor(value, view.getContext()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public interface BaseViewManagerInterface<T extends View> {
void setAccessibilityLiveRegion(T view, @Nullable String liveRegion);

void setAccessibilityRole(T view, @Nullable String accessibilityRole);

void setAccessibilityCollectionInfo(T view, @Nullable ReadableMap accessibilityCollectionInfo);

void setAccessibilityCollectionItemInfo(T view, @Nullable ReadableMap accessibilityCollectionItemInfo);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public class ViewProps {
public static final String Z_INDEX = "zIndex";
public static final String RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
public static final String ACCESSIBILITY_LABEL = "accessibilityLabel";
public static final String ACCESSIBILITY_COLLECTION_INFO = "accessibilityCollectionInfo";
public static final String ACCESSIBILITY_COLLECTION_ITEM_INFO = "accessibilityCollectionItemInfo";
public static final String ACCESSIBILITY_HINT = "accessibilityHint";
public static final String ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
public static final String ACCESSIBILITY_ROLE = "accessibilityRole";
Expand Down
8 changes: 7 additions & 1 deletion ReactAndroid/src/main/res/views/uimanager/values/ids.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
<!--tag is used to store accessibilityRole tag-->
<item type="id" name="accessibility_role"/>

<!--tag is used to store accessibilityState -->
<!--tag is used to store accessibilityCollectionInfo -->
<item type="id" name="accessibility_collection_info"/>

<!--tag is used to store accessibilityCollectionItemInfo -->
<item type="id" name="accessibility_collection_item_info"/>

<!--tag is used to store accessibilityState -->
<item type="id" name="accessibility_state"/>

<!--tag is used to store accessibilityLabel tag-->
Expand Down