react-native-gesture-handler 2.4.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. package/README.md +3 -2
  2. package/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandler.kt +9 -5
  3. package/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandlerOrchestrator.kt +6 -1
  4. package/android/lib/src/main/java/com/swmansion/gesturehandler/NativeViewGestureHandler.kt +103 -22
  5. package/android/lib/src/main/java/com/swmansion/gesturehandler/PanGestureHandler.kt +29 -2
  6. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +74 -84
  7. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +4 -0
  8. package/android/src/main/jni/Android.mk +1 -2
  9. package/android/src/paper/java/com/facebook/react/viewmanagers/RNGestureHandlerButtonManagerDelegate.java +12 -9
  10. package/android/src/paper/java/com/facebook/react/viewmanagers/RNGestureHandlerButtonManagerInterface.java +1 -0
  11. package/ios/Handlers/RNFlingHandler.m +43 -1
  12. package/ios/Handlers/{RNNativeViewHandler.m → RNNativeViewHandler.mm} +13 -1
  13. package/ios/Handlers/RNPanHandler.m +27 -0
  14. package/ios/RNGestureHandler.h +1 -0
  15. package/ios/RNGestureHandler.m +22 -4
  16. package/ios/RNGestureHandlerManager.mm +10 -2
  17. package/ios/RNGestureHandlerModule.mm +4 -1
  18. package/ios/RNManualActivationRecognizer.m +10 -3
  19. package/ios/RNRootViewGestureRecognizer.m +12 -1
  20. package/lib/commonjs/RNGestureHandlerModule.macos.js +81 -0
  21. package/lib/commonjs/RNGestureHandlerModule.macos.js.map +1 -0
  22. package/lib/commonjs/components/GestureButtons.js.map +1 -1
  23. package/lib/commonjs/components/touchables/GenericTouchable.js +4 -1
  24. package/lib/commonjs/components/touchables/GenericTouchable.js.map +1 -1
  25. package/lib/commonjs/fabric/RNGestureHandlerButtonNativeComponent.js.map +1 -1
  26. package/lib/commonjs/handlers/PanGestureHandler.js +1 -1
  27. package/lib/commonjs/handlers/PanGestureHandler.js.map +1 -1
  28. package/lib/commonjs/handlers/PressabilityDebugView.js +14 -0
  29. package/lib/commonjs/handlers/PressabilityDebugView.js.map +1 -0
  30. package/lib/commonjs/handlers/PressabilityDebugView.web.js +12 -0
  31. package/lib/commonjs/handlers/PressabilityDebugView.web.js.map +1 -0
  32. package/lib/commonjs/handlers/createHandler.js +6 -3
  33. package/lib/commonjs/handlers/createHandler.js.map +1 -1
  34. package/lib/commonjs/handlers/gestures/gestureStateManager.js +13 -9
  35. package/lib/commonjs/handlers/gestures/gestureStateManager.js.map +1 -1
  36. package/lib/commonjs/handlers/gestures/panGesture.js +5 -0
  37. package/lib/commonjs/handlers/gestures/panGesture.js.map +1 -1
  38. package/lib/commonjs/utils.js +6 -3
  39. package/lib/commonjs/utils.js.map +1 -1
  40. package/lib/module/RNGestureHandlerModule.macos.js +57 -0
  41. package/lib/module/RNGestureHandlerModule.macos.js.map +1 -0
  42. package/lib/module/components/GestureButtons.js.map +1 -1
  43. package/lib/module/components/touchables/GenericTouchable.js +4 -1
  44. package/lib/module/components/touchables/GenericTouchable.js.map +1 -1
  45. package/lib/module/fabric/RNGestureHandlerButtonNativeComponent.js.map +1 -1
  46. package/lib/module/handlers/PanGestureHandler.js +1 -1
  47. package/lib/module/handlers/PanGestureHandler.js.map +1 -1
  48. package/lib/module/handlers/PressabilityDebugView.js +3 -0
  49. package/lib/module/handlers/PressabilityDebugView.js.map +1 -0
  50. package/lib/module/handlers/PressabilityDebugView.web.js +5 -0
  51. package/lib/module/handlers/PressabilityDebugView.web.js.map +1 -0
  52. package/lib/module/handlers/createHandler.js +6 -4
  53. package/lib/module/handlers/createHandler.js.map +1 -1
  54. package/lib/module/handlers/gestures/gestureStateManager.js +13 -9
  55. package/lib/module/handlers/gestures/gestureStateManager.js.map +1 -1
  56. package/lib/module/handlers/gestures/panGesture.js +5 -0
  57. package/lib/module/handlers/gestures/panGesture.js.map +1 -1
  58. package/lib/module/utils.js +2 -1
  59. package/lib/module/utils.js.map +1 -1
  60. package/lib/typescript/RNGestureHandlerModule.macos.d.ts +34 -0
  61. package/lib/typescript/RNGestureHandlerModule.web.d.ts +1 -1
  62. package/lib/typescript/components/GestureButtons.d.ts +6 -0
  63. package/lib/typescript/fabric/RNGestureHandlerButtonNativeComponent.d.ts +1 -0
  64. package/lib/typescript/handlers/PanGestureHandler.d.ts +2 -1
  65. package/lib/typescript/handlers/PressabilityDebugView.d.ts +1 -0
  66. package/lib/typescript/handlers/PressabilityDebugView.web.d.ts +1 -0
  67. package/lib/typescript/handlers/gestures/panGesture.d.ts +1 -0
  68. package/lib/typescript/web/NodeManager.d.ts +2 -2
  69. package/package.json +1 -1
  70. package/src/RNGestureHandlerModule.macos.ts +62 -0
  71. package/src/components/GestureButtons.tsx +7 -0
  72. package/src/components/touchables/GenericTouchable.tsx +1 -0
  73. package/src/fabric/RNGestureHandlerButtonNativeComponent.ts +1 -0
  74. package/src/handlers/PanGestureHandler.ts +2 -0
  75. package/src/handlers/PressabilityDebugView.tsx +2 -0
  76. package/src/handlers/PressabilityDebugView.web.tsx +4 -0
  77. package/src/handlers/{createHandler.ts → createHandler.tsx} +7 -6
  78. package/src/handlers/gestures/gestureStateManager.ts +13 -8
  79. package/src/handlers/gestures/panGesture.ts +5 -0
  80. package/src/utils.ts +3 -1
  81. package/ios/RNGestureHandler.xcodeproj/project.xcworkspace/xcuserdata/jakubpiasecki.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  82. package/ios/RNGestureHandler.xcodeproj/xcuserdata/jakubpiasecki.xcuserdatad/xcschemes/xcschememanagement.plist +0 -19
package/README.md CHANGED
@@ -24,7 +24,7 @@ Check out our dedicated documentation page for info about this library, API refe
24
24
  If you want to play with the API but don't feel like trying it on a real app, you can run the example project. Clone the repo, go to the `example` folder and run:
25
25
 
26
26
  ```bash
27
- yarn install
27
+ yarn install
28
28
  ```
29
29
 
30
30
  If you are running on ios, run `pod install` in the ios folder
@@ -44,7 +44,8 @@ You will need to have an Android or iOS device or emulator connected as well as
44
44
  | <1.1.0 | 0.50.0+ |
45
45
 
46
46
  It may be possible to use newer versions of react-native-gesture-handler on React Native with version <= 0.59 by reverse Jetifying.
47
- Read more on that here https://github.com/mikehardy/jetifier#to-reverse-jetify--convert-node_modules-dependencies-to-support-libraries
47
+ Read more on that here <https://github.com/mikehardy/jetifier#to-reverse-jetify--convert-node_modules-dependencies-to-support-libraries>
48
+
48
49
  ## License
49
50
 
50
51
  Gesture handler library is licensed under [The MIT License](LICENSE).
@@ -172,8 +172,12 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
172
172
  windowOffset[0] = 0
173
173
  windowOffset[1] = 0
174
174
  }
175
+
176
+ onPrepare()
175
177
  }
176
178
 
179
+ protected open fun onPrepare() {}
180
+
177
181
  private fun getWindow(context: Context?): Window? {
178
182
  if (context == null) return null
179
183
  if (context is Activity) return context.window
@@ -253,9 +257,9 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
253
257
  }
254
258
  initPointerProps(trackedPointersIDsCount)
255
259
  var count = 0
256
- val oldX = event.x
257
- val oldY = event.y
258
- event.setLocation(event.rawX, event.rawY)
260
+ val deltaX = event.rawX - event.x
261
+ val deltaY = event.rawY - event.y
262
+ event.offsetLocation(deltaX, deltaY)
259
263
  var index = 0
260
264
  val size = event.pointerCount
261
265
  while (index < size) {
@@ -298,8 +302,8 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
298
302
  } catch (e: IllegalArgumentException) {
299
303
  throw AdaptEventException(this, event, e)
300
304
  }
301
- event.setLocation(oldX, oldY)
302
- result.setLocation(oldX, oldY)
305
+ event.offsetLocation(-deltaX, -deltaY)
306
+ result.offsetLocation(-deltaX, -deltaY)
303
307
  return result
304
308
  }
305
309
 
@@ -475,7 +475,12 @@ class GestureHandlerOrchestrator(
475
475
  PointerEventsConfig.BOX_NONE -> {
476
476
  // This view can't be the target, but its children might
477
477
  if (view is ViewGroup) {
478
- extractGestureHandlers(view, coords, pointerId)
478
+ extractGestureHandlers(view, coords, pointerId).also { found ->
479
+ // A child view is handling touch, also extract handlers attached to this view
480
+ if (found) {
481
+ recordViewHandlersForPointer(view, coords, pointerId)
482
+ }
483
+ }
479
484
  } else false
480
485
  }
481
486
  PointerEventsConfig.AUTO -> {
@@ -3,13 +3,16 @@ package com.swmansion.gesturehandler
3
3
  import android.os.SystemClock
4
4
  import android.view.MotionEvent
5
5
  import android.view.View
6
+ import android.view.ViewConfiguration
6
7
  import android.view.ViewGroup
7
- import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
8
+ import com.facebook.react.views.textinput.ReactEditText
8
9
 
9
10
  class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
10
11
  private var shouldActivateOnStart = false
11
12
  private var disallowInterruption = false
12
13
 
14
+ private var hook: NativeViewGestureHandlerHook = defaultHook
15
+
13
16
  init {
14
17
  setShouldCancelWhenOutside(true)
15
18
  }
@@ -34,13 +37,17 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
34
37
  }
35
38
 
36
39
  override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean {
40
+ // if the gesture is marked by user as simultaneous with other or the hook return true
41
+ if (super.shouldRecognizeSimultaneously(handler) || hook.shouldRecognizeSimultaneously(handler)) {
42
+ return true
43
+ }
44
+
37
45
  if (handler is NativeViewGestureHandler) {
38
46
  // Special case when the peer handler is also an instance of NativeViewGestureHandler:
39
47
  // For the `disallowInterruption` to work correctly we need to check the property when
40
48
  // accessed as a peer, because simultaneous recognizers can be set on either side of the
41
49
  // connection.
42
- val nativeWrapper = handler
43
- if (nativeWrapper.state == STATE_ACTIVE && nativeWrapper.disallowInterruption) {
50
+ if (handler.state == STATE_ACTIVE && handler.disallowInterruption) {
44
51
  // other handler is active and it disallows interruption, we don't want to get into its way
45
52
  return false
46
53
  }
@@ -52,7 +59,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
52
59
  // as it means the other handler has turned active and returning `true` would prevent it from
53
60
  // interrupting the current handler
54
61
  false
55
- } else state == STATE_ACTIVE && canBeInterrupted
62
+ } else state == STATE_ACTIVE && canBeInterrupted && (!hook.shouldCancelRootViewGestureHandlerIfNecessary() || handler.tag > 0)
56
63
  // otherwise we can only return `true` if already in an active state
57
64
  }
58
65
 
@@ -60,19 +67,10 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
60
67
  return !disallowInterruption
61
68
  }
62
69
 
63
- private fun canStart(): Boolean {
64
- val view = view
65
- if (view is StateChangeHook) {
66
- return view.canStart()
67
- }
68
-
69
- return true
70
- }
71
-
72
- private fun afterGestureEnd() {
73
- val view = view
74
- if (view is StateChangeHook) {
75
- view.afterGestureEnd()
70
+ override fun onPrepare() {
71
+ when (val view = view) {
72
+ is NativeViewGestureHandlerHook -> this.hook = view
73
+ is ReactEditText -> this.hook = EditTextHook(this, view)
76
74
  }
77
75
  }
78
76
 
@@ -84,7 +82,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
84
82
  activate()
85
83
  }
86
84
  end()
87
- afterGestureEnd()
85
+ hook.afterGestureEnd(event)
88
86
  } else if (state == STATE_UNDETERMINED || state == STATE_BEGAN) {
89
87
  when {
90
88
  shouldActivateOnStart -> {
@@ -96,8 +94,11 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
96
94
  view.onTouchEvent(event)
97
95
  activate()
98
96
  }
97
+ hook.wantsToHandleEventBeforeActivation() -> {
98
+ hook.handleEventBeforeActivation(event)
99
+ }
99
100
  state != STATE_BEGAN -> {
100
- if (canStart()) {
101
+ if (hook.canBegin()) {
101
102
  begin()
102
103
  } else {
103
104
  cancel()
@@ -117,13 +118,93 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
117
118
  view!!.onTouchEvent(event)
118
119
  }
119
120
 
121
+ override fun onReset() {
122
+ this.hook = defaultHook
123
+ }
124
+
120
125
  companion object {
121
126
  private fun tryIntercept(view: View, event: MotionEvent) =
122
127
  view is ViewGroup && view.onInterceptTouchEvent(event)
128
+
129
+ private val defaultHook = object : NativeViewGestureHandlerHook {}
123
130
  }
124
131
 
125
- interface StateChangeHook {
126
- fun canStart(): Boolean
127
- fun afterGestureEnd()
132
+ interface NativeViewGestureHandlerHook {
133
+ /**
134
+ * Called when gesture is in the UNDETERMINED state, shouldActivateOnStart is set to false,
135
+ * and both tryIntercept and wantsToHandleEventBeforeActivation returned false.
136
+ *
137
+ * @return Boolean value signalling whether the handler can transition to the BEGAN state. If false
138
+ * the gesture will be cancelled.
139
+ */
140
+ fun canBegin() = true
141
+
142
+ /**
143
+ * Called after the gesture transitions to the END state.
144
+ */
145
+ fun afterGestureEnd(event: MotionEvent) = Unit
146
+
147
+ /**
148
+ * @return Boolean value signalling whether the gesture can be recognized simultaneously with
149
+ * other (handler). Returning false doesn't necessarily prevent it from happening.
150
+ */
151
+ fun shouldRecognizeSimultaneously(handler: GestureHandler<*>) = false
152
+
153
+ /**
154
+ * shouldActivateOnStart and tryIntercept have priority over this method
155
+ *
156
+ * @return Boolean value signalling if the hook wants to handle events passed to the handler
157
+ * before it activates (after that the events are passed to the underlying view).
158
+ */
159
+ fun wantsToHandleEventBeforeActivation() = false
160
+
161
+ /**
162
+ * Will be called with events if wantsToHandleEventBeforeActivation returns true.
163
+ */
164
+ fun handleEventBeforeActivation(event: MotionEvent) = Unit
165
+
166
+ /**
167
+ * @return Boolean value indicating whether the RootViewGestureHandler should be cancelled
168
+ * by this one.
169
+ */
170
+ fun shouldCancelRootViewGestureHandlerIfNecessary() = false
171
+ }
172
+
173
+ private class EditTextHook(
174
+ private val handler: NativeViewGestureHandler,
175
+ private val editText: ReactEditText
176
+ ) : NativeViewGestureHandlerHook {
177
+ private var startX = 0f
178
+ private var startY = 0f
179
+ private var touchSlopSquared: Int
180
+
181
+ init {
182
+ val vc = ViewConfiguration.get(editText.context)
183
+ touchSlopSquared = vc.scaledTouchSlop * vc.scaledTouchSlop
184
+ }
185
+
186
+ override fun afterGestureEnd(event: MotionEvent) {
187
+ if ((event.x - startX) * (event.x - startX) + (event.y - startY) * (event.y - startY) < touchSlopSquared) {
188
+ editText.requestFocusFromJS()
189
+ }
190
+ }
191
+
192
+ // recognize alongside every handler besides RootViewGestureHandler, which is a private inner class
193
+ // of RNGestureHandlerRootHelper so no explicit type checks, but its tag is always negative
194
+ // also if other handler is NativeViewGestureHandler then don't override the default implementation
195
+ override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>) =
196
+ handler.tag > 0 && handler !is NativeViewGestureHandler
197
+
198
+ override fun wantsToHandleEventBeforeActivation() = true
199
+
200
+ override fun handleEventBeforeActivation(event: MotionEvent) {
201
+ handler.activate()
202
+ editText.onTouchEvent(event)
203
+
204
+ startX = event.x
205
+ startY = event.y
206
+ }
207
+
208
+ override fun shouldCancelRootViewGestureHandlerIfNecessary() = true
128
209
  }
129
210
  }
@@ -1,6 +1,7 @@
1
1
  package com.swmansion.gesturehandler
2
2
 
3
3
  import android.content.Context
4
+ import android.os.Handler
4
5
  import android.view.MotionEvent
5
6
  import android.view.VelocityTracker
6
7
  import android.view.ViewConfiguration
@@ -40,6 +41,9 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
40
41
  private var lastY = 0f
41
42
  private var velocityTracker: VelocityTracker? = null
42
43
  private var averageTouches = false
44
+ private var activateAfterLongPress = DEFAULT_ACTIVATE_AFTER_LONG_PRESS
45
+ private val activateDelayed = Runnable { activate() }
46
+ private var handler: Handler? = null
43
47
 
44
48
  /**
45
49
  * On Android when there are multiple pointers on the screen pan gestures most often just consider
@@ -54,7 +58,7 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
54
58
  * position of all the fingers will remain still while doing a rotation gesture.
55
59
  */
56
60
  init {
57
- val vc = ViewConfiguration.get(context)
61
+ val vc = ViewConfiguration.get(context!!)
58
62
  val touchSlop = vc.scaledTouchSlop
59
63
  defaultMinDistSq = (touchSlop * touchSlop).toFloat()
60
64
  minDistSq = defaultMinDistSq
@@ -76,6 +80,7 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
76
80
  minDistSq = defaultMinDistSq
77
81
  minPointers = DEFAULT_MIN_POINTERS
78
82
  maxPointers = DEFAULT_MAX_POINTERS
83
+ activateAfterLongPress = DEFAULT_ACTIVATE_AFTER_LONG_PRESS
79
84
  averageTouches = false
80
85
  }
81
86
 
@@ -127,6 +132,10 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
127
132
  this.averageTouches = averageTouches
128
133
  }
129
134
 
135
+ fun setActivateAfterLongPress(time: Long) = apply {
136
+ this.activateAfterLongPress = time
137
+ }
138
+
130
139
  /**
131
140
  * @param minVelocity in pixels per second
132
141
  */
@@ -177,13 +186,18 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
177
186
 
178
187
  private fun shouldFail(): Boolean {
179
188
  val dx = lastX - startX + offsetX
189
+ val dy = lastY - startY + offsetY
190
+
191
+ if (activateAfterLongPress > 0 && dx * dx + dy * dy > defaultMinDistSq) {
192
+ handler?.removeCallbacksAndMessages(null)
193
+ return true
194
+ }
180
195
  if (failOffsetXStart != MAX_VALUE_IGNORE && dx < failOffsetXStart) {
181
196
  return true
182
197
  }
183
198
  if (failOffsetXEnd != MIN_VALUE_IGNORE && dx > failOffsetXEnd) {
184
199
  return true
185
200
  }
186
- val dy = lastY - startY + offsetY
187
201
  if (failOffsetYStart != MAX_VALUE_IGNORE && dy < failOffsetYStart) {
188
202
  return true
189
203
  }
@@ -216,6 +230,13 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
216
230
  velocityTracker = VelocityTracker.obtain()
217
231
  addVelocityMovement(velocityTracker, event)
218
232
  begin()
233
+
234
+ if (activateAfterLongPress > 0) {
235
+ if (handler == null) {
236
+ handler = Handler()
237
+ }
238
+ handler!!.postDelayed(activateDelayed, activateAfterLongPress)
239
+ }
219
240
  } else if (velocityTracker != null) {
220
241
  addVelocityMovement(velocityTracker, event)
221
242
  velocityTracker!!.computeCurrentVelocity(1000)
@@ -257,7 +278,12 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
257
278
  super.activate(force)
258
279
  }
259
280
 
281
+ override fun onCancel() {
282
+ handler?.removeCallbacksAndMessages(null)
283
+ }
284
+
260
285
  override fun onReset() {
286
+ handler?.removeCallbacksAndMessages(null)
261
287
  velocityTracker?.let {
262
288
  it.recycle()
263
289
  velocityTracker = null
@@ -274,6 +300,7 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
274
300
  private const val MAX_VALUE_IGNORE = Float.MIN_VALUE
275
301
  private const val DEFAULT_MIN_POINTERS = 1
276
302
  private const val DEFAULT_MAX_POINTERS = 10
303
+ private const val DEFAULT_ACTIVATE_AFTER_LONG_PRESS = 0L
277
304
 
278
305
  /**
279
306
  * This method adds movement to {@class VelocityTracker} first resetting offset of the event so
@@ -9,6 +9,8 @@ import android.graphics.drawable.Drawable
9
9
  import android.graphics.drawable.LayerDrawable
10
10
  import android.graphics.drawable.PaintDrawable
11
11
  import android.graphics.drawable.RippleDrawable
12
+ import android.graphics.drawable.ShapeDrawable
13
+ import android.graphics.drawable.shapes.RectShape
12
14
  import android.os.Build
13
15
  import android.util.TypedValue
14
16
  import android.view.MotionEvent
@@ -16,7 +18,6 @@ import android.view.View
16
18
  import android.view.View.OnClickListener
17
19
  import android.view.ViewGroup
18
20
  import androidx.core.view.children
19
- import com.facebook.react.bridge.SoftAssertions
20
21
  import com.facebook.react.module.annotations.ReactModule
21
22
  import com.facebook.react.uimanager.PixelUtil
22
23
  import com.facebook.react.uimanager.ThemedReactContext
@@ -77,6 +78,11 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
77
78
  view.exclusive = exclusive
78
79
  }
79
80
 
81
+ @ReactProp(name = "touchSoundDisabled")
82
+ override fun setTouchSoundDisabled(view: ButtonViewGroup, touchSoundDisabled: Boolean) {
83
+ view.isSoundEffectsEnabled = !touchSoundDisabled
84
+ }
85
+
80
86
  override fun onAfterUpdateTransaction(view: ButtonViewGroup) {
81
87
  view.updateBackground()
82
88
  }
@@ -86,7 +92,7 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
86
92
  }
87
93
 
88
94
  class ButtonViewGroup(context: Context?) : ViewGroup(context),
89
- NativeViewGestureHandler.StateChangeHook {
95
+ NativeViewGestureHandler.NativeViewGestureHandlerHook {
90
96
  // Using object because of handling null representing no value set.
91
97
  var rippleColor: Int? = null
92
98
  set(color) = withBackgroundUpdate {
@@ -132,30 +138,6 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
132
138
  _backgroundColor = color
133
139
  }
134
140
 
135
- private fun applyRippleEffectWhenNeeded(selectable: Drawable): Drawable {
136
- val rippleColor = rippleColor
137
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && selectable is RippleDrawable) {
138
- val states = arrayOf(intArrayOf(android.R.attr.state_enabled))
139
- val colorStateList = if (rippleColor != null) {
140
- val colors = intArrayOf(rippleColor)
141
- ColorStateList(states, colors)
142
- } else {
143
- // if rippleColor is null, reapply the default color
144
- context.theme.resolveAttribute(android.R.attr.colorControlHighlight, resolveOutValue, true)
145
- val colors = intArrayOf(resolveOutValue.data)
146
- ColorStateList(states, colors)
147
- }
148
-
149
- selectable.setColor(colorStateList)
150
- }
151
-
152
- val rippleRadius = rippleRadius
153
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && rippleRadius != null && selectable is RippleDrawable) {
154
- selectable.radius = PixelUtil.toPixelFromDIP(rippleRadius.toFloat()).toInt()
155
- }
156
- return selectable
157
- }
158
-
159
141
  override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
160
142
  if (super.onInterceptTouchEvent(ev)) {
161
143
  return true
@@ -210,49 +192,74 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
210
192
  // reset foreground
211
193
  foreground = null
212
194
  }
195
+
196
+ val selectable = createSelectableDrawable()
197
+
198
+ if (borderRadius != 0f) {
199
+ // Radius-connected lines below ought to be considered
200
+ // as a temporary solution. It do not allow to set
201
+ // different radius on each corner. However, I suppose it's fairly
202
+ // fine for button-related use cases.
203
+ // Therefore it might be used as long as:
204
+ // 1. ReactViewManager is not a generic class with a possibility to handle another ViewGroup
205
+ // 2. There's no way to force native behavior of ReactViewGroup's superclass's onTouchEvent
206
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && selectable is RippleDrawable) {
207
+ val mask = PaintDrawable(Color.WHITE)
208
+ mask.setCornerRadius(borderRadius)
209
+ selectable.setDrawableByLayerId(android.R.id.mask, mask)
210
+ }
211
+ }
212
+
213
213
  if (useDrawableOnForeground && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
214
- foreground = applyRippleEffectWhenNeeded(createSelectableDrawable())
214
+ foreground = selectable
215
215
  if (_backgroundColor != Color.TRANSPARENT) {
216
216
  setBackgroundColor(_backgroundColor)
217
217
  }
218
218
  } else if (_backgroundColor == Color.TRANSPARENT && rippleColor == null) {
219
- background = createSelectableDrawable()
219
+ background = selectable
220
220
  } else {
221
221
  val colorDrawable = PaintDrawable(_backgroundColor)
222
- val selectable = createSelectableDrawable()
222
+
223
223
  if (borderRadius != 0f) {
224
- // Radius-connected lines below ought to be considered
225
- // as a temporary solution. It do not allow to set
226
- // different radius on each corner. However, I suppose it's fairly
227
- // fine for button-related use cases.
228
- // Therefore it might be used as long as:
229
- // 1. ReactViewManager is not a generic class with a possibility to handle another ViewGroup
230
- // 2. There's no way to force native behavior of ReactViewGroup's superclass's onTouchEvent
231
224
  colorDrawable.setCornerRadius(borderRadius)
232
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
233
- && selectable is RippleDrawable) {
234
- val mask = PaintDrawable(Color.WHITE)
235
- mask.setCornerRadius(borderRadius)
236
- selectable.setDrawableByLayerId(android.R.id.mask, mask)
237
- }
238
225
  }
239
- applyRippleEffectWhenNeeded(selectable)
226
+
240
227
  val layerDrawable = LayerDrawable(arrayOf(colorDrawable, selectable))
241
228
  background = layerDrawable
242
229
  }
243
230
  }
244
231
 
245
232
  private fun createSelectableDrawable(): Drawable {
246
- val version = Build.VERSION.SDK_INT
247
- val identifier = if (useBorderlessDrawable && version >= 21) SELECTABLE_ITEM_BACKGROUND_BORDERLESS else SELECTABLE_ITEM_BACKGROUND
248
- val attrID = getAttrId(context, identifier)
249
- context.theme.resolveAttribute(attrID, resolveOutValue, true)
250
- return if (version >= 21) {
251
- resources.getDrawable(resolveOutValue.resourceId, context.theme)
252
- } else {
233
+ // TODO: remove once support for RN 0.63 is dropped, since 0.64 minSdkVersion is 21
234
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
235
+ context.theme.resolveAttribute(android.R.attr.selectableItemBackground, resolveOutValue, true)
253
236
  @Suppress("Deprecation")
254
- resources.getDrawable(resolveOutValue.resourceId)
237
+ return resources.getDrawable(resolveOutValue.resourceId)
238
+ }
239
+
240
+ val states = arrayOf(intArrayOf(android.R.attr.state_enabled))
241
+ val rippleRadius = rippleRadius
242
+ val colorStateList = if (rippleColor != null) {
243
+ val colors = intArrayOf(rippleColor!!)
244
+ ColorStateList(states, colors)
245
+ } else {
246
+ // if rippleColor is null, reapply the default color
247
+ context.theme.resolveAttribute(android.R.attr.colorControlHighlight, resolveOutValue, true)
248
+ val colors = intArrayOf(resolveOutValue.data)
249
+ ColorStateList(states, colors)
250
+ }
251
+
252
+ val drawable = RippleDrawable(
253
+ colorStateList,
254
+ null,
255
+ if (useBorderlessDrawable) null else ShapeDrawable(RectShape())
256
+ )
257
+
258
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && rippleRadius != null) {
259
+ drawable.radius = PixelUtil.toPixelFromDIP(rippleRadius.toFloat()).toInt()
255
260
  }
261
+
262
+ return drawable
256
263
  }
257
264
 
258
265
  override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
@@ -265,7 +272,7 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
265
272
  }
266
273
  }
267
274
 
268
- override fun canStart(): Boolean {
275
+ override fun canBegin(): Boolean {
269
276
  val isResponder = tryGrabbingResponder()
270
277
  if (isResponder) {
271
278
  isTouched = true
@@ -273,11 +280,6 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
273
280
  return isResponder
274
281
  }
275
282
 
276
- override fun afterGestureEnd() {
277
- tryFreeingResponder()
278
- isTouched = false
279
- }
280
-
281
283
  private fun tryGrabbingResponder(): Boolean {
282
284
  if (isChildTouched()) {
283
285
  return false
@@ -294,24 +296,30 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
294
296
  }
295
297
  }
296
298
 
297
- private fun tryFreeingResponder() {
298
- if (responder === this) {
299
- responder = null
300
- }
301
- }
302
-
303
299
  private fun isChildTouched(children: Sequence<View> = this.children): Boolean {
304
300
  for (child in children) {
305
301
  if (child is ButtonViewGroup && (child.isTouched || child.isPressed)) {
306
302
  return true
307
303
  } else if (child is ViewGroup) {
308
- return isChildTouched(child.children)
304
+ if (isChildTouched(child.children)) {
305
+ return true
306
+ }
309
307
  }
310
308
  }
311
309
 
312
310
  return false
313
311
  }
314
312
 
313
+ override fun performClick(): Boolean {
314
+ // don't preform click when a child button is pressed (mainly to prevent sound effect of
315
+ // a parent button from playing)
316
+ return if (!isChildTouched()) {
317
+ super.performClick()
318
+ } else {
319
+ false
320
+ }
321
+ }
322
+
315
323
  override fun setPressed(pressed: Boolean) {
316
324
  // there is a possibility of this method being called before NativeViewGestureHandler has
317
325
  // opportunity to call canStart, in that case we need to grab responder in case the gesture
@@ -329,12 +337,13 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
329
337
  if (!pressed || responder === this || canBePressedAlongsideOther) {
330
338
  // we set pressed state only for current responder or any non-exclusive button when responder
331
339
  // is null or non-exclusive, assuming it doesn't have pressed children
332
- super.setPressed(pressed)
333
340
  isTouched = pressed
341
+ super.setPressed(pressed)
334
342
  }
335
343
  if (!pressed && responder === this) {
336
344
  // if the responder is no longer pressed we release button responder
337
345
  responder = null
346
+ isTouched = false
338
347
  }
339
348
  }
340
349
 
@@ -344,28 +353,9 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager<ButtonViewGroup>(), R
344
353
  }
345
354
 
346
355
  companion object {
347
- const val SELECTABLE_ITEM_BACKGROUND = "selectableItemBackground"
348
- const val SELECTABLE_ITEM_BACKGROUND_BORDERLESS = "selectableItemBackgroundBorderless"
349
-
350
356
  var resolveOutValue = TypedValue()
351
357
  var responder: ButtonViewGroup? = null
352
358
  var dummyClickListener = OnClickListener { }
353
-
354
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
355
- private fun getAttrId(context: Context, attr: String): Int {
356
- SoftAssertions.assertNotNull(attr)
357
- return when (attr) {
358
- SELECTABLE_ITEM_BACKGROUND -> {
359
- android.R.attr.selectableItemBackground
360
- }
361
- SELECTABLE_ITEM_BACKGROUND_BORDERLESS -> {
362
- android.R.attr.selectableItemBackgroundBorderless
363
- }
364
- else -> {
365
- context.resources.getIdentifier(attr, "attr", "android")
366
- }
367
- }
368
- }
369
359
  }
370
360
  }
371
361
 
@@ -224,6 +224,9 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?)
224
224
  if (config.hasKey(KEY_PAN_AVG_TOUCHES)) {
225
225
  handler.setAverageTouches(config.getBoolean(KEY_PAN_AVG_TOUCHES))
226
226
  }
227
+ if (config.hasKey(KEY_PAN_ACTIVATE_AFTER_LONG_PRESS)) {
228
+ handler.setActivateAfterLongPress(config.getInt(KEY_PAN_ACTIVATE_AFTER_LONG_PRESS).toLong())
229
+ }
227
230
  }
228
231
 
229
232
  override fun extractEventData(handler: PanGestureHandler, eventData: WritableMap) {
@@ -656,6 +659,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?)
656
659
  private const val KEY_PAN_MIN_POINTERS = "minPointers"
657
660
  private const val KEY_PAN_MAX_POINTERS = "maxPointers"
658
661
  private const val KEY_PAN_AVG_TOUCHES = "avgTouches"
662
+ private const val KEY_PAN_ACTIVATE_AFTER_LONG_PRESS = "activateAfterLongPress"
659
663
  private const val KEY_NUMBER_OF_POINTERS = "numberOfPointers"
660
664
  private const val KEY_DIRECTION = "direction"
661
665
 
@@ -28,7 +28,7 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) $(GENERATED_SRC_DIR)/codegen/jni
28
28
  LOCAL_SHARED_LIBRARIES := libjsi \
29
29
  libfbjni \
30
30
  libglog \
31
- libfolly_json \
31
+ libfolly_runtime \
32
32
  libyoga \
33
33
  libreact_nativemodule_core \
34
34
  libturbomodulejsijni \
@@ -36,7 +36,6 @@ LOCAL_SHARED_LIBRARIES := libjsi \
36
36
  libreact_render_core \
37
37
  libreact_render_graphics \
38
38
  libfabricjni \
39
- libfolly_futures \
40
39
  libreact_debug \
41
40
  libreact_render_componentregistry \
42
41
  libreact_render_debug \