react-native-keyboard-controller 1.13.4 → 1.14.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 (115) hide show
  1. package/README.md +1 -0
  2. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -3
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +19 -20
  4. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +22 -12
  5. package/android/src/fabric/java/com/reactnativekeyboardcontroller/OverKeyboardViewManager.kt +40 -0
  6. package/android/src/fabric/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +7 -3
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +33 -27
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/constants/Keyboard.kt +7 -0
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputLayoutChangedEvent.kt +16 -15
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputSelectionChangedEvent.kt +25 -24
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputTextChangedEvent.kt +4 -3
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/events/KeyboardTransitionEvent.kt +7 -6
  13. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/Context.kt +38 -0
  14. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +78 -58
  15. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ReactContext.kt +9 -4
  16. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +10 -3
  17. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +10 -8
  18. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/KeyboardAnimationController.kt +65 -60
  19. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/interpolators/Interpolator.kt +6 -1
  20. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/interpolators/IosInterpolator.kt +4 -2
  21. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/interpolators/LinearInterpolator.kt +1 -3
  22. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +62 -58
  23. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +75 -50
  24. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +13 -7
  25. package/android/src/main/java/com/reactnativekeyboardcontroller/log/Logger.kt +10 -2
  26. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +35 -24
  27. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +21 -8
  28. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/OverKeyboardViewManagerImpl.kt +27 -0
  29. package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt +46 -16
  30. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +8 -8
  31. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/StatusBarManagerCompatModuleImpl.kt +14 -4
  32. package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/FocusedInputHolder.kt +1 -3
  33. package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +19 -13
  34. package/android/src/main/java/com/reactnativekeyboardcontroller/ui/FrameScheduler.kt +11 -8
  35. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +27 -18
  36. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardGestureAreaReactViewGroup.kt +23 -14
  37. package/android/src/main/java/com/reactnativekeyboardcontroller/views/overlay/OverKeyboardHostShadowNode.kt +30 -0
  38. package/android/src/main/java/com/reactnativekeyboardcontroller/views/overlay/OverKeyboardViewGroup.kt +185 -0
  39. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +3 -1
  40. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +19 -10
  41. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +21 -8
  42. package/android/src/paper/java/com/reactnativekeyboardcontroller/OverKeyboardViewManager.kt +33 -0
  43. package/android/src/paper/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +7 -3
  44. package/android/src/test/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigatorTest.kt +30 -21
  45. package/ios/KeyboardController-Bridging-Header.h +1 -0
  46. package/ios/{core → animations}/KeyboardAnimation.swift +1 -0
  47. package/ios/{core → animations}/SpringAnimation.swift +1 -1
  48. package/ios/{core → animations}/TimingAnimation.swift +21 -20
  49. package/ios/extensions/UIApplication.swift +16 -0
  50. package/ios/extensions/UIResponder.swift +0 -3
  51. package/ios/extensions/UIWindow.swift +48 -0
  52. package/ios/views/OverKeyboardViewManager.h +29 -0
  53. package/ios/views/OverKeyboardViewManager.mm +220 -0
  54. package/jest/index.js +1 -0
  55. package/lib/commonjs/bindings.js +2 -1
  56. package/lib/commonjs/bindings.js.map +1 -1
  57. package/lib/commonjs/bindings.native.js +2 -1
  58. package/lib/commonjs/bindings.native.js.map +1 -1
  59. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js +6 -0
  60. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js.map +1 -1
  61. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +2 -1
  62. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  63. package/lib/commonjs/components/KeyboardToolbar/index.js +5 -3
  64. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
  65. package/lib/commonjs/index.js +9 -1
  66. package/lib/commonjs/index.js.map +1 -1
  67. package/lib/commonjs/specs/OverKeyboardViewNativeComponent.js +10 -0
  68. package/lib/commonjs/specs/OverKeyboardViewNativeComponent.js.map +1 -0
  69. package/lib/commonjs/types.js.map +1 -1
  70. package/lib/commonjs/views/OverKeyboardView/index.js +43 -0
  71. package/lib/commonjs/views/OverKeyboardView/index.js.map +1 -0
  72. package/lib/commonjs/views/index.js +14 -0
  73. package/lib/commonjs/views/index.js.map +1 -0
  74. package/lib/module/bindings.js +1 -0
  75. package/lib/module/bindings.js.map +1 -1
  76. package/lib/module/bindings.native.js +1 -0
  77. package/lib/module/bindings.native.js.map +1 -1
  78. package/lib/module/components/KeyboardAvoidingView/hooks.js +6 -0
  79. package/lib/module/components/KeyboardAvoidingView/hooks.js.map +1 -1
  80. package/lib/module/components/KeyboardAwareScrollView/index.js +2 -1
  81. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  82. package/lib/module/components/KeyboardToolbar/index.js +5 -3
  83. package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
  84. package/lib/module/index.js +1 -0
  85. package/lib/module/index.js.map +1 -1
  86. package/lib/module/specs/OverKeyboardViewNativeComponent.js +3 -0
  87. package/lib/module/specs/OverKeyboardViewNativeComponent.js.map +1 -0
  88. package/lib/module/types.js.map +1 -1
  89. package/lib/module/views/OverKeyboardView/index.js +35 -0
  90. package/lib/module/views/OverKeyboardView/index.js.map +1 -0
  91. package/lib/module/views/index.js +2 -0
  92. package/lib/module/views/index.js.map +1 -0
  93. package/lib/typescript/bindings.d.ts +2 -1
  94. package/lib/typescript/bindings.native.d.ts +2 -1
  95. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +4 -0
  96. package/lib/typescript/components/KeyboardToolbar/index.d.ts +2 -2
  97. package/lib/typescript/index.d.ts +1 -0
  98. package/lib/typescript/specs/OverKeyboardViewNativeComponent.d.ts +7 -0
  99. package/lib/typescript/types.d.ts +4 -1
  100. package/lib/typescript/views/OverKeyboardView/index.d.ts +5 -0
  101. package/lib/typescript/views/index.d.ts +1 -0
  102. package/package.json +8 -3
  103. package/src/bindings.native.ts +3 -0
  104. package/src/bindings.ts +3 -0
  105. package/src/components/KeyboardAvoidingView/hooks.ts +6 -0
  106. package/src/components/KeyboardAwareScrollView/index.tsx +5 -2
  107. package/src/components/KeyboardToolbar/index.tsx +7 -3
  108. package/src/index.ts +1 -0
  109. package/src/specs/OverKeyboardViewNativeComponent.ts +12 -0
  110. package/src/types.ts +4 -0
  111. package/src/views/OverKeyboardView/index.tsx +43 -0
  112. package/src/views/index.ts +1 -0
  113. package/ios/.clang-format +0 -90
  114. package/ios/.swiftformat +0 -5
  115. package/ios/.swiftlint.yml +0 -14
@@ -14,14 +14,21 @@ fun ThemedReactContext.setupWindowDimensionsListener() {
14
14
  WindowDimensionListener(this)
15
15
  }
16
16
 
17
- fun ThemedReactContext?.dispatchEvent(viewId: Int, event: Event<*>) {
17
+ fun ThemedReactContext?.dispatchEvent(
18
+ viewId: Int,
19
+ event: Event<*>,
20
+ ) {
18
21
  val eventDispatcher: EventDispatcher? =
19
22
  UIManagerHelper.getEventDispatcherForReactTag(this as ReactContext, viewId)
20
23
  eventDispatcher?.dispatchEvent(event)
21
24
  }
22
25
 
23
- fun ThemedReactContext?.emitEvent(event: String, params: WritableMap) {
24
- this?.reactApplicationContext
26
+ fun ThemedReactContext?.emitEvent(
27
+ event: String,
28
+ params: WritableMap,
29
+ ) {
30
+ this
31
+ ?.reactApplicationContext
25
32
  ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
26
33
  ?.emit(event, params)
27
34
 
@@ -18,16 +18,18 @@ fun View.requestApplyInsetsWhenAttached() {
18
18
  requestApplyInsets()
19
19
  } else {
20
20
  // We're not attached to the hierarchy, add a listener to request when we are
21
- addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
22
- override fun onViewAttachedToWindow(v: View) {
23
- v.removeOnAttachStateChangeListener(this)
24
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
25
- v.requestApplyInsets()
21
+ addOnAttachStateChangeListener(
22
+ object : View.OnAttachStateChangeListener {
23
+ override fun onViewAttachedToWindow(v: View) {
24
+ v.removeOnAttachStateChangeListener(this)
25
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
26
+ v.requestApplyInsets()
27
+ }
26
28
  }
27
- }
28
29
 
29
- override fun onViewDetachedFromWindow(v: View) = Unit
30
- })
30
+ override fun onViewDetachedFromWindow(v: View) = Unit
31
+ },
32
+ )
31
33
  }
32
34
  }
33
35
 
@@ -73,7 +73,8 @@ internal class KeyboardAnimationController {
73
73
  }
74
74
 
75
75
  // Keep track of the IME insets, and the IME visibility, at the start of the request
76
- isImeShownAtStart = ViewCompat.getRootWindowInsets(view)
76
+ isImeShownAtStart = ViewCompat
77
+ .getRootWindowInsets(view)
77
78
  ?.isVisible(WindowInsetsCompat.Type.ime()) == true
78
79
 
79
80
  // Create a cancellation signal, which we pass to controlWindowInsetsAnimation() below
@@ -114,7 +115,10 @@ internal class KeyboardAnimationController {
114
115
  * @param view The view which is triggering this request
115
116
  * @param velocityY the velocity of the touch gesture which caused this call
116
117
  */
117
- fun startAndFling(view: View, velocityY: Float) = startControlRequest(view) {
118
+ fun startAndFling(
119
+ view: View,
120
+ velocityY: Float,
121
+ ) = startControlRequest(view) {
118
122
  animateToFinish(velocityY)
119
123
  }
120
124
 
@@ -127,11 +131,12 @@ internal class KeyboardAnimationController {
127
131
  * @return the amount of [dy] consumed by the inset animation, in pixels
128
132
  */
129
133
  fun insetBy(dy: Int): Int {
130
- val controller = insetsAnimationController
131
- ?: throw IllegalStateException(
132
- "Current WindowInsetsAnimationController is null." +
133
- "This should only be called if isAnimationInProgress() returns true",
134
- )
134
+ val controller =
135
+ insetsAnimationController
136
+ ?: throw IllegalStateException(
137
+ "Current WindowInsetsAnimationController is null." +
138
+ "This should only be called if isAnimationInProgress() returns true",
139
+ )
135
140
 
136
141
  InteractiveKeyboardProvider.isInteractive = true
137
142
  // Call updateInsetTo() with the new inset value
@@ -147,11 +152,12 @@ internal class KeyboardAnimationController {
147
152
  * @return the distance moved by the inset animation, in pixels
148
153
  */
149
154
  fun insetTo(inset: Int): Int {
150
- val controller = insetsAnimationController
151
- ?: throw IllegalStateException(
152
- "Current WindowInsetsAnimationController is null." +
153
- "This should only be called if isAnimationInProgress() returns true",
154
- )
155
+ val controller =
156
+ insetsAnimationController
157
+ ?: throw IllegalStateException(
158
+ "Current WindowInsetsAnimationController is null." +
159
+ "This should only be called if isAnimationInProgress() returns true",
160
+ )
155
161
 
156
162
  val hiddenBottom = controller.hiddenStateInsets.bottom
157
163
  val shownBottom = controller.shownStateInsets.bottom
@@ -182,23 +188,17 @@ internal class KeyboardAnimationController {
182
188
  /**
183
189
  * Return `true` if an inset animation is in progress.
184
190
  */
185
- fun isInsetAnimationInProgress(): Boolean {
186
- return insetsAnimationController != null
187
- }
191
+ fun isInsetAnimationInProgress(): Boolean = insetsAnimationController != null
188
192
 
189
193
  /**
190
194
  * Return `true` if an inset animation is currently finishing.
191
195
  */
192
- fun isInsetAnimationFinishing(): Boolean {
193
- return currentSpringAnimation != null
194
- }
196
+ fun isInsetAnimationFinishing(): Boolean = currentSpringAnimation != null
195
197
 
196
198
  /**
197
199
  * Return `true` if a request to control an inset animation is in progress.
198
200
  */
199
- fun isInsetAnimationRequestPending(): Boolean {
200
- return pendingRequestCancellationSignal != null
201
- }
201
+ fun isInsetAnimationRequestPending(): Boolean = pendingRequestCancellationSignal != null
202
202
 
203
203
  /**
204
204
  * Cancel the current [WindowInsetsAnimationControllerCompat]. We immediately finish
@@ -280,10 +280,11 @@ internal class KeyboardAnimationController {
280
280
  when {
281
281
  // If we have a velocity, we can use it's direction to determine
282
282
  // the visibility. Upwards == visible
283
- velocityY != null -> animateImeToVisibility(
284
- visible = velocityY < 0,
285
- velocityY = velocityY,
286
- )
283
+ velocityY != null ->
284
+ animateImeToVisibility(
285
+ visible = velocityY < 0,
286
+ velocityY = velocityY,
287
+ )
287
288
  // The current inset matches either the shown/hidden inset, finish() immediately
288
289
  current == shown -> {
289
290
  InteractiveKeyboardProvider.shown = true
@@ -307,11 +308,12 @@ internal class KeyboardAnimationController {
307
308
  }
308
309
 
309
310
  fun getCurrentKeyboardHeight(): Int {
310
- val controller = insetsAnimationController
311
- ?: throw IllegalStateException(
312
- "Current WindowInsetsAnimationController is null." +
313
- "This should only be called if isAnimationInProgress() returns true",
314
- )
311
+ val controller =
312
+ insetsAnimationController
313
+ ?: throw IllegalStateException(
314
+ "Current WindowInsetsAnimationController is null." +
315
+ "This should only be called if isAnimationInProgress() returns true",
316
+ )
315
317
 
316
318
  return controller.currentInsets.bottom
317
319
  }
@@ -356,37 +358,40 @@ internal class KeyboardAnimationController {
356
358
  velocityY: Float? = null,
357
359
  ) {
358
360
  @Suppress("detekt:UseCheckOrError")
359
- val controller = insetsAnimationController
360
- ?: throw IllegalStateException("Controller should not be null")
361
-
362
- currentSpringAnimation = springAnimationOf(
363
- setter = {
364
- insetTo(it.roundToInt())
365
- },
366
- getter = { controller.currentInsets.bottom.toFloat() },
367
- finalPosition = when {
368
- visible -> controller.shownStateInsets.bottom.toFloat()
369
- else -> controller.hiddenStateInsets.bottom.toFloat()
370
- },
371
- ).withSpringForceProperties {
372
- // Tweak the damping value, to remove any bounciness.
373
- dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
374
- // The stiffness value controls the strength of the spring animation, which
375
- // controls the speed. Medium (the default) is a good value, but feel free to
376
- // play around with this value.
377
- stiffness = SpringForce.STIFFNESS_MEDIUM
378
- }.apply {
379
- if (velocityY != null) {
380
- setStartVelocity(velocityY)
381
- }
382
- addEndListener { anim, _, _, _ ->
383
- if (anim == currentSpringAnimation) {
384
- currentSpringAnimation = null
361
+ val controller =
362
+ insetsAnimationController
363
+ ?: throw IllegalStateException("Controller should not be null")
364
+
365
+ currentSpringAnimation =
366
+ springAnimationOf(
367
+ setter = {
368
+ insetTo(it.roundToInt())
369
+ },
370
+ getter = { controller.currentInsets.bottom.toFloat() },
371
+ finalPosition =
372
+ when {
373
+ visible -> controller.shownStateInsets.bottom.toFloat()
374
+ else -> controller.hiddenStateInsets.bottom.toFloat()
375
+ },
376
+ ).withSpringForceProperties {
377
+ // Tweak the damping value, to remove any bounciness.
378
+ dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
379
+ // The stiffness value controls the strength of the spring animation, which
380
+ // controls the speed. Medium (the default) is a good value, but feel free to
381
+ // play around with this value.
382
+ stiffness = SpringForce.STIFFNESS_MEDIUM
383
+ }.apply {
384
+ if (velocityY != null) {
385
+ setStartVelocity(velocityY)
385
386
  }
386
- // Once the animation has ended, finish the controller
387
- finish()
388
- }
389
- }.also { it.start() }
387
+ addEndListener { anim, _, _, _ ->
388
+ if (anim == currentSpringAnimation) {
389
+ currentSpringAnimation = null
390
+ }
391
+ // Once the animation has ended, finish the controller
392
+ finish()
393
+ }
394
+ }.also { it.start() }
390
395
  }
391
396
  }
392
397
 
@@ -11,5 +11,10 @@ interface Interpolator {
11
11
  * @param offset extra space to the keyboard to activate a gesture
12
12
  * @return the distance the keyboard should be moved from its current location.
13
13
  */
14
- fun interpolate(dy: Int, absoluteFingerPosition: Int, keyboardPosition: Int, offset: Int): Int
14
+ fun interpolate(
15
+ dy: Int,
16
+ absoluteFingerPosition: Int,
17
+ keyboardPosition: Int,
18
+ offset: Int,
19
+ ): Int
15
20
  }
@@ -8,8 +8,10 @@ class IosInterpolator : Interpolator {
8
8
  offset: Int,
9
9
  ): Int {
10
10
  if (
11
- absoluteFingerPosition <= keyboardPosition + offset || // user over scrolled keyboard
12
- dy <= 0 // user scrolls up
11
+ // user over scrolled keyboard
12
+ absoluteFingerPosition <= keyboardPosition + offset ||
13
+ // user scrolls up
14
+ dy <= 0
13
15
  ) {
14
16
  return dy
15
17
  }
@@ -6,7 +6,5 @@ class LinearInterpolator : Interpolator {
6
6
  absoluteFingerPosition: Int,
7
7
  keyboardPosition: Int,
8
8
  offset: Int,
9
- ): Int {
10
- return dy
11
- }
9
+ ): Int = dy
12
10
  }
@@ -25,16 +25,17 @@ import com.reactnativekeyboardcontroller.extensions.screenLocation
25
25
  import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder
26
26
  import com.reactnativekeyboardcontroller.traversal.ViewHierarchyNavigator
27
27
 
28
- val noFocusedInputEvent = FocusedInputLayoutChangedEventData(
29
- x = 0.0,
30
- y = 0.0,
31
- width = 0.0,
32
- height = 0.0,
33
- absoluteX = 0.0,
34
- absoluteY = 0.0,
35
- target = -1,
36
- parentScrollViewTarget = -1,
37
- )
28
+ val noFocusedInputEvent =
29
+ FocusedInputLayoutChangedEventData(
30
+ x = 0.0,
31
+ y = 0.0,
32
+ width = 0.0,
33
+ height = 0.0,
34
+ absoluteX = 0.0,
35
+ absoluteY = 0.0,
36
+ target = -1,
37
+ parentScrollViewTarget = -1,
38
+ )
38
39
 
39
40
  class FocusedInputObserver(
40
41
  val view: View,
@@ -82,50 +83,52 @@ class FocusedInputObserver(
82
83
  FocusedInputSelectionChangedEvent(
83
84
  surfaceId,
84
85
  eventPropagationView.id,
85
- event = FocusedInputSelectionChangedEventData(
86
- target = input.id,
87
- start = start,
88
- end = end,
89
- startX = startX,
90
- startY = startY,
91
- endX = endX,
92
- endY = endY,
93
- ),
86
+ event =
87
+ FocusedInputSelectionChangedEventData(
88
+ target = input.id,
89
+ start = start,
90
+ end = end,
91
+ startX = startX,
92
+ startY = startY,
93
+ endX = endX,
94
+ endY = endY,
95
+ ),
94
96
  ),
95
97
  )
96
98
  }
97
- private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
98
- // unfocused or focus was changed
99
- if (newFocus == null || oldFocus != null) {
100
- lastFocusedInput?.removeOnLayoutChangeListener(layoutListener)
101
- lastFocusedInput?.removeTextChangedListener(textWatcher)
102
- selectionSubscription?.invoke()
103
- lastFocusedInput = null
104
- }
105
- if (newFocus is ReactEditText) {
106
- lastFocusedInput = newFocus
107
- newFocus.addOnLayoutChangeListener(layoutListener)
108
- this.syncUpLayout()
109
- textWatcher = newFocus.addOnTextChangedListener(textListener)
110
- selectionSubscription = newFocus.addOnSelectionChangedListener(selectionListener)
111
- FocusedInputHolder.set(newFocus)
99
+ private val focusListener =
100
+ OnGlobalFocusChangeListener { oldFocus, newFocus ->
101
+ // unfocused or focus was changed
102
+ if (newFocus == null || oldFocus != null) {
103
+ lastFocusedInput?.removeOnLayoutChangeListener(layoutListener)
104
+ lastFocusedInput?.removeTextChangedListener(textWatcher)
105
+ selectionSubscription?.invoke()
106
+ lastFocusedInput = null
107
+ }
108
+ if (newFocus is ReactEditText) {
109
+ lastFocusedInput = newFocus
110
+ newFocus.addOnLayoutChangeListener(layoutListener)
111
+ this.syncUpLayout()
112
+ textWatcher = newFocus.addOnTextChangedListener(textListener)
113
+ selectionSubscription = newFocus.addOnSelectionChangedListener(selectionListener)
114
+ FocusedInputHolder.set(newFocus)
112
115
 
113
- val allInputFields = ViewHierarchyNavigator.getAllInputFields(context?.rootView)
114
- val currentIndex = allInputFields.indexOf(newFocus)
116
+ val allInputFields = ViewHierarchyNavigator.getAllInputFields(context?.rootView)
117
+ val currentIndex = allInputFields.indexOf(newFocus)
115
118
 
116
- context.emitEvent(
117
- "KeyboardController::focusDidSet",
118
- Arguments.createMap().apply {
119
- putInt("current", currentIndex)
120
- putInt("count", allInputFields.size)
121
- },
122
- )
119
+ context.emitEvent(
120
+ "KeyboardController::focusDidSet",
121
+ Arguments.createMap().apply {
122
+ putInt("current", currentIndex)
123
+ putInt("count", allInputFields.size)
124
+ },
125
+ )
126
+ }
127
+ // unfocused
128
+ if (newFocus == null) {
129
+ dispatchEventToJS(noFocusedInputEvent)
130
+ }
123
131
  }
124
- // unfocused
125
- if (newFocus == null) {
126
- dispatchEventToJS(noFocusedInputEvent)
127
- }
128
- }
129
132
 
130
133
  init {
131
134
  view.viewTreeObserver.addOnGlobalFocusChangeListener(focusListener)
@@ -135,16 +138,17 @@ class FocusedInputObserver(
135
138
  val input = lastFocusedInput ?: return
136
139
 
137
140
  val (x, y) = input.screenLocation
138
- val event = FocusedInputLayoutChangedEventData(
139
- x = input.x.dp,
140
- y = input.y.dp,
141
- width = input.width.toFloat().dp,
142
- height = input.height.toFloat().dp,
143
- absoluteX = x.toFloat().dp,
144
- absoluteY = y.toFloat().dp,
145
- target = input.id,
146
- parentScrollViewTarget = input.parentScrollViewTarget,
147
- )
141
+ val event =
142
+ FocusedInputLayoutChangedEventData(
143
+ x = input.x.dp,
144
+ y = input.y.dp,
145
+ width = input.width.toFloat().dp,
146
+ height = input.height.toFloat().dp,
147
+ absoluteX = x.toFloat().dp,
148
+ absoluteY = y.toFloat().dp,
149
+ target = input.id,
150
+ parentScrollViewTarget = input.parentScrollViewTarget,
151
+ )
148
152
 
149
153
  dispatchEventToJS(event)
150
154
  }
@@ -1,6 +1,5 @@
1
1
  package com.reactnativekeyboardcontroller.listeners
2
2
 
3
- import android.os.Build
4
3
  import android.view.View
5
4
  import android.view.ViewTreeObserver.OnGlobalFocusChangeListener
6
5
  import androidx.core.graphics.Insets
@@ -14,6 +13,7 @@ import com.facebook.react.uimanager.ThemedReactContext
14
13
  import com.facebook.react.uimanager.UIManagerHelper
15
14
  import com.facebook.react.views.textinput.ReactEditText
16
15
  import com.facebook.react.views.view.ReactViewGroup
16
+ import com.reactnativekeyboardcontroller.constants.Keyboard
17
17
  import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
18
18
  import com.reactnativekeyboardcontroller.extensions.dispatchEvent
19
19
  import com.reactnativekeyboardcontroller.extensions.dp
@@ -24,7 +24,7 @@ import com.reactnativekeyboardcontroller.log.Logger
24
24
  import kotlin.math.abs
25
25
 
26
26
  private val TAG = KeyboardAnimationCallback::class.qualifiedName
27
- private val isResizeHandledInCallbackMethods = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
27
+ private val isResizeHandledInCallbackMethods = Keyboard.IS_ANIMATION_EMULATED
28
28
 
29
29
  data class KeyboardAnimationCallbackConfig(
30
30
  val persistentInsetTypes: Int,
@@ -33,12 +33,22 @@ data class KeyboardAnimationCallbackConfig(
33
33
  val hasTranslucentNavigationBar: Boolean = false,
34
34
  )
35
35
 
36
+ interface Suspendable {
37
+ var isSuspended: Boolean
38
+
39
+ fun suspend(suspended: Boolean) {
40
+ isSuspended = suspended
41
+ }
42
+ }
43
+
36
44
  class KeyboardAnimationCallback(
37
45
  val eventPropagationView: ReactViewGroup,
38
46
  val view: View,
39
47
  val context: ThemedReactContext?,
40
48
  private val config: KeyboardAnimationCallbackConfig,
41
- ) : WindowInsetsAnimationCompat.Callback(config.dispatchMode), OnApplyWindowInsetsListener {
49
+ ) : WindowInsetsAnimationCompat.Callback(config.dispatchMode),
50
+ OnApplyWindowInsetsListener,
51
+ Suspendable {
42
52
  private val surfaceId = UIManagerHelper.getSurfaceId(eventPropagationView)
43
53
 
44
54
  // state variables
@@ -49,48 +59,50 @@ class KeyboardAnimationCallback(
49
59
  private var duration = 0
50
60
  private var viewTagFocused = -1
51
61
  private var animationsToSkip = hashSetOf<WindowInsetsAnimationCompat>()
62
+ override var isSuspended: Boolean = false
52
63
 
53
64
  // listeners
54
- private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
55
- if (newFocus is ReactEditText) {
56
- viewTagFocused = newFocus.id
57
-
58
- // keyboard is visible and focus has been changed
59
- if (this.isKeyboardVisible && oldFocus !== null) {
60
- // imitate iOS behavior and send two instant start/end events containing an info about new tag
61
- // 1. onStart/onMove/onEnd can be still dispatched after, if keyboard change size (numeric -> alphabetic type)
62
- // 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
63
- // 100% included in onStart/onMove/onEnd life cycles, but triggering onStart/onEnd several time
64
- // can bring breaking changes
65
- context.dispatchEvent(
66
- eventPropagationView.id,
67
- KeyboardTransitionEvent(
68
- surfaceId,
65
+ private val focusListener =
66
+ OnGlobalFocusChangeListener { oldFocus, newFocus ->
67
+ if (newFocus is ReactEditText) {
68
+ viewTagFocused = newFocus.id
69
+
70
+ // keyboard is visible and focus has been changed
71
+ if (this.isKeyboardVisible && oldFocus !== null) {
72
+ // imitate iOS behavior and send two instant start/end events containing an info about new tag
73
+ // 1. onStart/onMove/onEnd can be still dispatched after, if keyboard change size (numeric -> alphabetic type)
74
+ // 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
75
+ // 100% included in onStart/onMove/onEnd life cycles, but triggering onStart/onEnd several time
76
+ // can bring breaking changes
77
+ context.dispatchEvent(
69
78
  eventPropagationView.id,
70
- "topKeyboardMoveStart",
71
- this.persistentKeyboardHeight,
72
- 1.0,
73
- 0,
74
- viewTagFocused,
75
- ),
76
- )
77
- context.dispatchEvent(
78
- eventPropagationView.id,
79
- KeyboardTransitionEvent(
80
- surfaceId,
79
+ KeyboardTransitionEvent(
80
+ surfaceId,
81
+ eventPropagationView.id,
82
+ "topKeyboardMoveStart",
83
+ this.persistentKeyboardHeight,
84
+ 1.0,
85
+ 0,
86
+ viewTagFocused,
87
+ ),
88
+ )
89
+ context.dispatchEvent(
81
90
  eventPropagationView.id,
82
- "topKeyboardMoveEnd",
83
- this.persistentKeyboardHeight,
84
- 1.0,
85
- 0,
86
- viewTagFocused,
87
- ),
88
- )
89
- context.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
90
- context.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
91
+ KeyboardTransitionEvent(
92
+ surfaceId,
93
+ eventPropagationView.id,
94
+ "topKeyboardMoveEnd",
95
+ this.persistentKeyboardHeight,
96
+ 1.0,
97
+ 0,
98
+ viewTagFocused,
99
+ ),
100
+ )
101
+ context.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
102
+ context.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
103
+ }
91
104
  }
92
105
  }
93
- }
94
106
  private var layoutObserver: FocusedInputObserver? = null
95
107
 
96
108
  init {
@@ -113,7 +125,10 @@ class KeyboardAnimationCallback(
113
125
  * have to implement `onApplyWindowInsets` listener and simulate onStart/onProgress/onEnd
114
126
  * events when keyboard changes its size.
115
127
  */
116
- override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
128
+ override fun onApplyWindowInsets(
129
+ v: View,
130
+ insets: WindowInsetsCompat,
131
+ ): WindowInsetsCompat {
117
132
  val keyboardHeight = getCurrentKeyboardHeight()
118
133
  // when keyboard appears values will be (false && true)
119
134
  // when keyboard disappears values will be (true && false)
@@ -142,7 +157,7 @@ class KeyboardAnimationCallback(
142
157
  this.onKeyboardResized(keyboardHeight)
143
158
  }
144
159
 
145
- return WindowInsetsCompat.CONSUMED
160
+ return insets
146
161
  }
147
162
 
148
163
  @Suppress("detekt:ReturnCount")
@@ -150,7 +165,7 @@ class KeyboardAnimationCallback(
150
165
  animation: WindowInsetsAnimationCompat,
151
166
  bounds: WindowInsetsAnimationCompat.BoundsCompat,
152
167
  ): WindowInsetsAnimationCompat.BoundsCompat {
153
- if (!animation.isKeyboardAnimation) {
168
+ if (!animation.isKeyboardAnimation || isSuspended) {
154
169
  return bounds
155
170
  }
156
171
 
@@ -206,7 +221,13 @@ class KeyboardAnimationCallback(
206
221
  // onProgress() is called when any of the running animations progress...
207
222
 
208
223
  // ignore non-keyboard animation or animation that we intentionally want to skip
209
- runningAnimations.find { it.isKeyboardAnimation && !animationsToSkip.contains(it) } ?: return insets
224
+ val shouldSkipAnimation =
225
+ runningAnimations.find {
226
+ it.isKeyboardAnimation && !animationsToSkip.contains(it)
227
+ } == null
228
+ if (isSuspended || shouldSkipAnimation) {
229
+ return insets
230
+ }
210
231
 
211
232
  // First we get the insets which are potentially deferred
212
233
  val typesInset = insets.getInsets(config.deferredInsetTypes)
@@ -218,9 +239,10 @@ class KeyboardAnimationCallback(
218
239
  if (config.hasTranslucentNavigationBar) {
219
240
  otherInset = Insets.NONE
220
241
  }
221
- val diff = Insets.subtract(typesInset, otherInset).let {
222
- Insets.max(it, Insets.NONE)
223
- }
242
+ val diff =
243
+ Insets.subtract(typesInset, otherInset).let {
244
+ Insets.max(it, Insets.NONE)
245
+ }
224
246
  val diffY = (diff.bottom - diff.top).toFloat()
225
247
  val height = diffY.dp
226
248
 
@@ -256,7 +278,7 @@ class KeyboardAnimationCallback(
256
278
  override fun onEnd(animation: WindowInsetsAnimationCompat) {
257
279
  super.onEnd(animation)
258
280
 
259
- if (!animation.isKeyboardAnimation) {
281
+ if (!animation.isKeyboardAnimation || isSuspended) {
260
282
  return
261
283
  }
262
284
 
@@ -305,10 +327,13 @@ class KeyboardAnimationCallback(
305
327
  duration = 0
306
328
  }
307
329
 
308
- fun syncKeyboardPosition() {
309
- val keyboardHeight = getCurrentKeyboardHeight()
330
+ fun syncKeyboardPosition(
331
+ height: Double? = null,
332
+ isVisible: Boolean? = null,
333
+ ) {
334
+ val keyboardHeight = height ?: getCurrentKeyboardHeight()
310
335
  // update internal state
311
- isKeyboardVisible = isKeyboardVisible()
336
+ isKeyboardVisible = isVisible ?: isKeyboardVisible()
312
337
  prevKeyboardHeight = keyboardHeight
313
338
  isTransitioning = false
314
339
  duration = 0