react-native-gesture-handler 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 \