Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout
import com.facebook.react.views.text.ReactTextView
import com.facebook.react.views.textinput.ReactEditText
import com.facebook.react.views.view.ReactViewGroup
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper
import com.swmansion.gesturehandler.react.events.eventbuilders.NativeGestureHandlerEventDataBuilder
import com.swmansion.gesturehandler.react.isScreenReaderOn

class NativeViewGestureHandler : GestureHandler() {
override val isContinuous = true
Expand Down Expand Up @@ -121,17 +119,6 @@ class NativeViewGestureHandler : GestureHandler() {

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
val view = view!!

val isTouchExplorationEnabled = view.context.isScreenReaderOn()

if (view is RNGestureHandlerButtonViewManager.ButtonViewGroup && isTouchExplorationEnabled) {
// Fix for: https://github.com/software-mansion/react-native-gesture-handler/issues/2808
// When TalkBack is enabled, events are often not being sent to the orchestrator for processing.
// Instead, states will be changed directly by an alternative mechanism added in this PR:
// https://github.com/software-mansion/react-native-gesture-handler/pull/2234
return
}

if (event.actionMasked == MotionEvent.ACTION_UP) {
if (state == STATE_UNDETERMINED && !hook.canBegin(event)) {
cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.core.view.children
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.facebook.react.R
import com.facebook.react.bridge.Dynamic
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.BackgroundStyleApplicator
import com.facebook.react.uimanager.LengthPercentage
Expand Down Expand Up @@ -347,6 +348,7 @@ class RNGestureHandlerButtonViewManager :
super.onAfterUpdateTransaction(view)

view.updateBackground()
view.updateLongPressAccessibility()
}

override fun getDelegate(): ViewManagerDelegate<ButtonViewGroup>? = mDelegate
Expand Down Expand Up @@ -434,6 +436,23 @@ class RNGestureHandlerButtonViewManager :
invalidate()
}

fun updateLongPressAccessibility() {
val hasLongPress = hasLongPressAccessibilityAction()
setOnLongClickListener(if (hasLongPress) dummyLongClickListener else null)
isLongClickable = hasLongPress
Comment on lines +440 to +442
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can't we just set isLongClickable and then use it?

Suggested change
val hasLongPress = hasLongPressAccessibilityAction()
setOnLongClickListener(if (hasLongPress) dummyLongClickListener else null)
isLongClickable = hasLongPress
isLongClickable = hasLongPressAccessibilityAction()
setOnLongClickListener(if (isLongClickable) dummyLongClickListener else null)

}

private fun hasLongPressAccessibilityAction(): Boolean {
val actions = getTag(R.id.accessibility_actions) as? ReadableArray ?: return false
for (i in 0 until actions.size()) {
if (actions.getMap(i)?.getString("name") == "longpress") {
return true
}
}

return false
}

override fun setBackgroundColor(color: Int) {
BackgroundStyleApplicator.setBackgroundColor(this, color)
}
Expand Down Expand Up @@ -879,7 +898,8 @@ class RNGestureHandlerButtonViewManager :
// don't preform click when a child button is pressed (mainly to prevent sound effect of
// a parent button from playing)
return if (!isChildTouched()) {
if (context.isScreenReaderOn()) {
// Don't activate native handlers when isPressed is true (motion events are passing through)
if (context.isScreenReaderOn() && !isPressed) {
RNGestureHandlerRootView.findGestureHandlerRootView(this)?.activateNativeHandlers(this)
} else if (receivedKeyEvent) {
RNGestureHandlerRootView.findGestureHandlerRootView(this)?.activateNativeHandlers(this)
Expand Down Expand Up @@ -936,7 +956,14 @@ class RNGestureHandlerButtonViewManager :
var resolveOutValue = TypedValue()
var touchResponder: ButtonViewGroup? = null
var soundResponder: ButtonViewGroup? = null
var dummyClickListener = OnClickListener { }
val dummyClickListener = OnClickListener { }
val dummyLongClickListener = OnLongClickListener { view ->
if (view.context.isScreenReaderOn()) {
view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
} else {
false
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
gestureTouchToPressableEvent,
isTouchWithinInset,
numberAsInset,
viewCenterToPressableEvent,
} from './utils';

const DEFAULT_LONG_PRESS_DURATION = 500;
Expand Down Expand Up @@ -301,6 +302,13 @@ const LegacyPressable = (props: LegacyPressableProps) => {
}
})
.onBegin(() => {
if (Platform.OS === 'android' && isScreenReaderEnabled) {
stateMachine.handleEvent(
StateMachineEvent.NATIVE_BEGIN,
viewCenterToPressableEvent(dimensions.current)
);
return;
}
stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN);
})
.onStart(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { PressableEvent } from './PressableProps';
export interface StateDefinition {
eventName: string;
callback?: (event: PressableEvent) => void;
optional?: boolean;
}

class PressableStateMachine {
Expand Down Expand Up @@ -30,9 +31,23 @@ class PressableStateMachine {
return;
}

const step = this.states[this.currentStepIndex];
this.eventPayload = eventPayload || this.eventPayload;

// Skip past optional steps that don't match the incoming event
while (
this.currentStepIndex < this.states.length &&
this.states[this.currentStepIndex].eventName !== eventName &&
this.states[this.currentStepIndex].optional
) {
this.currentStepIndex++;
}

if (this.currentStepIndex >= this.states.length) {
this.reset();
return;
}

const step = this.states[this.currentStepIndex];
if (step.eventName !== eventName) {
if (this.currentStepIndex > 0) {
// retry with position at index 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ function getAndroidAccessibilityStatesConfig(
) {
return [
{
eventName: StateMachineEvent.LONG_PRESS_TOUCHES_DOWN,
eventName: StateMachineEvent.NATIVE_BEGIN,
callback: handlePressIn,
},
{
eventName: StateMachineEvent.NATIVE_BEGIN,
eventName: StateMachineEvent.LONG_PRESS_TOUCHES_DOWN,
optional: true,
},
{
eventName: StateMachineEvent.FINALIZE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,47 @@ const gestureTouchToPressableEvent = (
};
};

const viewCenterToPressableEvent = (
dimensions: PressableDimensions
): PressableEvent => {
const timestamp = Date.now();
const targetId = 0;
const centerX = dimensions.width / 2;
const centerY = dimensions.height / 2;

const pressEvent: InnerPressableEvent = {
identifier: 0,
locationX: centerX,
locationY: centerY,
pageX: centerX,
pageY: centerY,
target: targetId,
Comment on lines +155 to +159
timestamp,
touches: [],
changedTouches: [],
};

return {
nativeEvent: {
touches: [pressEvent],
changedTouches: [pressEvent],
identifier: 0,
locationX: centerX,
locationY: centerY,
pageX: centerX,
pageY: centerY,
target: targetId,
Comment on lines +170 to +174
timestamp,
force: undefined,
},
};
};

export {
addInsets,
gestureToPressableEvent,
gestureTouchToPressableEvent,
isTouchWithinInset,
numberAsInset,
viewCenterToPressableEvent,
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
gestureTouchToPressableEvent,
isTouchWithinInset,
numberAsInset,
viewCenterToPressableEvent,
} from '../../components/Pressable/utils';
import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
import { useIsScreenReaderEnabled } from '../../useIsScreenReaderEnabled';
Expand Down Expand Up @@ -305,6 +306,13 @@ const Pressable = (props: PressableProps) => {
}
},
onBegin: () => {
if (Platform.OS === 'android' && isScreenReaderEnabled) {
stateMachine.handleEvent(
StateMachineEvent.NATIVE_BEGIN,
viewCenterToPressableEvent(dimensions.current)
);
return;
}
stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN);
},
onActivate: () => {
Expand Down
Loading