react-native-keyboard-controller 1.4.3 → 1.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.
- 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/KeyboardControllerViewManager.kt +7 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +47 -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 +8 -1
- 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/KeyboardControllerViewManager.kt +5 -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 +16 -20
- 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 +16 -8
- 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 +10 -2
- 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 +7 -5
- package/lib/typescript/specs/KeyboardGestureAreaNativeComponent.d.ts +10 -0
- package/lib/typescript/types.d.ts +30 -13
- package/package.json +11 -12
- package/src/animated.tsx +17 -0
- package/src/internal.ts +20 -3
- package/src/native.ts +5 -0
- package/src/specs/KeyboardControllerViewNativeComponent.ts +6 -6
- package/src/specs/KeyboardGestureAreaNativeComponent.ts +14 -0
- package/src/types.ts +25 -5
package/android/src/main/java/com/reactnativekeyboardcontroller/events/KeyboardTransitionEvent.kt
CHANGED
|
@@ -4,7 +4,7 @@ import com.facebook.react.bridge.Arguments
|
|
|
4
4
|
import com.facebook.react.uimanager.events.Event
|
|
5
5
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
6
6
|
|
|
7
|
-
class KeyboardTransitionEvent(viewId: Int, private val event: String, private val height:
|
|
7
|
+
class KeyboardTransitionEvent(viewId: Int, private val event: String, private val height: Double, private val progress: Double) : Event<KeyboardTransitionEvent>(viewId) {
|
|
8
8
|
override fun getEventName() = event
|
|
9
9
|
|
|
10
10
|
// TODO: All events for a given view can be coalesced?
|
|
@@ -13,7 +13,7 @@ class KeyboardTransitionEvent(viewId: Int, private val event: String, private va
|
|
|
13
13
|
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
14
14
|
val map = Arguments.createMap()
|
|
15
15
|
map.putDouble("progress", progress)
|
|
16
|
-
map.
|
|
16
|
+
map.putDouble("height", height)
|
|
17
17
|
rctEventEmitter.receiveEvent(viewTag, eventName, map)
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -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
|
+
}
|
|
@@ -17,6 +17,7 @@ import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
|
|
|
17
17
|
class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicationContext) {
|
|
18
18
|
private val TAG = KeyboardControllerViewManagerImpl::class.qualifiedName
|
|
19
19
|
private var isStatusBarTranslucent = false
|
|
20
|
+
private var isNavigationBarTranslucent = false
|
|
20
21
|
|
|
21
22
|
fun createViewInstance(reactContext: ThemedReactContext): ReactViewGroup {
|
|
22
23
|
val view = EdgeToEdgeReactViewGroup(reactContext)
|
|
@@ -44,7 +45,7 @@ class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicat
|
|
|
44
45
|
0,
|
|
45
46
|
if (this.isStatusBarTranslucent) 0 else insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0,
|
|
46
47
|
0,
|
|
47
|
-
insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0,
|
|
48
|
+
if (this.isNavigationBarTranslucent) 0 else insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0,
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
insets
|
|
@@ -61,6 +62,10 @@ class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicat
|
|
|
61
62
|
this.isStatusBarTranslucent = isStatusBarTranslucent
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
fun setNavigationBarTranslucent(view: ReactViewGroup, isNavigationBarTranslucent: Boolean) {
|
|
66
|
+
this.isNavigationBarTranslucent = isNavigationBarTranslucent
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
65
70
|
val map: MutableMap<String, Any> = MapBuilder.of(
|
|
66
71
|
"topKeyboardMove",
|
|
@@ -69,6 +74,8 @@ class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicat
|
|
|
69
74
|
MapBuilder.of("registrationName", "onKeyboardMoveStart"),
|
|
70
75
|
"topKeyboardMoveEnd",
|
|
71
76
|
MapBuilder.of("registrationName", "onKeyboardMoveEnd"),
|
|
77
|
+
"topKeyboardMoveInteractive",
|
|
78
|
+
MapBuilder.of("registrationName", "onKeyboardMoveInteractive"),
|
|
72
79
|
)
|
|
73
80
|
|
|
74
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/KeyboardControllerViewManager.kt
CHANGED
|
@@ -21,6 +21,11 @@ class KeyboardControllerViewManager(mReactContext: ReactApplicationContext) : Re
|
|
|
21
21
|
manager.setStatusBarTranslucent(view, isStatusBarTranslucent)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
@ReactProp(name = "navigationBarTranslucent")
|
|
25
|
+
fun setNavigationBarTranslucent(view: ReactViewGroup, isNavigationBarTranslucent: Boolean) {
|
|
26
|
+
manager.setNavigationBarTranslucent(view, isNavigationBarTranslucent)
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
25
30
|
return manager.getExportedCustomDirectEventTypeConstants()
|
|
26
31
|
}
|
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 = "allowToShowKeyboardFromHiddenStateBySwipeUp")
|
|
25
|
+
fun setScrollKeyboardOnScreenWhenNotVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
|
|
26
|
+
manager.setScrollKeyboardOnScreenWhenNotVisible(view, value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@ReactProp(name = "allowToDragKeyboardFromShownStateBySwipes")
|
|
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
|