react-native-keyboard-controller 1.5.7 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +3 -1
  2. package/android/src/fabric/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +2 -1
  3. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationCallback.kt +130 -18
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationController.kt +10 -2
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/events/KeyboardTransitionEvent.kt +11 -2
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +7 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -1
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +1 -0
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +6 -1
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/StatusBarManagerCompatModuleImpl.kt +9 -3
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +44 -27
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardGestureAreaReactViewGroup.kt +121 -85
  13. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +6 -4
  14. package/android/src/paper/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +2 -1
  15. package/ios/Extensions.swift +31 -0
  16. package/ios/KeyboardController-Bridging-Header.h +1 -0
  17. package/ios/KeyboardControllerView.mm +25 -6
  18. package/ios/KeyboardControllerViewManager.swift +4 -2
  19. package/ios/KeyboardMoveEvent.h +3 -1
  20. package/ios/KeyboardMoveEvent.m +8 -0
  21. package/ios/KeyboardMovementObserver.swift +64 -12
  22. package/lib/commonjs/animated.js +4 -3
  23. package/lib/commonjs/animated.js.map +1 -1
  24. package/lib/commonjs/bindings.js +26 -0
  25. package/lib/commonjs/bindings.js.map +1 -0
  26. package/lib/commonjs/bindings.native.js +33 -0
  27. package/lib/commonjs/bindings.native.js.map +1 -0
  28. package/lib/commonjs/{native.js → constants.js} +2 -29
  29. package/lib/commonjs/constants.js.map +1 -0
  30. package/lib/commonjs/context.js +9 -1
  31. package/lib/commonjs/context.js.map +1 -1
  32. package/lib/commonjs/hooks.js +7 -6
  33. package/lib/commonjs/hooks.js.map +1 -1
  34. package/lib/commonjs/index.js +15 -4
  35. package/lib/commonjs/index.js.map +1 -1
  36. package/lib/commonjs/internal.js +0 -30
  37. package/lib/commonjs/internal.js.map +1 -1
  38. package/lib/commonjs/monkey-patch.android.js +34 -0
  39. package/lib/commonjs/monkey-patch.android.js.map +1 -0
  40. package/lib/commonjs/monkey-patch.js +1 -35
  41. package/lib/commonjs/monkey-patch.js.map +1 -1
  42. package/lib/commonjs/reanimated.js +9 -0
  43. package/lib/commonjs/reanimated.js.map +1 -0
  44. package/lib/commonjs/reanimated.native.js +37 -0
  45. package/lib/commonjs/reanimated.native.js.map +1 -0
  46. package/lib/commonjs/replicas.js +4 -3
  47. package/lib/commonjs/replicas.js.map +1 -1
  48. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  49. package/lib/commonjs/types.js.map +1 -1
  50. package/lib/module/animated.js +3 -2
  51. package/lib/module/animated.js.map +1 -1
  52. package/lib/module/bindings.js +16 -0
  53. package/lib/module/bindings.js.map +1 -0
  54. package/lib/module/bindings.native.js +23 -0
  55. package/lib/module/bindings.native.js.map +1 -0
  56. package/lib/module/{native.js → constants.js} +1 -24
  57. package/lib/module/constants.js.map +1 -0
  58. package/lib/module/context.js +8 -1
  59. package/lib/module/context.js.map +1 -1
  60. package/lib/module/hooks.js +7 -6
  61. package/lib/module/hooks.js.map +1 -1
  62. package/lib/module/index.js +2 -1
  63. package/lib/module/index.js.map +1 -1
  64. package/lib/module/internal.js +1 -30
  65. package/lib/module/internal.js.map +1 -1
  66. package/lib/module/monkey-patch.android.js +29 -0
  67. package/lib/module/monkey-patch.android.js.map +1 -0
  68. package/lib/module/monkey-patch.js +1 -31
  69. package/lib/module/monkey-patch.js.map +1 -1
  70. package/lib/module/reanimated.js +2 -0
  71. package/lib/module/reanimated.js.map +1 -0
  72. package/lib/module/reanimated.native.js +30 -0
  73. package/lib/module/reanimated.native.js.map +1 -0
  74. package/lib/module/replicas.js +2 -1
  75. package/lib/module/replicas.js.map +1 -1
  76. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  77. package/lib/module/types.js.map +1 -1
  78. package/lib/typescript/bindings.d.ts +6 -0
  79. package/lib/typescript/bindings.native.d.ts +6 -0
  80. package/lib/typescript/constants.d.ts +16 -0
  81. package/lib/typescript/context.d.ts +1 -0
  82. package/lib/typescript/index.d.ts +2 -1
  83. package/lib/typescript/internal.d.ts +1 -7
  84. package/lib/typescript/monkey-patch.android.d.ts +1 -0
  85. package/lib/typescript/monkey-patch.d.ts +0 -1
  86. package/lib/typescript/reanimated.d.ts +2 -0
  87. package/lib/typescript/reanimated.native.d.ts +2 -0
  88. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +3 -1
  89. package/lib/typescript/types.d.ts +15 -1
  90. package/package.json +1 -1
  91. package/react-native-keyboard-controller.podspec +9 -1
  92. package/src/animated.tsx +3 -6
  93. package/src/{native.ts → bindings.native.ts} +4 -25
  94. package/src/bindings.ts +22 -0
  95. package/src/constants.ts +17 -0
  96. package/src/context.ts +12 -1
  97. package/src/hooks.ts +12 -7
  98. package/src/index.ts +2 -1
  99. package/src/internal.ts +2 -57
  100. package/src/monkey-patch.android.ts +33 -0
  101. package/src/monkey-patch.ts +1 -37
  102. package/src/reanimated.native.ts +51 -0
  103. package/src/reanimated.ts +6 -0
  104. package/src/replicas.ts +2 -1
  105. package/src/specs/KeyboardControllerViewNativeComponent.ts +3 -0
  106. package/src/types.ts +27 -5
  107. package/lib/commonjs/native.js.map +0 -1
  108. package/lib/module/native.js.map +0 -1
  109. package/lib/typescript/native.d.ts +0 -24
@@ -11,7 +11,9 @@ import com.facebook.react.views.view.ReactViewManager
11
11
  import com.reactnativekeyboardcontroller.managers.KeyboardControllerViewManagerImpl
12
12
  import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
13
13
 
14
- class KeyboardControllerViewManager(mReactContext: ReactApplicationContext) : ReactViewManager(), KeyboardControllerViewManagerInterface<ReactViewGroup> {
14
+ class KeyboardControllerViewManager(mReactContext: ReactApplicationContext) :
15
+ ReactViewManager(),
16
+ KeyboardControllerViewManagerInterface<ReactViewGroup> {
15
17
  private val manager = KeyboardControllerViewManagerImpl(mReactContext)
16
18
  private val mDelegate = KeyboardControllerViewManagerDelegate(this)
17
19
 
@@ -3,7 +3,8 @@ package com.reactnativekeyboardcontroller
3
3
  import com.facebook.react.bridge.ReactApplicationContext
4
4
  import com.reactnativekeyboardcontroller.modules.StatusBarManagerCompatModuleImpl
5
5
 
6
- class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationContext) : NativeStatusBarManagerCompatSpec(mReactContext) {
6
+ class StatusBarManagerCompatModule(mReactContext: ReactApplicationContext) :
7
+ NativeStatusBarManagerCompatSpec(mReactContext) {
7
8
  private val module = StatusBarManagerCompatModuleImpl(mReactContext)
8
9
 
9
10
  override fun getName(): String = StatusBarManagerCompatModuleImpl.NAME
@@ -17,29 +17,69 @@ import com.facebook.react.uimanager.ThemedReactContext
17
17
  import com.facebook.react.uimanager.UIManagerHelper
18
18
  import com.facebook.react.uimanager.events.Event
19
19
  import com.facebook.react.uimanager.events.EventDispatcher
20
+ import com.facebook.react.views.textinput.ReactEditText
20
21
  import com.facebook.react.views.view.ReactViewGroup
21
22
  import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
22
23
  import com.reactnativekeyboardcontroller.extensions.dp
23
24
  import kotlin.math.abs
24
25
 
26
+ private val TAG = KeyboardAnimationCallback::class.qualifiedName
27
+
25
28
  class KeyboardAnimationCallback(
26
29
  val view: ReactViewGroup,
27
30
  val persistentInsetTypes: Int,
28
31
  val deferredInsetTypes: Int,
29
32
  dispatchMode: Int = DISPATCH_MODE_STOP,
30
33
  val context: ThemedReactContext?,
31
- val onApplyWindowInsetsListener: OnApplyWindowInsetsListener,
32
34
  ) : WindowInsetsAnimationCompat.Callback(dispatchMode), OnApplyWindowInsetsListener {
33
- private val TAG = KeyboardAnimationCallback::class.qualifiedName
34
35
  private var persistentKeyboardHeight = 0.0
35
36
  private var isKeyboardVisible = false
36
37
  private var isTransitioning = false
38
+ private var duration = 0
39
+ private var viewTagFocused = -1
37
40
 
38
41
  init {
39
42
  require(persistentInsetTypes and deferredInsetTypes == 0) {
40
43
  "persistentInsetTypes and deferredInsetTypes can not contain any of " +
41
44
  " same WindowInsetsCompat.Type values"
42
45
  }
46
+
47
+ view.viewTreeObserver.addOnGlobalFocusChangeListener { oldFocus, newFocus ->
48
+ if (newFocus is ReactEditText) {
49
+ viewTagFocused = newFocus.id
50
+
51
+ // keyboard is visible and focus has been changed
52
+ if (this.isKeyboardVisible && oldFocus !== null) {
53
+ // imitate iOS behavior and send two instant start/end events containing an info about new tag
54
+ // 1. onStart/onMove/onEnd can be still dispatched after, if keyboard change size (numeric -> alphabetic type)
55
+ // 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
56
+ // 100% included in onStart/onMove/onEnd lifecycles, but triggering onStart/onEnd several time
57
+ // can bring breaking changes
58
+ this.sendEventToJS(
59
+ KeyboardTransitionEvent(
60
+ view.id,
61
+ "topKeyboardMoveStart",
62
+ this.persistentKeyboardHeight,
63
+ 1.0,
64
+ 0,
65
+ viewTagFocused,
66
+ ),
67
+ )
68
+ this.sendEventToJS(
69
+ KeyboardTransitionEvent(
70
+ view.id,
71
+ "topKeyboardMoveEnd",
72
+ this.persistentKeyboardHeight,
73
+ 1.0,
74
+ 0,
75
+ viewTagFocused,
76
+ ),
77
+ )
78
+ this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
79
+ this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
80
+ }
81
+ }
82
+ }
43
83
  }
44
84
 
45
85
  /**
@@ -59,32 +99,65 @@ class KeyboardAnimationCallback(
59
99
  override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
60
100
  // when keyboard appears values will be (false && true)
61
101
  // when keyboard disappears values will be (true && false)
62
- // `!isTransitioning` check is needed to avoid calls of `onApplyWindowInsets` during keyboard animation
102
+ val isKeyboardShown = isKeyboardVisible && isKeyboardVisible()
103
+ // `isTransitioning` check is needed to avoid calls of `onApplyWindowInsets` during keyboard animation
63
104
  // having such check allows us not to dispatch unnecessary incorrect events
64
105
  // the condition will be executed only when keyboard is opened and changes its size
65
106
  // (for example it happens when user changes keyboard type from 'text' to 'emoji' input
66
- if (isKeyboardVisible && isKeyboardVisible() && !isTransitioning && Build.VERSION.SDK_INT >= 30 && !InteractiveKeyboardProvider.isInteractive) {
107
+ //
108
+ // `InteractiveKeyboardProvider.isInteractive` detect case when keyboard moves
109
+ // because of the gesture
110
+ val isMoving = isTransitioning || InteractiveKeyboardProvider.isInteractive
111
+ if (isKeyboardShown && !isMoving && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
67
112
  val keyboardHeight = getCurrentKeyboardHeight()
113
+ val duration = DEFAULT_ANIMATION_TIME.toInt()
68
114
 
69
115
  this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
70
- this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveStart", keyboardHeight, 1.0))
116
+ this.sendEventToJS(
117
+ KeyboardTransitionEvent(
118
+ view.id,
119
+ "topKeyboardMoveStart",
120
+ keyboardHeight,
121
+ 1.0,
122
+ duration,
123
+ viewTagFocused,
124
+ ),
125
+ )
71
126
 
72
127
  val animation = ValueAnimator.ofFloat(this.persistentKeyboardHeight.toFloat(), keyboardHeight.toFloat())
73
128
  animation.addUpdateListener { animator ->
74
129
  val toValue = animator.animatedValue as Float
75
- this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMove", toValue.toDouble(), toValue.toDouble() / keyboardHeight))
130
+ this.sendEventToJS(
131
+ KeyboardTransitionEvent(
132
+ view.id,
133
+ "topKeyboardMove",
134
+ toValue.toDouble(),
135
+ toValue.toDouble() / keyboardHeight,
136
+ duration,
137
+ viewTagFocused,
138
+ ),
139
+ )
76
140
  }
77
141
  animation.doOnEnd {
78
142
  this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
79
- this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveEnd", keyboardHeight, 1.0))
143
+ this.sendEventToJS(
144
+ KeyboardTransitionEvent(
145
+ view.id,
146
+ "topKeyboardMoveEnd",
147
+ keyboardHeight,
148
+ 1.0,
149
+ duration,
150
+ viewTagFocused,
151
+ ),
152
+ )
80
153
  }
81
- animation.setDuration(250).startDelay = 0
154
+ animation.setDuration(DEFAULT_ANIMATION_TIME).startDelay = 0
82
155
  animation.start()
83
156
 
84
157
  this.persistentKeyboardHeight = keyboardHeight
85
158
  }
86
159
 
87
- return onApplyWindowInsetsListener.onApplyWindowInsets(v, insets)
160
+ return insets
88
161
  }
89
162
 
90
163
  override fun onStart(
@@ -93,6 +166,7 @@ class KeyboardAnimationCallback(
93
166
  ): WindowInsetsAnimationCompat.BoundsCompat {
94
167
  isTransitioning = true
95
168
  isKeyboardVisible = isKeyboardVisible()
169
+ duration = animation.durationMillis.toInt()
96
170
  val keyboardHeight = getCurrentKeyboardHeight()
97
171
 
98
172
  if (isKeyboardVisible) {
@@ -100,10 +174,22 @@ class KeyboardAnimationCallback(
100
174
  this.persistentKeyboardHeight = keyboardHeight
101
175
  }
102
176
 
103
- this.emitEvent("KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow", getEventParams(keyboardHeight))
177
+ this.emitEvent(
178
+ "KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow",
179
+ getEventParams(keyboardHeight),
180
+ )
104
181
 
105
- Log.i(TAG, "HEIGHT:: $keyboardHeight")
106
- this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveStart", keyboardHeight, if (!isKeyboardVisible) 0.0 else 1.0))
182
+ Log.i(TAG, "HEIGHT:: $keyboardHeight TAG:: $viewTagFocused")
183
+ this.sendEventToJS(
184
+ KeyboardTransitionEvent(
185
+ view.id,
186
+ "topKeyboardMoveStart",
187
+ keyboardHeight,
188
+ if (!isKeyboardVisible) 0.0 else 1.0,
189
+ duration,
190
+ viewTagFocused,
191
+ ),
192
+ )
107
193
 
108
194
  return super.onStart(animation, bounds)
109
195
  }
@@ -131,12 +217,13 @@ class KeyboardAnimationCallback(
131
217
  try {
132
218
  progress = abs((height / persistentKeyboardHeight)).let { if (it.isNaN()) 0.0 else it }
133
219
  } catch (e: ArithmeticException) {
134
- // do nothing, send progress as 0
220
+ // do nothing, just log an exception send progress as 0
221
+ Log.w(TAG, "Caught arithmetic exception during `progress` calculation: $e")
135
222
  }
136
- Log.i(TAG, "DiffY: $diffY $height $progress ${InteractiveKeyboardProvider.isInteractive}")
223
+ Log.i(TAG, "DiffY: $diffY $height $progress ${InteractiveKeyboardProvider.isInteractive} $viewTagFocused")
137
224
 
138
225
  val event = if (InteractiveKeyboardProvider.isInteractive) "topKeyboardMoveInteractive" else "topKeyboardMove"
139
- this.sendEventToJS(KeyboardTransitionEvent(view.id, event, height, progress))
226
+ this.sendEventToJS(KeyboardTransitionEvent(view.id, event, height, progress, duration, viewTagFocused))
140
227
 
141
228
  return insets
142
229
  }
@@ -145,12 +232,15 @@ class KeyboardAnimationCallback(
145
232
  super.onEnd(animation)
146
233
 
147
234
  isTransitioning = false
235
+ duration = animation.durationMillis.toInt()
236
+
237
+ var keyboardHeight = this.persistentKeyboardHeight
148
238
  // if keyboard becomes shown after interactive animation completion
149
239
  // getCurrentKeyboardHeight() will be `0` and isKeyboardVisible will be `false`
150
240
  // it's not correct behavior, so we are handling it here
151
241
  val isKeyboardShown = InteractiveKeyboardProvider.shown
152
242
  if (!isKeyboardShown) {
153
- this.persistentKeyboardHeight = getCurrentKeyboardHeight()
243
+ keyboardHeight = getCurrentKeyboardHeight()
154
244
  } else {
155
245
  // if keyboard is shown after interactions and the animation has finished
156
246
  // then we need to reset the state
@@ -158,8 +248,23 @@ class KeyboardAnimationCallback(
158
248
  }
159
249
  isKeyboardVisible = isKeyboardVisible || isKeyboardShown
160
250
 
161
- this.emitEvent("KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
162
- this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveEnd", this.persistentKeyboardHeight, if (!isKeyboardVisible) 0.0 else 1.0))
251
+ this.emitEvent(
252
+ "KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow",
253
+ getEventParams(keyboardHeight),
254
+ )
255
+ this.sendEventToJS(
256
+ KeyboardTransitionEvent(
257
+ view.id,
258
+ "topKeyboardMoveEnd",
259
+ keyboardHeight,
260
+ if (!isKeyboardVisible) 0.0 else 1.0,
261
+ duration,
262
+ viewTagFocused,
263
+ ),
264
+ )
265
+
266
+ // reset to initial state
267
+ duration = 0
163
268
  }
164
269
 
165
270
  private fun isKeyboardVisible(): Boolean {
@@ -192,7 +297,14 @@ class KeyboardAnimationCallback(
192
297
  private fun getEventParams(height: Double): WritableMap {
193
298
  val params: WritableMap = Arguments.createMap()
194
299
  params.putDouble("height", height)
300
+ params.putInt("duration", duration)
301
+ params.putDouble("timestamp", System.currentTimeMillis().toDouble())
302
+ params.putInt("target", viewTagFocused)
195
303
 
196
304
  return params
197
305
  }
306
+
307
+ companion object {
308
+ private const val DEFAULT_ANIMATION_TIME = 250L
309
+ }
198
310
  }
@@ -14,6 +14,7 @@ import androidx.dynamicanimation.animation.springAnimationOf
14
14
  import androidx.dynamicanimation.animation.withSpringForceProperties
15
15
  import kotlin.math.roundToInt
16
16
 
17
+ @Suppress("detekt:TooManyFunctions")
17
18
  internal class KeyboardAnimationController {
18
19
  private var insetsAnimationController: WindowInsetsAnimationControllerCompat? = null
19
20
  private var pendingRequestCancellationSignal: CancellationSignal? = null
@@ -284,8 +285,14 @@ internal class KeyboardAnimationController {
284
285
  velocityY = velocityY,
285
286
  )
286
287
  // The current inset matches either the shown/hidden inset, finish() immediately
287
- current == shown -> controller.finish(true)
288
- current == hidden -> controller.finish(false)
288
+ current == shown -> {
289
+ InteractiveKeyboardProvider.shown = true
290
+ controller.finish(true)
291
+ }
292
+ current == hidden -> {
293
+ InteractiveKeyboardProvider.shown = false
294
+ controller.finish(false)
295
+ }
289
296
  else -> {
290
297
  // Otherwise, we'll look at the current position...
291
298
  if (controller.currentFraction >= SCROLL_THRESHOLD) {
@@ -348,6 +355,7 @@ internal class KeyboardAnimationController {
348
355
  visible: Boolean,
349
356
  velocityY: Float? = null,
350
357
  ) {
358
+ @Suppress("detekt:UseCheckOrError")
351
359
  val controller = insetsAnimationController
352
360
  ?: throw IllegalStateException("Controller should not be null")
353
361
 
@@ -4,16 +4,25 @@ 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: Double, private val progress: Double) : Event<KeyboardTransitionEvent>(viewId) {
7
+ class KeyboardTransitionEvent(
8
+ viewId: Int,
9
+ private val event: String,
10
+ private val height: Double,
11
+ private val progress: Double,
12
+ private val duration: Int,
13
+ private val target: Int,
14
+ ) : Event<KeyboardTransitionEvent>(viewId) {
8
15
  override fun getEventName() = event
9
16
 
10
- // TODO: All events for a given view can be coalesced?
17
+ // All events for a given view can be coalesced?
11
18
  override fun getCoalescingKey(): Short = 0
12
19
 
13
20
  override fun dispatch(rctEventEmitter: RCTEventEmitter) {
14
21
  val map = Arguments.createMap()
15
22
  map.putDouble("progress", progress)
16
23
  map.putDouble("height", height)
24
+ map.putInt("duration", duration)
25
+ map.putInt("target", target)
17
26
  rctEventEmitter.receiveEvent(viewTag, eventName, map)
18
27
  }
19
28
  }
@@ -0,0 +1,7 @@
1
+ package com.reactnativekeyboardcontroller.extensions
2
+
3
+ import android.view.View
4
+ import com.facebook.react.uimanager.ThemedReactContext
5
+
6
+ val ThemedReactContext.rootView: View?
7
+ get() = this.currentActivity?.window?.decorView?.rootView
@@ -5,7 +5,8 @@ import com.facebook.react.common.MapBuilder
5
5
  import com.facebook.react.uimanager.ThemedReactContext
6
6
  import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
7
7
 
8
- class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicationContext) {
8
+ @Suppress("detekt:UnusedPrivateProperty")
9
+ class KeyboardControllerViewManagerImpl(mReactContext: ReactApplicationContext) {
9
10
  fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup {
10
11
  return EdgeToEdgeReactViewGroup(reactContext)
11
12
  }
@@ -4,6 +4,7 @@ import com.facebook.react.bridge.ReactApplicationContext
4
4
  import com.facebook.react.uimanager.ThemedReactContext
5
5
  import com.reactnativekeyboardcontroller.views.KeyboardGestureAreaReactViewGroup
6
6
 
7
+ @Suppress("detekt:UnusedPrivateProperty")
7
8
  class KeyboardGestureAreaViewManagerImpl(mReactContext: ReactApplicationContext) {
8
9
  fun createViewInstance(reactContext: ThemedReactContext): KeyboardGestureAreaReactViewGroup {
9
10
  return KeyboardGestureAreaReactViewGroup(reactContext)
@@ -5,7 +5,12 @@ import com.facebook.react.bridge.ReactApplicationContext
5
5
  import com.facebook.react.bridge.UiThreadUtil
6
6
 
7
7
  class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationContext) {
8
- private val mDefaultMode: Int = mReactContext.currentActivity?.window?.attributes?.softInputMode ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
8
+ private val mDefaultMode: Int = mReactContext
9
+ .currentActivity
10
+ ?.window
11
+ ?.attributes
12
+ ?.softInputMode
13
+ ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
9
14
 
10
15
  fun setInputMode(mode: Int) {
11
16
  setSoftInputMode(mode)
@@ -10,8 +10,9 @@ import androidx.core.view.WindowInsetsControllerCompat
10
10
  import com.facebook.react.bridge.ReactApplicationContext
11
11
  import com.facebook.react.bridge.UiThreadUtil
12
12
 
13
+ private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName
14
+
13
15
  class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicationContext) {
14
- private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName
15
16
  private var controller: WindowInsetsControllerCompat? = null
16
17
 
17
18
  fun setHidden(hidden: Boolean) {
@@ -41,7 +42,7 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
41
42
  colorAnimation.addUpdateListener { animator ->
42
43
  window.statusBarColor = animator.animatedValue as Int
43
44
  }
44
- colorAnimation.setDuration(300).startDelay = 0
45
+ colorAnimation.setDuration(DEFAULT_ANIMATION_TIME).startDelay = 0
45
46
  colorAnimation.start()
46
47
  } else {
47
48
  window.statusBarColor = color
@@ -49,6 +50,7 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
49
50
  }
50
51
  }
51
52
 
53
+ @Suppress("detekt:UnusedParameter")
52
54
  fun setTranslucent(translucent: Boolean) {
53
55
  // the status bar is translucent by default (once you wrapped App in Provider,
54
56
  // and EdgeToEdgeReactViewGroup has been mounted and called
@@ -73,7 +75,10 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
73
75
  if (this.controller == null) {
74
76
  val activity = mReactContext.currentActivity
75
77
  if (activity == null) {
76
- Log.w(TAG, "StatusBarManagerCompatModule: can not get `WindowInsetsControllerCompat` because current activity is null.")
78
+ Log.w(
79
+ TAG,
80
+ "StatusBarManagerCompatModule: can not get `WindowInsetsControllerCompat` because current activity is null.",
81
+ )
77
82
  return this.controller
78
83
  }
79
84
 
@@ -87,5 +92,6 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
87
92
 
88
93
  companion object {
89
94
  const val NAME = "StatusBarManagerCompat"
95
+ private const val DEFAULT_ANIMATION_TIME = 300L
90
96
  }
91
97
  }
@@ -1,7 +1,10 @@
1
1
  package com.reactnativekeyboardcontroller.views
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.os.Handler
5
+ import android.os.Looper
4
6
  import android.util.Log
7
+ import android.widget.FrameLayout
5
8
  import androidx.appcompat.widget.FitWindowsLinearLayout
6
9
  import androidx.core.view.ViewCompat
7
10
  import androidx.core.view.WindowCompat
@@ -10,12 +13,13 @@ import androidx.core.view.WindowInsetsCompat
10
13
  import com.facebook.react.uimanager.ThemedReactContext
11
14
  import com.facebook.react.views.view.ReactViewGroup
12
15
  import com.reactnativekeyboardcontroller.KeyboardAnimationCallback
13
- import com.reactnativekeyboardcontroller.R
14
16
  import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached
17
+ import com.reactnativekeyboardcontroller.extensions.rootView
18
+
19
+ private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
15
20
 
16
21
  @SuppressLint("ViewConstructor")
17
22
  class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
18
- private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
19
23
  private var isStatusBarTranslucent = false
20
24
  private var isNavigationBarTranslucent = false
21
25
 
@@ -23,7 +27,6 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
23
27
  val activity = reactContext.currentActivity
24
28
 
25
29
  if (activity != null) {
26
-
27
30
  val callback = KeyboardAnimationCallback(
28
31
  view = this,
29
32
  persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
@@ -32,30 +35,6 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
32
35
  // child views, so that step 2.5 below receives the call
33
36
  dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
34
37
  context = reactContext,
35
- onApplyWindowInsetsListener = { v, insets ->
36
- val content =
37
- reactContext.currentActivity?.window?.decorView?.rootView?.findViewById<FitWindowsLinearLayout>(
38
- androidx.appcompat.R.id.action_bar_root,
39
- )
40
- content?.setPadding(
41
- 0,
42
- if (this.isStatusBarTranslucent) {
43
- 0
44
- } else {
45
- insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top
46
- ?: 0
47
- },
48
- 0,
49
- if (this.isNavigationBarTranslucent) {
50
- 0
51
- } else {
52
- insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom
53
- ?: 0
54
- },
55
- )
56
-
57
- insets
58
- },
59
38
  )
60
39
  ViewCompat.setWindowInsetsAnimationCallback(this, callback)
61
40
  ViewCompat.setOnApplyWindowInsetsListener(this, callback)
@@ -65,9 +44,47 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
65
44
  }
66
45
  }
67
46
 
47
+ private fun setupWindowInsets() {
48
+ val rootView = reactContext.rootView
49
+ if (rootView != null) {
50
+ ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
51
+ val content =
52
+ reactContext.rootView?.findViewById<FitWindowsLinearLayout>(
53
+ androidx.appcompat.R.id.action_bar_root,
54
+ )
55
+ val params = FrameLayout.LayoutParams(
56
+ FrameLayout.LayoutParams.MATCH_PARENT,
57
+ FrameLayout.LayoutParams.MATCH_PARENT,
58
+ )
59
+
60
+ params.setMargins(
61
+ 0,
62
+ if (this.isStatusBarTranslucent) {
63
+ 0
64
+ } else {
65
+ insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top
66
+ ?: 0
67
+ },
68
+ 0,
69
+ if (this.isNavigationBarTranslucent) {
70
+ 0
71
+ } else {
72
+ insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom
73
+ ?: 0
74
+ },
75
+ )
76
+
77
+ content?.layoutParams = params
78
+
79
+ insets
80
+ }
81
+ }
82
+ }
83
+
68
84
  override fun onAttachedToWindow() {
69
85
  super.onAttachedToWindow()
70
86
 
87
+ Handler(Looper.getMainLooper()).post(this::setupWindowInsets)
71
88
  reactContext.currentActivity?.let {
72
89
  WindowCompat.setDecorFitsSystemWindows(
73
90
  it.window,