react-native-keyboard-controller 1.4.4 → 1.5.1

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 (87) hide show
  1. package/README.md +1 -1
  2. package/android/build.gradle +9 -2
  3. package/android/gradle.properties +2 -2
  4. package/android/react-native-helpers.gradle +48 -0
  5. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +44 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/InteractiveKeyboardProvider.kt +6 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationCallback.kt +27 -13
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationController.kt +394 -0
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +1 -1
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/events/KeyboardTransitionEvent.kt +2 -2
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/Float.kt +4 -4
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +21 -0
  13. package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/Interpolator.kt +14 -0
  14. package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/IosInterpolator.kt +18 -0
  15. package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/LinearInterpolator.kt +11 -0
  16. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -0
  17. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +27 -0
  18. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardGestureAreaReactViewGroup.kt +192 -0
  19. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +33 -0
  20. package/ios/KeyboardControllerView.mm +10 -3
  21. package/ios/KeyboardControllerViewManager.mm +1 -0
  22. package/ios/KeyboardControllerViewManager.swift +1 -0
  23. package/ios/KeyboardMovementObserver.swift +78 -4
  24. package/jest/index.js +1 -0
  25. package/lib/commonjs/animated.js +13 -19
  26. package/lib/commonjs/animated.js.map +1 -1
  27. package/lib/commonjs/context.js +0 -3
  28. package/lib/commonjs/context.js.map +1 -1
  29. package/lib/commonjs/hooks.js +0 -13
  30. package/lib/commonjs/hooks.js.map +1 -1
  31. package/lib/commonjs/index.js +0 -13
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/commonjs/internal.js +13 -22
  34. package/lib/commonjs/internal.js.map +1 -1
  35. package/lib/commonjs/monkey-patch.js +3 -12
  36. package/lib/commonjs/monkey-patch.js.map +1 -1
  37. package/lib/commonjs/native.js +10 -10
  38. package/lib/commonjs/native.js.map +1 -1
  39. package/lib/commonjs/replicas.js +3 -19
  40. package/lib/commonjs/replicas.js.map +1 -1
  41. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js +0 -5
  42. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  43. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js +13 -0
  44. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js.map +1 -0
  45. package/lib/commonjs/specs/NativeKeyboardController.js +0 -3
  46. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  47. package/lib/commonjs/specs/NativeStatusBarManagerCompat.js +0 -3
  48. package/lib/commonjs/specs/NativeStatusBarManagerCompat.js.map +1 -1
  49. package/lib/commonjs/types.js.map +1 -1
  50. package/lib/commonjs/utils.js +0 -2
  51. package/lib/commonjs/utils.js.map +1 -1
  52. package/lib/module/animated.js +13 -7
  53. package/lib/module/animated.js.map +1 -1
  54. package/lib/module/context.js.map +1 -1
  55. package/lib/module/hooks.js.map +1 -1
  56. package/lib/module/index.js.map +1 -1
  57. package/lib/module/internal.js +13 -16
  58. package/lib/module/internal.js.map +1 -1
  59. package/lib/module/monkey-patch.js +4 -10
  60. package/lib/module/monkey-patch.js.map +1 -1
  61. package/lib/module/native.js +8 -5
  62. package/lib/module/native.js.map +1 -1
  63. package/lib/module/replicas.js +3 -5
  64. package/lib/module/replicas.js.map +1 -1
  65. package/lib/module/specs/KeyboardControllerViewNativeComponent.js +0 -1
  66. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  67. package/lib/module/specs/KeyboardGestureAreaNativeComponent.js +5 -0
  68. package/lib/module/specs/KeyboardGestureAreaNativeComponent.js.map +1 -0
  69. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  70. package/lib/module/specs/NativeStatusBarManagerCompat.js.map +1 -1
  71. package/lib/module/types.js.map +1 -1
  72. package/lib/module/utils.js.map +1 -1
  73. package/lib/typescript/animated.d.ts +1 -1
  74. package/lib/typescript/context.d.ts +3 -3
  75. package/lib/typescript/internal.d.ts +1 -0
  76. package/lib/typescript/native.d.ts +2 -1
  77. package/lib/typescript/replicas.d.ts +1 -1
  78. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +6 -5
  79. package/lib/typescript/specs/KeyboardGestureAreaNativeComponent.d.ts +10 -0
  80. package/lib/typescript/types.d.ts +29 -13
  81. package/package.json +11 -12
  82. package/src/animated.tsx +7 -0
  83. package/src/internal.ts +20 -3
  84. package/src/native.ts +5 -0
  85. package/src/specs/KeyboardControllerViewNativeComponent.ts +5 -6
  86. package/src/specs/KeyboardGestureAreaNativeComponent.ts +14 -0
  87. package/src/types.ts +24 -5
@@ -2,7 +2,7 @@ package com.reactnativekeyboardcontroller.extensions
2
2
 
3
3
  import android.content.res.Resources
4
4
 
5
- val Float.dp: Int
6
- get() = (this / Resources.getSystem().displayMetrics.density).toInt()
7
- val Float.px: Int
8
- get() = (this * Resources.getSystem().displayMetrics.density).toInt()
5
+ val Float.dp: Double
6
+ get() = (this / Resources.getSystem().displayMetrics.density).toDouble()
7
+ val Float.px: Double
8
+ get() = (this * Resources.getSystem().displayMetrics.density).toDouble()
@@ -1,7 +1,9 @@
1
1
  package com.reactnativekeyboardcontroller.extensions
2
2
 
3
+ import android.graphics.Rect
3
4
  import android.os.Build
4
5
  import android.view.View
6
+ import androidx.annotation.RequiresApi
5
7
 
6
8
  /**
7
9
  * Call this everytime when using [ViewCompat.setOnApplyWindowInsetsListener]
@@ -27,3 +29,22 @@ fun View.requestApplyInsetsWhenAttached() {
27
29
  })
28
30
  }
29
31
  }
32
+
33
+ private val tmpIntArr = IntArray(2)
34
+
35
+ /**
36
+ * Function which updates the given [rect] with this view's position and bounds in its window.
37
+ */
38
+ @RequiresApi(Build.VERSION_CODES.KITKAT)
39
+ fun View.copyBoundsInWindow(rect: Rect) {
40
+ if (isLaidOut && isAttachedToWindow) {
41
+ rect.set(0, 0, width, height)
42
+ getLocationInWindow(tmpIntArr)
43
+ rect.offset(tmpIntArr[0], tmpIntArr[1])
44
+ } else {
45
+ throw IllegalArgumentException(
46
+ "Can not copy bounds as view is not laid out" +
47
+ " or attached to window",
48
+ )
49
+ }
50
+ }
@@ -0,0 +1,14 @@
1
+ package com.reactnativekeyboardcontroller.interpolators
2
+
3
+ interface Interpolator {
4
+ /**
5
+ * A function that allows you to control the layout of the keyboard
6
+ * depending on the position of the finger on the screen.
7
+ *
8
+ * @param dy the distance that the finger has moved relative to the previous location.
9
+ * @param absoluteFingerPosition current position of the finger.
10
+ * @param keyboardPosition current keyboard position.
11
+ * @return the distance the keyboard should be moved from its current location.
12
+ */
13
+ fun interpolate(dy: Int, absoluteFingerPosition: Int, keyboardPosition: Int): Int
14
+ }
@@ -0,0 +1,18 @@
1
+ package com.reactnativekeyboardcontroller.interpolators
2
+
3
+ class IosInterpolator : Interpolator {
4
+ override fun interpolate(
5
+ dy: Int,
6
+ absoluteFingerPosition: Int,
7
+ keyboardPosition: Int,
8
+ ): Int {
9
+ if (
10
+ absoluteFingerPosition <= keyboardPosition || // user overscrolled keyboard
11
+ dy <= 0 // user scrolls up
12
+ ) {
13
+ return dy
14
+ }
15
+
16
+ return 0
17
+ }
18
+ }
@@ -0,0 +1,11 @@
1
+ package com.reactnativekeyboardcontroller.interpolators
2
+
3
+ class LinearInterpolator : Interpolator {
4
+ override fun interpolate(
5
+ dy: Int,
6
+ absoluteFingerPosition: Int,
7
+ keyboardPosition: Int,
8
+ ): Int {
9
+ return dy
10
+ }
11
+ }
@@ -74,6 +74,8 @@ class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicat
74
74
  MapBuilder.of("registrationName", "onKeyboardMoveStart"),
75
75
  "topKeyboardMoveEnd",
76
76
  MapBuilder.of("registrationName", "onKeyboardMoveEnd"),
77
+ "topKeyboardMoveInteractive",
78
+ MapBuilder.of("registrationName", "onKeyboardMoveInteractive"),
77
79
  )
78
80
 
79
81
  return map
@@ -0,0 +1,27 @@
1
+ package com.reactnativekeyboardcontroller.managers
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.uimanager.ThemedReactContext
5
+ import com.reactnativekeyboardcontroller.views.KeyboardGestureAreaReactViewGroup
6
+
7
+ class KeyboardGestureAreaViewManagerImpl(mReactContext: ReactApplicationContext) {
8
+ fun createViewInstance(reactContext: ThemedReactContext): KeyboardGestureAreaReactViewGroup {
9
+ return KeyboardGestureAreaReactViewGroup(reactContext)
10
+ }
11
+
12
+ fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
13
+ view.setInterpolator(interpolator)
14
+ }
15
+
16
+ fun setScrollKeyboardOffScreenWhenVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
17
+ view.setScrollKeyboardOffScreenWhenVisible(value)
18
+ }
19
+
20
+ fun setScrollKeyboardOnScreenWhenNotVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
21
+ view.setScrollKeyboardOnScreenWhenNotVisible(value)
22
+ }
23
+
24
+ companion object {
25
+ const val NAME = "KeyboardGestureArea"
26
+ }
27
+ }
@@ -0,0 +1,192 @@
1
+ package com.reactnativekeyboardcontroller.views
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.graphics.Rect
5
+ import android.os.Build
6
+ import android.view.MotionEvent
7
+ import android.view.VelocityTracker
8
+ import android.view.ViewConfiguration
9
+ import androidx.annotation.RequiresApi
10
+ import androidx.core.view.ViewCompat
11
+ import androidx.core.view.WindowInsetsCompat
12
+ import com.facebook.react.uimanager.ThemedReactContext
13
+ import com.facebook.react.views.view.ReactViewGroup
14
+ import com.reactnativekeyboardcontroller.KeyboardAnimationController
15
+ import com.reactnativekeyboardcontroller.extensions.copyBoundsInWindow
16
+ import com.reactnativekeyboardcontroller.interpolators.Interpolator
17
+ import com.reactnativekeyboardcontroller.interpolators.IosInterpolator
18
+ import com.reactnativekeyboardcontroller.interpolators.LinearInterpolator
19
+ import kotlin.math.absoluteValue
20
+ import kotlin.math.roundToInt
21
+
22
+ val interpolators = mapOf(
23
+ "linear" to LinearInterpolator(),
24
+ "ios" to IosInterpolator(),
25
+ )
26
+
27
+ @SuppressLint("ViewConstructor")
28
+ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
29
+ // internal state management
30
+ private var isHandling = false
31
+ private var lastTouchX = 0f
32
+ private var lastTouchY = 0f
33
+ private var lastWindowY = 0
34
+
35
+ // react props
36
+ private var interpolator: Interpolator = LinearInterpolator()
37
+ private var scrollKeyboardOnScreenWhenNotVisible = false
38
+ private var scrollKeyboardOffScreenWhenVisible = true
39
+
40
+ private val bounds = Rect()
41
+
42
+ private val controller = KeyboardAnimationController()
43
+
44
+ private var velocityTracker: VelocityTracker? = null
45
+
46
+ @RequiresApi(Build.VERSION_CODES.R)
47
+ override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
48
+ if (velocityTracker == null) {
49
+ // Obtain a VelocityTracker if we don't have one yet
50
+ velocityTracker = VelocityTracker.obtain()
51
+ }
52
+
53
+ when (event?.action) {
54
+ MotionEvent.ACTION_DOWN -> {
55
+ velocityTracker?.addMovement(event)
56
+
57
+ lastTouchX = event.x
58
+ lastTouchY = event.y
59
+
60
+ this.copyBoundsInWindow(bounds)
61
+ lastWindowY = bounds.top
62
+ }
63
+ MotionEvent.ACTION_MOVE -> {
64
+ // Since the view is likely to be translated/moved as the WindowInsetsAnimation
65
+ // progresses, we need to make sure we account for that change in our touch
66
+ // handling. We do that by keeping track of the view's Y position in the window,
67
+ // and detecting the difference between the current bounds.
68
+ this.copyBoundsInWindow(bounds)
69
+ val windowOffsetY = bounds.top - lastWindowY
70
+
71
+ // We then make a copy of the MotionEvent, and offset it with the calculated
72
+ // windowOffsetY. We can then pass it to the VelocityTracker.
73
+ val velocityTrackerEvent = MotionEvent.obtain(event)
74
+ velocityTrackerEvent.offsetLocation(0f, windowOffsetY.toFloat())
75
+ velocityTracker?.addMovement(velocityTrackerEvent)
76
+
77
+ val dx = velocityTrackerEvent.x - lastTouchX
78
+ val dy = velocityTrackerEvent.y - lastTouchY
79
+
80
+ if (!isHandling) {
81
+ // If we're not currently handling the touch gesture, lets check if we should
82
+ // start handling, by seeing if the gesture is majorly vertical, and
83
+ // larger than the touch slop
84
+ isHandling = dy.absoluteValue > dx.absoluteValue &&
85
+ dy.absoluteValue >= ViewConfiguration.get(this.context).scaledTouchSlop
86
+ }
87
+
88
+ if (isHandling) {
89
+ if (controller.isInsetAnimationInProgress()) {
90
+ // If we currently have control, we can update the IME insets to 'scroll'
91
+ // the IME in
92
+ val moveBy = this.interpolator.interpolate(dy.roundToInt(), this.getWindowHeight() - event.rawY.toInt(), controller.getCurrentKeyboardHeight())
93
+
94
+ if (moveBy != 0) {
95
+ controller.insetBy(moveBy)
96
+ }
97
+ } else if (
98
+ !controller.isInsetAnimationRequestPending() &&
99
+ shouldStartRequest(
100
+ dy = dy,
101
+ imeVisible = ViewCompat.getRootWindowInsets(this)
102
+ ?.isVisible(WindowInsetsCompat.Type.ime()) == true,
103
+ )
104
+ ) {
105
+ // If we don't currently have control (and a request isn't pending),
106
+ // the IME is not shown, the user is scrolling up, and the view can't
107
+ // scroll up any more (i.e. over-scrolling), we can start to control
108
+ // the IME insets
109
+ controller.startControlRequest(this)
110
+ }
111
+
112
+ // Lastly we record the event X, Y, and view's Y window position, for the
113
+ // next touch event
114
+ lastTouchY = event.y
115
+ lastTouchX = event.x
116
+ lastWindowY = bounds.top
117
+ }
118
+ }
119
+ MotionEvent.ACTION_UP -> {
120
+ velocityTracker?.addMovement(event)
121
+
122
+ // Calculate the current velocityY, over 500 milliseconds
123
+ velocityTracker?.computeCurrentVelocity(500)
124
+ val velocityY = velocityTracker?.yVelocity
125
+
126
+ // If we received a ACTION_UP event, end any current WindowInsetsAnimation passing
127
+ // in the calculated Y velocity
128
+ controller.animateToFinish(velocityY)
129
+
130
+ // Reset our touch handling state
131
+ reset()
132
+ }
133
+ MotionEvent.ACTION_CANCEL -> {
134
+ // If we received a ACTION_CANCEL event, cancel any current WindowInsetsAnimation
135
+ controller.cancel()
136
+ // Reset our touch handling state
137
+ reset()
138
+ }
139
+ }
140
+
141
+ return super.dispatchTouchEvent(event)
142
+ }
143
+
144
+ fun setInterpolator(interpolator: String) {
145
+ this.interpolator = interpolators[interpolator] ?: LinearInterpolator()
146
+ }
147
+
148
+ fun setScrollKeyboardOnScreenWhenNotVisible(scrollImeOnScreenWhenNotVisible: Boolean) {
149
+ this.scrollKeyboardOnScreenWhenNotVisible = scrollImeOnScreenWhenNotVisible
150
+ }
151
+
152
+ fun setScrollKeyboardOffScreenWhenVisible(scrollImeOffScreenWhenVisible: Boolean) {
153
+ this.scrollKeyboardOffScreenWhenVisible = scrollImeOffScreenWhenVisible
154
+ }
155
+
156
+ /**
157
+ * Resets all of our internal state.
158
+ */
159
+ private fun reset() {
160
+ // Clear all of our internal state
161
+ isHandling = false
162
+ lastTouchX = 0f
163
+ lastTouchY = 0f
164
+ lastWindowY = 0
165
+ bounds.setEmpty()
166
+
167
+ velocityTracker?.recycle()
168
+ velocityTracker = null
169
+ }
170
+
171
+ /**
172
+ * Returns true if the given [dy], [IME visibility][imeVisible], and constructor options
173
+ * support a IME animation request.
174
+ */
175
+ private fun shouldStartRequest(dy: Float, imeVisible: Boolean) = when {
176
+ // If the user is scroll up, return true if scrollImeOnScreenWhenNotVisible is true, and
177
+ // the IME is not currently visible
178
+ dy < 0 -> !imeVisible && scrollKeyboardOnScreenWhenNotVisible
179
+ // If the user is scroll down, start the request if scrollImeOffScreenWhenVisible is true,
180
+ // and the IME is currently visible
181
+ dy > 0 -> imeVisible && scrollKeyboardOffScreenWhenVisible
182
+ // Otherwise, return false
183
+ else -> false
184
+ }
185
+
186
+ @RequiresApi(Build.VERSION_CODES.R)
187
+ private fun getWindowHeight(): Int {
188
+ val metrics = reactContext.currentActivity?.windowManager?.currentWindowMetrics
189
+
190
+ return metrics?.bounds?.height() ?: 0
191
+ }
192
+ }
@@ -0,0 +1,33 @@
1
+ package com.reactnativekeyboardcontroller
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.uimanager.ThemedReactContext
5
+ import com.facebook.react.uimanager.annotations.ReactProp
6
+ import com.facebook.react.views.view.ReactViewManager
7
+ import com.reactnativekeyboardcontroller.managers.KeyboardGestureAreaViewManagerImpl
8
+ import com.reactnativekeyboardcontroller.views.KeyboardGestureAreaReactViewGroup
9
+
10
+ class KeyboardGestureAreaViewManager(mReactContext: ReactApplicationContext) : ReactViewManager() {
11
+ private val manager = KeyboardGestureAreaViewManagerImpl(mReactContext)
12
+
13
+ override fun getName(): String = KeyboardGestureAreaViewManagerImpl.NAME
14
+
15
+ override fun createViewInstance(reactContext: ThemedReactContext): KeyboardGestureAreaReactViewGroup {
16
+ return manager.createViewInstance(reactContext)
17
+ }
18
+
19
+ @ReactProp(name = "interpolator")
20
+ fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
21
+ manager.setInterpolator(view, interpolator)
22
+ }
23
+
24
+ @ReactProp(name = "showOnSwipeUp")
25
+ fun setScrollKeyboardOnScreenWhenNotVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
26
+ manager.setScrollKeyboardOnScreenWhenNotVisible(view, value)
27
+ }
28
+
29
+ @ReactProp(name = "enableSwipeToDismiss")
30
+ fun setScrollKeyboardOffScreenWhenVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
31
+ manager.setScrollKeyboardOffScreenWhenVisible(view, value)
32
+ }
33
+ }
@@ -52,23 +52,30 @@ using namespace facebook::react;
52
52
  self->_eventEmitter)
53
53
  ->onKeyboardMoveStart(
54
54
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMoveStart{
55
- .height = [height intValue], .progress = [progress floatValue]});
55
+ .height = [height doubleValue], .progress = [progress doubleValue]});
56
56
  }
57
57
  if ([event isEqualToString:@"onKeyboardMove"]) {
58
58
  std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
59
59
  self->_eventEmitter)
60
60
  ->onKeyboardMove(
61
61
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMove{
62
- .height = [height intValue], .progress = [progress floatValue]});
62
+ .height = [height doubleValue], .progress = [progress doubleValue]});
63
63
  }
64
64
  if ([event isEqualToString:@"onKeyboardMoveEnd"]) {
65
65
  std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
66
66
  self->_eventEmitter)
67
67
  ->onKeyboardMoveEnd(
68
68
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMoveEnd{
69
- .height = [height intValue], .progress = [progress floatValue]});
69
+ .height = [height doubleValue], .progress = [progress doubleValue]});
70
70
  }
71
71
  }
72
+ if ([event isEqualToString:@"onKeyboardMoveInteractive"]) {
73
+ std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
74
+ self->_eventEmitter)
75
+ ->onKeyboardMoveInteractive(
76
+ facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMoveInteractive{
77
+ .height = [height doubleValue], .progress = [progress doubleValue]});
78
+ }
72
79
 
73
80
  // TODO: use built-in _eventEmitter once NativeAnimated module will use ModernEventemitter
74
81
  RCTBridge *bridge = [RCTBridge currentBridge];
@@ -5,5 +5,6 @@
5
5
  RCT_EXPORT_VIEW_PROPERTY(onKeyboardMoveStart, RCTDirectEventBlock);
6
6
  RCT_EXPORT_VIEW_PROPERTY(onKeyboardMove, RCTDirectEventBlock);
7
7
  RCT_EXPORT_VIEW_PROPERTY(onKeyboardMoveEnd, RCTDirectEventBlock);
8
+ RCT_EXPORT_VIEW_PROPERTY(onKeyboardMoveInteractive, RCTDirectEventBlock);
8
9
 
9
10
  @end
@@ -15,6 +15,7 @@ class KeyboardControllerView: UIView {
15
15
  @objc var onKeyboardMoveStart: RCTDirectEventBlock?
16
16
  @objc var onKeyboardMove: RCTDirectEventBlock?
17
17
  @objc var onKeyboardMoveEnd: RCTDirectEventBlock?
18
+ @objc var onKeyboardMoveInteractive: RCTDirectEventBlock?
18
19
 
19
20
  init(frame: CGRect, eventDispatcher: RCTEventDispatcherProtocol) {
20
21
  self.eventDispatcher = eventDispatcher
@@ -8,6 +8,18 @@
8
8
 
9
9
  import Foundation
10
10
 
11
+ func interpolate(inputRange: [CGFloat], outputRange: [CGFloat], currentValue: CGFloat) -> CGFloat {
12
+ let inputMin = inputRange.min() ?? 0
13
+ let inputMax = inputRange.max() ?? 1
14
+ let outputMin = outputRange.min() ?? 0
15
+ let outputMax = outputRange.max() ?? 1
16
+
17
+ let normalizedValue = (currentValue - inputMin) / (inputMax - inputMin)
18
+ let interpolatedValue = outputMin + (outputMax - outputMin) * normalizedValue
19
+
20
+ return interpolatedValue
21
+ }
22
+
11
23
  @objc(KeyboardMovementObserver)
12
24
  public class KeyboardMovementObserver: NSObject {
13
25
  // class members
@@ -64,10 +76,63 @@ public class KeyboardMovementObserver: NSObject {
64
76
  name: UIResponder.keyboardDidHideNotification,
65
77
  object: nil
66
78
  )
79
+ NotificationCenter.default.addObserver(
80
+ self,
81
+ selector: #selector(windowDidBecomeVisible),
82
+ name: UIWindow.didBecomeVisibleNotification,
83
+ object: nil
84
+ )
85
+ }
86
+
87
+ @objc func windowDidBecomeVisible(_: Notification) {
88
+ let keyboardView = findKeyboardView()
89
+
90
+ if keyboardView == _keyboardView {
91
+ return
92
+ }
93
+
94
+ _keyboardView = keyboardView
95
+
96
+ keyboardView?.addObserver(self, forKeyPath: "center", options: .new, context: nil)
97
+ }
98
+
99
+ // swiftlint:disable:next block_based_kvo
100
+ @objc override public func observeValue(
101
+ forKeyPath keyPath: String?,
102
+ of object: Any?,
103
+ change: [NSKeyValueChangeKey: Any]?,
104
+ context _: UnsafeMutableRawPointer?
105
+ ) {
106
+ // swiftlint:disable:next force_cast
107
+ if keyPath == "center", object as! NSObject == _keyboardView! {
108
+ // if we are currently animating keyboard or keyboard is not shown yet -> we need to ignore values from KVO
109
+ if displayLink != nil || keyboardHeight == 0.0 {
110
+ return
111
+ }
112
+
113
+ // swiftlint:disable:next force_cast
114
+ let keyboardFrameY = (change?[.newKey] as! NSValue).cgPointValue.y
115
+ let keyboardWindowH = keyboardView?.window?.bounds.size.height ?? 0
116
+ let keyboardPosition = keyboardWindowH - keyboardFrameY
117
+ let position = interpolate(
118
+ inputRange: [keyboardHeight / 2, -keyboardHeight / 2],
119
+ outputRange: [keyboardHeight, 0],
120
+ currentValue: keyboardPosition
121
+ )
122
+
123
+ if position == 0 {
124
+ // it will be triggered before `keyboardWillDisappear` and
125
+ // we don't need to trigger `onInteractive` handler for that
126
+ // since it will be handled in `keyboardWillDisappear` function
127
+ return
128
+ }
129
+
130
+ onEvent("onKeyboardMoveInteractive", position as NSNumber, position / CGFloat(keyboardHeight) as NSNumber)
131
+ }
67
132
  }
68
133
 
69
134
  @objc public func unmount() {
70
- // swiftlint:disable notification_center_detachment
135
+ // swiftlint:disable:next notification_center_detachment
71
136
  NotificationCenter.default.removeObserver(self)
72
137
  }
73
138
 
@@ -115,6 +180,8 @@ public class KeyboardMovementObserver: NSObject {
115
180
  var data = [AnyHashable: Any]()
116
181
  data["height"] = 0
117
182
 
183
+ keyboardHeight = 0.0
184
+
118
185
  onEvent("onKeyboardMoveEnd", 0 as NSNumber, 0)
119
186
  onNotify("KeyboardController::keyboardDidHide", data)
120
187
 
@@ -122,6 +189,13 @@ public class KeyboardMovementObserver: NSObject {
122
189
  }
123
190
 
124
191
  @objc func setupKeyboardWatcher() {
192
+ // sometimes `will` events can be called multiple times.
193
+ // To avoid double re-creation of listener we are adding this condition
194
+ // (if active link is present, then no need to re-setup a listener)
195
+ if displayLink != nil {
196
+ return
197
+ }
198
+
125
199
  displayLink = CADisplayLink(target: self, selector: #selector(updateKeyboardFrame))
126
200
  displayLink?.preferredFramesPerSecond = 120 // will fallback to 60 fps for devices without Pro Motion display
127
201
  displayLink?.add(to: .main, forMode: .common)
@@ -161,9 +235,9 @@ public class KeyboardMovementObserver: NSObject {
161
235
  return
162
236
  }
163
237
 
164
- var keyboardFrameY = keyboardView?.layer.presentation()?.frame.origin.y ?? 0
165
- var keyboardWindowH = keyboardView?.window?.bounds.size.height ?? 0
166
- var keyboardPosition = keyboardWindowH - keyboardFrameY
238
+ let keyboardFrameY = keyboardView?.layer.presentation()?.frame.origin.y ?? 0
239
+ let keyboardWindowH = keyboardView?.window?.bounds.size.height ?? 0
240
+ let keyboardPosition = keyboardWindowH - keyboardFrameY
167
241
 
168
242
  if keyboardPosition == prevKeyboardPosition || keyboardFrameY == 0 {
169
243
  return
package/jest/index.js CHANGED
@@ -28,6 +28,7 @@ const mock = {
28
28
  },
29
29
  // views
30
30
  KeyboardControllerView: 'KeyboardControllerView',
31
+ KeyboardGestureArea: 'KeyboardGestureArea',
31
32
  // providers
32
33
  KeyboardProvider: 'KeyboardProvider',
33
34
  };
@@ -4,25 +4,15 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.KeyboardProvider = void 0;
7
-
8
7
  var _react = _interopRequireWildcard(require("react"));
9
-
10
8
  var _reactNative = require("react-native");
11
-
12
9
  var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
13
-
14
10
  var _context = require("./context");
15
-
16
11
  var _internal = require("./internal");
17
-
18
12
  var _native = require("./native");
19
-
20
13
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
21
-
22
14
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
23
-
24
15
  const KeyboardControllerViewAnimated = _reactNativeReanimated.default.createAnimatedComponent(_reactNative.Animated.createAnimatedComponent(_native.KeyboardControllerView));
25
-
26
16
  const styles = _reactNative.StyleSheet.create({
27
17
  container: {
28
18
  flex: 1
@@ -32,7 +22,6 @@ const styles = _reactNative.StyleSheet.create({
32
22
  position: 'absolute'
33
23
  }
34
24
  });
35
-
36
25
  const KeyboardProvider = _ref => {
37
26
  let {
38
27
  children,
@@ -41,15 +30,15 @@ const KeyboardProvider = _ref => {
41
30
  } = _ref;
42
31
  // animated values
43
32
  const progress = (0, _internal.useAnimatedValue)(0);
44
- const height = (0, _internal.useAnimatedValue)(0); // shared values
45
-
33
+ const height = (0, _internal.useAnimatedValue)(0);
34
+ // shared values
46
35
  const progressSV = (0, _reactNativeReanimated.useSharedValue)(0);
47
36
  const heightSV = (0, _reactNativeReanimated.useSharedValue)(0);
48
37
  const {
49
38
  setHandlers,
50
39
  broadcast
51
- } = (0, _internal.useSharedHandlers)(); // memo
52
-
40
+ } = (0, _internal.useSharedHandlers)();
41
+ // memo
53
42
  const context = (0, _react.useMemo)(() => ({
54
43
  animated: {
55
44
  progress: progress,
@@ -75,8 +64,8 @@ const KeyboardProvider = _ref => {
75
64
  }
76
65
  }], {
77
66
  useNativeDriver: true
78
- }), []); // handlers
79
-
67
+ }), []);
68
+ // handlers
80
69
  const updateSharedValues = (event, platforms) => {
81
70
  'worklet';
82
71
 
@@ -85,7 +74,6 @@ const KeyboardProvider = _ref => {
85
74
  heightSV.value = -event.height;
86
75
  }
87
76
  };
88
-
89
77
  const handler = (0, _internal.useAnimatedKeyboardHandler)({
90
78
  onKeyboardMoveStart: event => {
91
79
  'worklet';
@@ -103,6 +91,12 @@ const KeyboardProvider = _ref => {
103
91
  'worklet';
104
92
 
105
93
  broadcast('onEnd', event);
94
+ },
95
+ onKeyboardMoveInteractive: event => {
96
+ 'worklet';
97
+
98
+ updateSharedValues(event, ['android', 'ios']);
99
+ broadcast('onInteractive', event);
106
100
  }
107
101
  }, []);
108
102
  return /*#__PURE__*/_react.default.createElement(_context.KeyboardContext.Provider, {
@@ -111,6 +105,7 @@ const KeyboardProvider = _ref => {
111
105
  onKeyboardMoveReanimated: handler,
112
106
  onKeyboardMoveStart: _reactNative.Platform.OS === 'ios' ? onKeyboardMove : undefined,
113
107
  onKeyboardMove: _reactNative.Platform.OS === 'android' ? onKeyboardMove : undefined,
108
+ onKeyboardMoveInteractive: onKeyboardMove,
114
109
  navigationBarTranslucent: navigationBarTranslucent,
115
110
  statusBarTranslucent: statusBarTranslucent,
116
111
  style: styles.container
@@ -126,6 +121,5 @@ const KeyboardProvider = _ref => {
126
121
  style: style
127
122
  }), children)));
128
123
  };
129
-
130
124
  exports.KeyboardProvider = KeyboardProvider;
131
125
  //# sourceMappingURL=animated.js.map