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.
- package/README.md +1 -1
- package/android/build.gradle +9 -2
- package/android/gradle.properties +2 -2
- package/android/react-native-helpers.gradle +48 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +44 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/InteractiveKeyboardProvider.kt +6 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationCallback.kt +27 -13
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationController.kt +394 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +1 -1
- package/android/src/main/java/com/reactnativekeyboardcontroller/events/KeyboardTransitionEvent.kt +2 -2
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/Float.kt +4 -4
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +21 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/Interpolator.kt +14 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/IosInterpolator.kt +18 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/LinearInterpolator.kt +11 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +27 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardGestureAreaReactViewGroup.kt +192 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +33 -0
- package/ios/KeyboardControllerView.mm +10 -3
- package/ios/KeyboardControllerViewManager.mm +1 -0
- package/ios/KeyboardControllerViewManager.swift +1 -0
- package/ios/KeyboardMovementObserver.swift +78 -4
- package/jest/index.js +1 -0
- package/lib/commonjs/animated.js +13 -19
- package/lib/commonjs/animated.js.map +1 -1
- package/lib/commonjs/context.js +0 -3
- package/lib/commonjs/context.js.map +1 -1
- package/lib/commonjs/hooks.js +0 -13
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/commonjs/index.js +0 -13
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/internal.js +13 -22
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/monkey-patch.js +3 -12
- package/lib/commonjs/monkey-patch.js.map +1 -1
- package/lib/commonjs/native.js +10 -10
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/replicas.js +3 -19
- package/lib/commonjs/replicas.js.map +1 -1
- package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js +0 -5
- package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js +13 -0
- package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js.map +1 -0
- package/lib/commonjs/specs/NativeKeyboardController.js +0 -3
- package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
- package/lib/commonjs/specs/NativeStatusBarManagerCompat.js +0 -3
- package/lib/commonjs/specs/NativeStatusBarManagerCompat.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils.js +0 -2
- package/lib/commonjs/utils.js.map +1 -1
- package/lib/module/animated.js +13 -7
- package/lib/module/animated.js.map +1 -1
- package/lib/module/context.js.map +1 -1
- package/lib/module/hooks.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal.js +13 -16
- package/lib/module/internal.js.map +1 -1
- package/lib/module/monkey-patch.js +4 -10
- package/lib/module/monkey-patch.js.map +1 -1
- package/lib/module/native.js +8 -5
- package/lib/module/native.js.map +1 -1
- package/lib/module/replicas.js +3 -5
- package/lib/module/replicas.js.map +1 -1
- package/lib/module/specs/KeyboardControllerViewNativeComponent.js +0 -1
- package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/module/specs/KeyboardGestureAreaNativeComponent.js +5 -0
- package/lib/module/specs/KeyboardGestureAreaNativeComponent.js.map +1 -0
- package/lib/module/specs/NativeKeyboardController.js.map +1 -1
- package/lib/module/specs/NativeStatusBarManagerCompat.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/animated.d.ts +1 -1
- package/lib/typescript/context.d.ts +3 -3
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/native.d.ts +2 -1
- package/lib/typescript/replicas.d.ts +1 -1
- package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +6 -5
- package/lib/typescript/specs/KeyboardGestureAreaNativeComponent.d.ts +10 -0
- package/lib/typescript/types.d.ts +29 -13
- package/package.json +11 -12
- package/src/animated.tsx +7 -0
- package/src/internal.ts +20 -3
- package/src/native.ts +5 -0
- package/src/specs/KeyboardControllerViewNativeComponent.ts +5 -6
- package/src/specs/KeyboardGestureAreaNativeComponent.ts +14 -0
- 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:
|
|
6
|
-
get() = (this / Resources.getSystem().displayMetrics.density).
|
|
7
|
-
val Float.px:
|
|
8
|
-
get() = (this * Resources.getSystem().displayMetrics.density).
|
|
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
|
+
}
|
package/android/src/main/java/com/reactnativekeyboardcontroller/interpolators/IosInterpolator.kt
ADDED
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt
ADDED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
package/lib/commonjs/animated.js
CHANGED
|
@@ -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);
|
|
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)();
|
|
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
|
-
}), []);
|
|
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
|