AX tree expands with main tree on search (cannot search ax tree yet)
Summary: AX tree will now stay in sync with the main tree when searching. Also allows user to completely erase search (previously had left one letter highlighted in the tree even if entire query was erased). Reviewed By: danielbuechele Differential Revision: D9276721 fbshipit-source-id: 5272bb9cf3400ad3eb9d16bf438b0e5d4b551c6a
This commit is contained in:
committed by
Facebook Github Bot
parent
00847365ef
commit
656044ce69
@@ -372,7 +372,9 @@ public class InspectorSonarPlugin implements SonarPlugin {
|
|||||||
public void onReceiveOnMainThread(SonarObject params, SonarResponder responder)
|
public void onReceiveOnMainThread(SonarObject params, SonarResponder responder)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
final String query = params.getString("query");
|
final String query = params.getString("query");
|
||||||
final SearchResultNode matchTree = searchTree(query.toLowerCase(), mApplication);
|
final boolean axEnabled = params.getBoolean("axEnabled");
|
||||||
|
|
||||||
|
final SearchResultNode matchTree = searchTree(query.toLowerCase(), mApplication, axEnabled);
|
||||||
final SonarObject results = matchTree == null ? null : matchTree.toSonarObject();
|
final SonarObject results = matchTree == null ? null : matchTree.toSonarObject();
|
||||||
final SonarObject response =
|
final SonarObject response =
|
||||||
new SonarObject.Builder().put("results", results).put("query", query).build();
|
new SonarObject.Builder().put("results", results).put("query", query).build();
|
||||||
@@ -503,14 +505,19 @@ public class InspectorSonarPlugin implements SonarPlugin {
|
|||||||
descriptor.setHighlighted(obj, highlighted, isAlignmentMode);
|
descriptor.setHighlighted(obj, highlighted, isAlignmentMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchResultNode searchTree(String query, Object obj) throws Exception {
|
private boolean hasAXNode(SonarObject node) {
|
||||||
|
SonarObject extraInfo = node.getObject("extraInfo");
|
||||||
|
return extraInfo != null && extraInfo.getBoolean("hasAXNode");
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchResultNode searchTree(String query, Object obj, boolean axEnabled) throws Exception {
|
||||||
final NodeDescriptor descriptor = descriptorForObject(obj);
|
final NodeDescriptor descriptor = descriptorForObject(obj);
|
||||||
List<SearchResultNode> childTrees = null;
|
List<SearchResultNode> childTrees = null;
|
||||||
boolean isMatch = descriptor.matches(query, obj);
|
boolean isMatch = descriptor.matches(query, obj);
|
||||||
|
|
||||||
for (int i = 0; i < descriptor.getChildCount(obj); i++) {
|
for (int i = 0; i < descriptor.getChildCount(obj); i++) {
|
||||||
Object child = descriptor.getChildAt(obj, i);
|
Object child = descriptor.getChildAt(obj, i);
|
||||||
SearchResultNode childNode = searchTree(query, child);
|
SearchResultNode childNode = searchTree(query, child, axEnabled);
|
||||||
if (childNode != null) {
|
if (childNode != null) {
|
||||||
if (childTrees == null) {
|
if (childTrees == null) {
|
||||||
childTrees = new ArrayList<>();
|
childTrees = new ArrayList<>();
|
||||||
@@ -521,7 +528,8 @@ public class InspectorSonarPlugin implements SonarPlugin {
|
|||||||
|
|
||||||
if (isMatch || childTrees != null) {
|
if (isMatch || childTrees != null) {
|
||||||
final String id = trackObject(obj);
|
final String id = trackObject(obj);
|
||||||
return new SearchResultNode(id, isMatch, getNode(id), childTrees);
|
SonarObject node = getNode(id);
|
||||||
|
return new SearchResultNode(id, isMatch, node, childTrees, axEnabled && hasAXNode(node) ? getAXNode(id) : null);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,17 @@ public class SearchResultNode {
|
|||||||
private final String id;
|
private final String id;
|
||||||
private final boolean isMatch;
|
private final boolean isMatch;
|
||||||
private final SonarObject element;
|
private final SonarObject element;
|
||||||
|
private final SonarObject axElement;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final List<SearchResultNode> children;
|
private final List<SearchResultNode> children;
|
||||||
|
|
||||||
SearchResultNode(
|
SearchResultNode(
|
||||||
String id, boolean isMatch, SonarObject element, @Nullable List<SearchResultNode> children) {
|
String id, boolean isMatch, SonarObject element, List<SearchResultNode> children, SonarObject axElement) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.isMatch = isMatch;
|
this.isMatch = isMatch;
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.children = children;
|
this.children = children;
|
||||||
|
this.axElement = axElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
SonarObject toSonarObject() {
|
SonarObject toSonarObject() {
|
||||||
@@ -44,6 +46,7 @@ public class SearchResultNode {
|
|||||||
return new SonarObject.Builder()
|
return new SonarObject.Builder()
|
||||||
.put("id", this.id)
|
.put("id", this.id)
|
||||||
.put("isMatch", this.isMatch)
|
.put("isMatch", this.isMatch)
|
||||||
|
.put("axElement", this.axElement)
|
||||||
.put("element", this.element)
|
.put("element", this.element)
|
||||||
.put("children", childArray)
|
.put("children", childArray)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ public class ApplicationDescriptor extends NodeDescriptor<ApplicationWrapper> {
|
|||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SonarObject getExtraInfo(ApplicationWrapper node) {
|
||||||
|
return new SonarObject.Builder().put("hasAXNode", true).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHighlighted(ApplicationWrapper node, boolean selected, boolean isAlignmentMode) throws Exception {
|
public void setHighlighted(ApplicationWrapper node, boolean selected, boolean isAlignmentMode) throws Exception {
|
||||||
final int childCount = getChildCount(node);
|
final int childCount = getChildCount(node);
|
||||||
|
|||||||
@@ -463,7 +463,10 @@ public class ViewDescriptor extends NodeDescriptor<View> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SonarObject getExtraInfo(View node) {
|
public SonarObject getExtraInfo(View node) {
|
||||||
return new SonarObject.Builder().put("focused", AccessibilityUtil.isAXFocused(node)).build();
|
return new SonarObject.Builder()
|
||||||
|
.put("focused", AccessibilityUtil.isAXFocused(node))
|
||||||
|
.put("hasAXNode", true)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ public class AccessibilityRoleUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static AccessibilityRole getRole(View view) {
|
public static AccessibilityRole getRole(View view) {
|
||||||
|
if (view == null) {
|
||||||
|
return AccessibilityRole.NONE;
|
||||||
|
}
|
||||||
AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain();
|
AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain();
|
||||||
ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo);
|
ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo);
|
||||||
AccessibilityRole role = getRole(nodeInfo);
|
AccessibilityRole role = getRole(nodeInfo);
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ type SearchResultTree = {|
|
|||||||
isMatch: Boolean,
|
isMatch: Boolean,
|
||||||
children: ?Array<SearchResultTree>,
|
children: ?Array<SearchResultTree>,
|
||||||
element: Element,
|
element: Element,
|
||||||
|
axElement: Element,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
const LoadingSpinner = LoadingIndicator.extends({
|
const LoadingSpinner = LoadingIndicator.extends({
|
||||||
@@ -333,15 +334,17 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
search(query: string) {
|
search(query: string) {
|
||||||
if (!query) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
outstandingSearchQuery: query,
|
outstandingSearchQuery: query,
|
||||||
});
|
});
|
||||||
this.client
|
|
||||||
.call('getSearchResults', {query: query})
|
if (!query) {
|
||||||
.then(response => this.displaySearchResults(response));
|
this.displaySearchResults({query: '', results: null});
|
||||||
|
} else {
|
||||||
|
this.client
|
||||||
|
.call('getSearchResults', {query: query, axEnabled: this.axEnabled()})
|
||||||
|
.then(response => this.displaySearchResults(response));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executeCommand(command: string) {
|
executeCommand(command: string) {
|
||||||
@@ -391,7 +394,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
|||||||
results,
|
results,
|
||||||
query,
|
query,
|
||||||
}: {
|
}: {
|
||||||
results: SearchResultTree,
|
results: ?SearchResultTree,
|
||||||
query: string,
|
query: string,
|
||||||
}) {
|
}) {
|
||||||
const elements = this.getElementsFromSearchResultTree(results);
|
const elements = this.getElementsFromSearchResultTree(results);
|
||||||
@@ -405,10 +408,29 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
|||||||
elements: elements.map(x => x.element),
|
elements: elements.map(x => x.element),
|
||||||
type: 'UpdateElements',
|
type: 'UpdateElements',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dispatchAction({
|
this.dispatchAction({
|
||||||
elements: idsToExpand,
|
elements: idsToExpand,
|
||||||
type: 'ExpandElements',
|
type: 'ExpandElements',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.axEnabled()) {
|
||||||
|
const AXelements = elements.filter(x => x.axElement);
|
||||||
|
const AXidsToExpand = AXelements.filter(x => x.hasChildren).map(
|
||||||
|
x => x.axElement.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.dispatchAction({
|
||||||
|
elements: AXelements.map(x => x.axElement),
|
||||||
|
type: 'UpdateAXElements',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dispatchAction({
|
||||||
|
elements: AXidsToExpand,
|
||||||
|
type: 'ExpandAXElements',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
searchResults: {
|
searchResults: {
|
||||||
matches: new Set(
|
matches: new Set(
|
||||||
@@ -422,7 +444,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getElementsFromSearchResultTree(tree: SearchResultTree) {
|
getElementsFromSearchResultTree(tree: ?SearchResultTree) {
|
||||||
if (!tree) {
|
if (!tree) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -432,6 +454,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
|||||||
isMatch: tree.isMatch,
|
isMatch: tree.isMatch,
|
||||||
hasChildren: Boolean(tree.children),
|
hasChildren: Boolean(tree.children),
|
||||||
element: tree.element,
|
element: tree.element,
|
||||||
|
axElement: tree.axElement,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (tree.children) {
|
if (tree.children) {
|
||||||
@@ -842,12 +865,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
|||||||
|
|
||||||
onElementSelected = debounce((selectedKey: ElementID) => {
|
onElementSelected = debounce((selectedKey: ElementID) => {
|
||||||
const {key, AXkey} = this.getKeysFromSelected(selectedKey);
|
const {key, AXkey} = this.getKeysFromSelected(selectedKey);
|
||||||
|
this.dispatchAction({key, AXkey, type: 'SelectElement'});
|
||||||
this.dispatchAction({
|
|
||||||
key: key,
|
|
||||||
AXkey: AXkey,
|
|
||||||
type: 'SelectElement',
|
|
||||||
});
|
|
||||||
|
|
||||||
this.client.send('setHighlighted', {
|
this.client.send('setHighlighted', {
|
||||||
id: selectedKey,
|
id: selectedKey,
|
||||||
|
|||||||
Reference in New Issue
Block a user