Skip to content

Commit

Permalink
Add KComponent support for keyboard focusability of virtual views
Browse files Browse the repository at this point in the history
Summary: This does the same work as D50910356, but for KComponents.

Reviewed By: colriot

Differential Revision: D51734814

fbshipit-source-id: 27afca60e7580ee7315e3c8813609d91cd3d001f
  • Loading branch information
Brett Lavalla authored and facebook-github-bot committed Dec 7, 2023
1 parent e529701 commit ba08ada
Show file tree
Hide file tree
Showing 15 changed files with 401 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import com.facebook.litho.OnPopulateAccessibilityEventEvent
import com.facebook.litho.OnPopulateAccessibilityNodeEvent
import com.facebook.litho.OnRequestSendAccessibilityEventEvent
import com.facebook.litho.PerformAccessibilityActionEvent
import com.facebook.litho.PerformActionForVirtualViewEvent
import com.facebook.litho.SendAccessibilityEventEvent
import com.facebook.litho.SendAccessibilityEventUncheckedEvent
import com.facebook.litho.Style
import com.facebook.litho.StyleItem
import com.facebook.litho.StyleItemField
import com.facebook.litho.VirtualViewKeyboardFocusChangedEvent
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_AUTO
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_NO
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
Expand All @@ -53,6 +55,8 @@ internal enum class AccessibilityField : StyleItemField {
PERFORM_ACCESSIBILITY_ACTION,
SEND_ACCESSIBILITY_EVENT,
SEND_ACCESSIBILITY_EVENT_UNCHECKED,
ON_PERFORM_ACTION_FOR_VIRTUAL_VIEW,
ON_VIRTUAL_VIEW_KEYBOARD_FOCUS_CHANGED,
}

@PublishedApi
Expand Down Expand Up @@ -95,6 +99,12 @@ internal data class AccessibilityStyleItem(
AccessibilityField.SEND_ACCESSIBILITY_EVENT_UNCHECKED ->
commonProps.sendAccessibilityEventUncheckedHandler(
eventHandler(value as (SendAccessibilityEventUncheckedEvent) -> Unit))
AccessibilityField.ON_PERFORM_ACTION_FOR_VIRTUAL_VIEW ->
commonProps.onPerformActionForVirtualViewHandler(
eventHandler(value as (PerformActionForVirtualViewEvent) -> Unit))
AccessibilityField.ON_VIRTUAL_VIEW_KEYBOARD_FOCUS_CHANGED ->
commonProps.onVirtualViewKeyboardFocusChangedHandler(
eventHandler(value as (VirtualViewKeyboardFocusChangedEvent) -> Unit))
}
}
}
Expand Down Expand Up @@ -242,6 +252,35 @@ inline fun Style.onSendAccessibilityEventUnchecked(
AccessibilityField.SEND_ACCESSIBILITY_EVENT_UNCHECKED,
onSendAccessibilityEventUncheckedHandler)

/**
* Called when a virtual view child of the host View has changed keyboard focus and gives an
* opportunity to the parent (the host) to react (changing visual display, etc.)
*
* See [androidx.customview.widget.ExploreByTouchHelper#onVirtualViewKeyboardFocusChanged].
*/
inline fun Style.onVirtualViewKeyboardFocusChanged(
noinline onVirtualViewKeyboardFocusChangedHandler:
(VirtualViewKeyboardFocusChangedEvent) -> Unit
): Style =
this +
AccessibilityStyleItem(
AccessibilityField.ON_VIRTUAL_VIEW_KEYBOARD_FOCUS_CHANGED,
onVirtualViewKeyboardFocusChangedHandler)

/**
* Performs the specified accessibility action on a virtual view child of the host View and gives an
* opportunity to the parent (the host) to implement the desired behavior.
*
* See [androidx.customview.widget.ExploreByTouchHelper#onPerformActionForVirtualView].
*/
inline fun Style.onPerformActionForVirtualView(
noinline onPerformActionForVirtualViewHandler: (PerformActionForVirtualViewEvent) -> Unit
): Style =
this +
AccessibilityStyleItem(
AccessibilityField.ON_PERFORM_ACTION_FOR_VIRTUAL_VIEW,
onPerformActionForVirtualViewHandler)

/**
* Initializes an [AccessibilityNodeInfoCompat] with information about the host view.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.facebook.litho.OnPopulateAccessibilityEventEvent
import com.facebook.litho.OnPopulateAccessibilityNodeEvent
import com.facebook.litho.OnRequestSendAccessibilityEventEvent
import com.facebook.litho.PerformAccessibilityActionEvent
import com.facebook.litho.PerformActionForVirtualViewEvent
import com.facebook.litho.Row
import com.facebook.litho.SendAccessibilityEventEvent
import com.facebook.litho.SendAccessibilityEventUncheckedEvent
Expand Down Expand Up @@ -326,6 +327,62 @@ class AccessibilityStylesTest {
assertThat(nodeInfo?.sendAccessibilityEventUncheckedHandler).isNotNull
}

@Test
fun performActionForVirtualView_whenNotSet_isNotSetOnView() {
class TestComponent : KComponent() {
override fun ComponentScope.render(): Component? {
return Row(style = Style.width(200.px))
}
}
val testLithoView = lithoViewRule.render { TestComponent() }
val node = testLithoView.currentRootNode?.node
val nodeInfo = node?.nodeInfo
assertThat(nodeInfo?.onPerformActionForVirtualViewHandler).isNull()
}

@Test
fun performActionForVirtualView_whenSet_isSetOnView() {
val eventHandler: EventHandler<PerformActionForVirtualViewEvent> = mock()

class TestComponentWithHandler : KComponent() {
override fun ComponentScope.render(): Component? {
return Row(style = Style.onPerformActionForVirtualView { eventHandler })
}
}
val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
val node = testLithoView.currentRootNode?.node
val nodeInfo = node?.nodeInfo
assertThat(nodeInfo?.onPerformActionForVirtualViewHandler).isNotNull
}

@Test
fun virtualViewKeyboardFocusChanged_whenNotSet_isNotSetOnView() {
class TestComponent : KComponent() {
override fun ComponentScope.render(): Component? {
return Row(style = Style.width(200.px))
}
}
val testLithoView = lithoViewRule.render { TestComponent() }
val node = testLithoView.currentRootNode?.node
val nodeInfo = node?.nodeInfo
assertThat(nodeInfo?.onVirtualViewKeyboardFocusChangedHandler).isNull()
}

@Test
fun virtualViewKeyboardFocusChanged_whenSet_isSetOnView() {
val eventHandler: EventHandler<PerformActionForVirtualViewEvent> = mock()

class TestComponentWithHandler : KComponent() {
override fun ComponentScope.render(): Component? {
return Row(style = Style.onVirtualViewKeyboardFocusChanged { eventHandler })
}
}
val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
val node = testLithoView.currentRootNode?.node
val nodeInfo = node?.nodeInfo
assertThat(nodeInfo?.onVirtualViewKeyboardFocusChangedHandler).isNotNull
}

@Test
fun accessibilityRole_whenNotSet_isNotSetOnView() {
class TestComponent : KComponent() {
Expand Down
15 changes: 15 additions & 0 deletions litho-core/src/main/java/com/facebook/litho/CommonProps.java
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,21 @@ public void sendAccessibilityEventUncheckedHandler(
.setSendAccessibilityEventUncheckedHandler(sendAccessibilityEventUncheckedHandler);
}

public void onVirtualViewKeyboardFocusChangedHandler(
@Nullable
EventHandler<VirtualViewKeyboardFocusChangedEvent>
onVirtualViewKeyboardFocusChangedHandler) {
getOrCreateNodeInfo()
.setOnVirtualViewKeyboardFocusChangedHandler(onVirtualViewKeyboardFocusChangedHandler);
}

public void onPerformActionForVirtualViewHandler(
@Nullable
EventHandler<PerformActionForVirtualViewEvent> onPerformActionForVirtualViewHandler) {
getOrCreateNodeInfo()
.setOnPerformActionForVirtualViewHandler(onPerformActionForVirtualViewHandler);
}

public void scale(float scale) {
getOrCreateNodeInfo().setScale(scale);
if (scale == 1) {
Expand Down
19 changes: 19 additions & 0 deletions litho-core/src/main/java/com/facebook/litho/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,25 @@ public T sendAccessibilityEventUncheckedHandler(
return getThis();
}

public T onPerformActionForVirtualViewHandler(
@Nullable
EventHandler<PerformActionForVirtualViewEvent> onPerformActionForVirtualViewHandler) {
mComponent
.getOrCreateCommonProps()
.onPerformActionForVirtualViewHandler(onPerformActionForVirtualViewHandler);
return getThis();
}

public T onVirtualViewKeyboardFocusChangedHandler(
@Nullable
EventHandler<VirtualViewKeyboardFocusChangedEvent>
onVirtualViewKeyboardFocusChangedHandler) {
mComponent
.getOrCreateCommonProps()
.onVirtualViewKeyboardFocusChangedHandler(onVirtualViewKeyboardFocusChangedHandler);
return getThis();
}

public T shadowElevationAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return shadowElevationPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,37 @@ private void dispatchOnPopulateAccessibilityNodeEvent(
}
}

private void dispatchOnVirtualViewKeyboardFocusChangedEvent(
View host, @Nullable AccessibilityNodeInfoCompat node, int virtualViewId, boolean hasFocus) {
if (mNodeInfo != null && mNodeInfo.getOnVirtualViewKeyboardFocusChangedHandler() != null) {
EventDispatcherUtils.dispatchVirtualViewKeyboardFocusChanged(
mNodeInfo.getOnVirtualViewKeyboardFocusChangedHandler(),
host,
node,
virtualViewId,
hasFocus,
mSuperDelegate);
}
}

private boolean dispatchOnPerformActionForVirtualViewEvent(
View host,
AccessibilityNodeInfoCompat node,
int virtualViewId,
int action,
@Nullable Bundle arguments) {
if (mNodeInfo != null && mNodeInfo.getOnPerformActionForVirtualViewHandler() != null) {
return EventDispatcherUtils.dispatchPerformActionForVirtualView(
mNodeInfo.getOnPerformActionForVirtualViewHandler(),
host,
node,
virtualViewId,
action,
arguments);
}
return false;
}

@Override
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
final MountItem mountItem = getAccessibleMountItem(mView);
Expand Down Expand Up @@ -273,6 +304,7 @@ protected void onVirtualViewKeyboardFocusChanged(int virtualViewId, boolean hasF

final LithoRenderUnit renderUnit = getRenderUnit(mountItem);
if (!(renderUnit.getComponent() instanceof SpecGeneratedComponent)) {
dispatchOnVirtualViewKeyboardFocusChangedEvent(mView, node, virtualViewId, hasFocus);
return;
}
final SpecGeneratedComponent component = (SpecGeneratedComponent) renderUnit.getComponent();
Expand Down Expand Up @@ -326,7 +358,8 @@ protected boolean onPerformActionForVirtualView(

final LithoRenderUnit renderUnit = getRenderUnit(mountItem);
if (!(renderUnit.getComponent() instanceof SpecGeneratedComponent)) {
return false;
return dispatchOnPerformActionForVirtualViewEvent(
mView, node, virtualViewId, action, arguments);
}
final SpecGeneratedComponent component = (SpecGeneratedComponent) renderUnit.getComponent();
final ComponentContext scopedContext = getComponentContext(mountItem);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,34 @@ internal object EventDispatcherUtils {
sendAccessibilityEventUncheckedEvent.superDelegate = superDelegate
eventHandler.dispatchEvent(sendAccessibilityEventUncheckedEvent)
}

@JvmStatic
fun dispatchVirtualViewKeyboardFocusChanged(
eventHandler: EventHandler<VirtualViewKeyboardFocusChangedEvent>,
host: View,
nodeInfo: AccessibilityNodeInfoCompat?,
virtualViewId: Int,
hasFocus: Boolean,
superDelegate: AccessibilityDelegateCompat
) {
ThreadUtils.assertMainThread()
val event =
VirtualViewKeyboardFocusChangedEvent(host, nodeInfo, virtualViewId, hasFocus, superDelegate)
eventHandler.dispatchEvent(event)
}

@JvmStatic
fun dispatchPerformActionForVirtualView(
eventHandler: EventHandler<PerformActionForVirtualViewEvent>,
host: View,
nodeInfo: AccessibilityNodeInfoCompat,
virtualViewId: Int,
action: Int,
arguments: Bundle?
): Boolean {
ThreadUtils.assertMainThread()
val event = PerformActionForVirtualViewEvent(host, nodeInfo, virtualViewId, action, arguments)
val returnValue = eventHandler.dispatchEvent(event)
return returnValue is Boolean && returnValue
}
}
Loading

0 comments on commit ba08ada

Please sign in to comment.