diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index c9527fb958..082bfffc42 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -706,6 +706,12 @@ open class GestureHandler Unit) { + isWithinBounds = true + closure() + isWithinBounds = false + } + fun setOnTouchEventListener(listener: OnTouchEventListener?): GestureHandler<*> { onTouchEventListener = listener return this diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt b/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt index 2407c8a271..6116617ed2 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt @@ -580,6 +580,21 @@ class GestureHandlerOrchestrator( private fun isClipping(view: View) = view !is ViewGroup || viewConfigHelper.isViewClippingChildren(view) + fun activateNativeHandlersForView(view: View) { + handlerRegistry.getHandlersForView(view)?.forEach { + if (it !is NativeViewGestureHandler) { + return@forEach + } + this.recordHandlerIfNotPresent(it, view) + + it.withMarkedAsInBounds { + it.begin() + it.activate() + it.end() + } + } + } + companion object { // The limit doesn't necessarily need to exists, it was just simpler to implement it that way // it is also more allocation-wise efficient to have a fixed limit diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt b/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt index 5f00002d18..c7791411a2 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt @@ -1,5 +1,7 @@ package com.swmansion.gesturehandler.react +import android.content.Context +import android.view.accessibility.AccessibilityManager import com.facebook.react.bridge.ReactContext import com.facebook.react.modules.core.DeviceEventManagerModule import com.facebook.react.uimanager.UIManagerModule @@ -9,3 +11,6 @@ val ReactContext.deviceEventEmitter: DeviceEventManagerModule.RCTDeviceEventEmit val ReactContext.UIManager: UIManagerModule get() = this.getNativeModule(UIManagerModule::class.java)!! + +fun Context.isScreenReaderOn() = + (getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager).isEnabled diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt index 1bec0762e8..9c9e9eeaf0 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt @@ -13,10 +13,12 @@ import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RectShape import android.os.Build import android.util.TypedValue +import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.View.OnClickListener import android.view.ViewGroup +import android.view.ViewParent import androidx.core.view.children import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.PixelUtil @@ -119,6 +121,7 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R private var needBackgroundUpdate = false private var lastEventTime = -1L private var lastAction = -1 + private var receivedKeyEvent = false var isTouched = false @@ -333,13 +336,30 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R return false } + override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + receivedKeyEvent = true + return super.onKeyUp(keyCode, event) + } + override fun performClick(): Boolean { // don't preform click when a child button is pressed (mainly to prevent sound effect of // a parent button from playing) - return if (!isChildTouched() && soundResponder == this) { - tryFreeingResponder() - soundResponder = null - super.performClick() + return if (!isChildTouched()) { + + if (context.isScreenReaderOn()) { + findGestureHandlerRootView()?.activateNativeHandlers(this) + } else if (receivedKeyEvent) { + findGestureHandlerRootView()?.activateNativeHandlers(this) + receivedKeyEvent = false + } + + if (soundResponder === this) { + tryFreeingResponder() + soundResponder = null + super.performClick() + } else { + false + } } else { false } @@ -356,7 +376,6 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R soundResponder = this } } - // button can be pressed alongside other button if both are non-exclusive and it doesn't have // any pressed children (to prevent pressing the parent when children is pressed). val canBePressedAlongsideOther = !exclusive && touchResponder?.exclusive != true && !isChildTouched() @@ -378,6 +397,20 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R // by default Viewgroup would pass hotspot change events } + private fun findGestureHandlerRootView(): RNGestureHandlerRootView? { + var parent: ViewParent? = this.parent + var gestureHandlerRootView: RNGestureHandlerRootView? = null + + while (parent != null) { + if (parent is RNGestureHandlerRootView) { + gestureHandlerRootView = parent + } + parent = parent.parent + } + + return gestureHandlerRootView + } + companion object { var resolveOutValue = TypedValue() var touchResponder: ButtonViewGroup? = null diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt index 5e8403d8a4..b20ba42229 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt @@ -3,6 +3,7 @@ package com.swmansion.gesturehandler.react import android.os.SystemClock import android.util.Log import android.view.MotionEvent +import android.view.View import android.view.ViewGroup import android.view.ViewParent import com.facebook.react.bridge.ReactContext @@ -118,6 +119,10 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: } } + fun activateNativeHandlers(view: View) { + orchestrator?.activateNativeHandlersForView(view) + } + companion object { private const val MIN_ALPHA_FOR_TOUCH = 0.1f private fun findRootViewTag(viewGroup: ViewGroup): ViewGroup { diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt index 953d3f3d74..c6c456e10c 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt @@ -3,6 +3,7 @@ package com.swmansion.gesturehandler.react import android.content.Context import android.util.Log import android.view.MotionEvent +import android.view.View import android.view.ViewGroup import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UiThreadUtil @@ -43,6 +44,10 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { super.requestDisallowInterceptTouchEvent(disallowIntercept) } + fun activateNativeHandlers(view: View) { + rootHelper?.activateNativeHandlers(view) + } + companion object { private fun hasGestureHandlerEnabledRootView(viewGroup: ViewGroup): Boolean { UiThreadUtil.assertOnUiThread()