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
@@ -8,9 +8,14 @@ import com.reactnativekeyboardcontroller.extensions.content
8
8
  import com.reactnativekeyboardcontroller.extensions.dp
9
9
  import com.reactnativekeyboardcontroller.extensions.emitEvent
10
10
 
11
- data class Dimensions(val width: Double, val height: Double)
12
-
13
- class WindowDimensionListener(private val context: ThemedReactContext?) {
11
+ data class Dimensions(
12
+ val width: Double,
13
+ val height: Double,
14
+ )
15
+
16
+ class WindowDimensionListener(
17
+ private val context: ThemedReactContext?,
18
+ ) {
14
19
  private var lastDispatchedDimensions = Dimensions(0.0, 0.0)
15
20
 
16
21
  init {
@@ -33,10 +38,11 @@ class WindowDimensionListener(private val context: ThemedReactContext?) {
33
38
  return
34
39
  }
35
40
 
36
- val newDimensions = Dimensions(
37
- content.width.toFloat().dp,
38
- content.height.toFloat().dp + content.marginTop.toFloat().dp,
39
- )
41
+ val newDimensions =
42
+ Dimensions(
43
+ content.width.toFloat().dp,
44
+ content.height.toFloat().dp + content.marginTop.toFloat().dp,
45
+ )
40
46
 
41
47
  if (newDimensions != lastDispatchedDimensions) {
42
48
  lastDispatchedDimensions = newDimensions
@@ -6,13 +6,21 @@ import com.reactnativekeyboardcontroller.BuildConfig
6
6
  object Logger {
7
7
  private val enabled = BuildConfig.DEBUG
8
8
 
9
- fun i(tag: String?, message: String, throwable: Throwable? = null) {
9
+ fun i(
10
+ tag: String?,
11
+ message: String,
12
+ throwable: Throwable? = null,
13
+ ) {
10
14
  if (enabled) {
11
15
  Log.i(tag, message, throwable)
12
16
  }
13
17
  }
14
18
 
15
- fun w(tag: String?, message: String, throwable: Throwable? = null) {
19
+ fun w(
20
+ tag: String?,
21
+ message: String,
22
+ throwable: Throwable? = null,
23
+ ) {
16
24
  if (enabled) {
17
25
  Log.w(tag, message, throwable)
18
26
  }
@@ -6,40 +6,51 @@ import com.facebook.react.uimanager.ThemedReactContext
6
6
  import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
7
7
 
8
8
  @Suppress("detekt:UnusedPrivateProperty")
9
- class KeyboardControllerViewManagerImpl(mReactContext: ReactApplicationContext) {
10
- fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup {
11
- return EdgeToEdgeReactViewGroup(reactContext)
12
- }
13
-
14
- fun setEnabled(view: EdgeToEdgeReactViewGroup, enabled: Boolean) {
9
+ class KeyboardControllerViewManagerImpl(
10
+ mReactContext: ReactApplicationContext,
11
+ ) {
12
+ fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup =
13
+ EdgeToEdgeReactViewGroup(reactContext)
14
+
15
+ fun setEnabled(
16
+ view: EdgeToEdgeReactViewGroup,
17
+ enabled: Boolean,
18
+ ) {
15
19
  view.setActive(enabled)
16
20
  }
17
21
 
18
- fun setStatusBarTranslucent(view: EdgeToEdgeReactViewGroup, isStatusBarTranslucent: Boolean) {
22
+ fun setStatusBarTranslucent(
23
+ view: EdgeToEdgeReactViewGroup,
24
+ isStatusBarTranslucent: Boolean,
25
+ ) {
19
26
  view.setStatusBarTranslucent(isStatusBarTranslucent)
20
27
  }
21
28
 
22
- fun setNavigationBarTranslucent(view: EdgeToEdgeReactViewGroup, isNavigationBarTranslucent: Boolean) {
29
+ fun setNavigationBarTranslucent(
30
+ view: EdgeToEdgeReactViewGroup,
31
+ isNavigationBarTranslucent: Boolean,
32
+ ) {
23
33
  view.setNavigationBarTranslucent(isNavigationBarTranslucent)
24
34
  }
25
35
 
26
36
  fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
27
- val map: MutableMap<String, Any> = MapBuilder.of(
28
- "topKeyboardMove",
29
- MapBuilder.of("registrationName", "onKeyboardMove"),
30
- "topKeyboardMoveStart",
31
- MapBuilder.of("registrationName", "onKeyboardMoveStart"),
32
- "topKeyboardMoveEnd",
33
- MapBuilder.of("registrationName", "onKeyboardMoveEnd"),
34
- "topKeyboardMoveInteractive",
35
- MapBuilder.of("registrationName", "onKeyboardMoveInteractive"),
36
- "topFocusedInputLayoutChanged",
37
- MapBuilder.of("registrationName", "onFocusedInputLayoutChanged"),
38
- "topFocusedInputTextChanged",
39
- MapBuilder.of("registrationName", "onFocusedInputTextChanged"),
40
- "topFocusedInputSelectionChanged",
41
- MapBuilder.of("registrationName", "onFocusedInputSelectionChanged"),
42
- )
37
+ val map: MutableMap<String, Any> =
38
+ MapBuilder.of(
39
+ "topKeyboardMove",
40
+ MapBuilder.of("registrationName", "onKeyboardMove"),
41
+ "topKeyboardMoveStart",
42
+ MapBuilder.of("registrationName", "onKeyboardMoveStart"),
43
+ "topKeyboardMoveEnd",
44
+ MapBuilder.of("registrationName", "onKeyboardMoveEnd"),
45
+ "topKeyboardMoveInteractive",
46
+ MapBuilder.of("registrationName", "onKeyboardMoveInteractive"),
47
+ "topFocusedInputLayoutChanged",
48
+ MapBuilder.of("registrationName", "onFocusedInputLayoutChanged"),
49
+ "topFocusedInputTextChanged",
50
+ MapBuilder.of("registrationName", "onFocusedInputTextChanged"),
51
+ "topFocusedInputSelectionChanged",
52
+ MapBuilder.of("registrationName", "onFocusedInputSelectionChanged"),
53
+ )
43
54
 
44
55
  return map
45
56
  }
@@ -5,24 +5,37 @@ import com.facebook.react.uimanager.ThemedReactContext
5
5
  import com.reactnativekeyboardcontroller.views.KeyboardGestureAreaReactViewGroup
6
6
 
7
7
  @Suppress("detekt:UnusedPrivateProperty")
8
- class KeyboardGestureAreaViewManagerImpl(mReactContext: ReactApplicationContext) {
9
- fun createViewInstance(reactContext: ThemedReactContext): KeyboardGestureAreaReactViewGroup {
10
- return KeyboardGestureAreaReactViewGroup(reactContext)
11
- }
8
+ class KeyboardGestureAreaViewManagerImpl(
9
+ mReactContext: ReactApplicationContext,
10
+ ) {
11
+ fun createViewInstance(reactContext: ThemedReactContext): KeyboardGestureAreaReactViewGroup =
12
+ KeyboardGestureAreaReactViewGroup(reactContext)
12
13
 
13
- fun setOffset(view: KeyboardGestureAreaReactViewGroup, offset: Double) {
14
+ fun setOffset(
15
+ view: KeyboardGestureAreaReactViewGroup,
16
+ offset: Double,
17
+ ) {
14
18
  view.setOffset(offset)
15
19
  }
16
20
 
17
- fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
21
+ fun setInterpolator(
22
+ view: KeyboardGestureAreaReactViewGroup,
23
+ interpolator: String,
24
+ ) {
18
25
  view.setInterpolator(interpolator)
19
26
  }
20
27
 
21
- fun setScrollKeyboardOffScreenWhenVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
28
+ fun setScrollKeyboardOffScreenWhenVisible(
29
+ view: KeyboardGestureAreaReactViewGroup,
30
+ value: Boolean,
31
+ ) {
22
32
  view.setScrollKeyboardOffScreenWhenVisible(value)
23
33
  }
24
34
 
25
- fun setScrollKeyboardOnScreenWhenNotVisible(view: KeyboardGestureAreaReactViewGroup, value: Boolean) {
35
+ fun setScrollKeyboardOnScreenWhenNotVisible(
36
+ view: KeyboardGestureAreaReactViewGroup,
37
+ value: Boolean,
38
+ ) {
26
39
  view.setScrollKeyboardOnScreenWhenNotVisible(value)
27
40
  }
28
41
 
@@ -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.overlay.OverKeyboardHostView
6
+
7
+ @Suppress("detekt:UnusedPrivateProperty")
8
+ class OverKeyboardViewManagerImpl(
9
+ mReactContext: ReactApplicationContext,
10
+ ) {
11
+ fun createViewInstance(reactContext: ThemedReactContext): OverKeyboardHostView = OverKeyboardHostView(reactContext)
12
+
13
+ fun setVisible(
14
+ view: OverKeyboardHostView,
15
+ value: Boolean,
16
+ ) {
17
+ if (value) {
18
+ view.show()
19
+ } else {
20
+ view.hide()
21
+ }
22
+ }
23
+
24
+ companion object {
25
+ const val NAME = "OverKeyboardView"
26
+ }
27
+ }
@@ -1,5 +1,6 @@
1
1
  package com.reactnativekeyboardcontroller.modal
2
2
 
3
+ import android.view.ViewGroup
3
4
  import android.view.WindowManager
4
5
  import androidx.core.view.ViewCompat
5
6
  import com.facebook.react.uimanager.ThemedReactContext
@@ -10,16 +11,20 @@ import com.facebook.react.uimanager.events.EventDispatcherListener
10
11
  import com.facebook.react.views.modal.ReactModalHostView
11
12
  import com.facebook.react.views.view.ReactViewGroup
12
13
  import com.reactnativekeyboardcontroller.BuildConfig
14
+ import com.reactnativekeyboardcontroller.constants.Keyboard
15
+ import com.reactnativekeyboardcontroller.extensions.removeSelf
13
16
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
14
17
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
15
18
  import com.reactnativekeyboardcontroller.log.Logger
16
19
 
17
20
  private val TAG = ModalAttachedWatcher::class.qualifiedName
21
+ private val areEventsComingFromOwnWindow = !Keyboard.IS_ANIMATION_EMULATED
18
22
 
19
23
  class ModalAttachedWatcher(
20
24
  private val view: ReactViewGroup,
21
25
  private val reactContext: ThemedReactContext,
22
26
  private val config: () -> KeyboardAnimationCallbackConfig,
27
+ private var callback: () -> KeyboardAnimationCallback?,
23
28
  ) : EventDispatcherListener {
24
29
  private val archType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) UIManagerType.FABRIC else UIManagerType.DEFAULT
25
30
  private val uiManager = UIManagerHelper.getUIManager(reactContext.reactApplicationContext, archType)
@@ -30,12 +35,13 @@ class ModalAttachedWatcher(
30
35
  return
31
36
  }
32
37
 
33
- val modal = try {
34
- uiManager?.resolveView(event.viewTag) as? ReactModalHostView
35
- } catch (ignore: Exception) {
36
- Logger.w(TAG, "Can not resolve view for Modal#${event.viewTag}", ignore)
37
- null
38
- }
38
+ val modal =
39
+ try {
40
+ uiManager?.resolveView(event.viewTag) as? ReactModalHostView
41
+ } catch (ignore: Exception) {
42
+ Logger.w(TAG, "Can not resolve view for Modal#${event.viewTag}", ignore)
43
+ null
44
+ }
39
45
 
40
46
  if (modal == null) {
41
47
  return
@@ -43,26 +49,50 @@ class ModalAttachedWatcher(
43
49
 
44
50
  val dialog = modal.dialog
45
51
  val window = dialog?.window
46
- val rootView = window?.decorView?.rootView
52
+ val rootView = window?.decorView?.rootView as ViewGroup?
47
53
 
48
54
  if (rootView != null) {
49
- val callback = KeyboardAnimationCallback(
50
- view = rootView,
51
- eventPropagationView = view,
52
- context = reactContext,
53
- config = config(),
54
- )
55
+ val eventView =
56
+ ReactViewGroup(reactContext).apply {
57
+ layoutParams = ViewGroup.LayoutParams(0, 0)
58
+ }
59
+ val callback =
60
+ KeyboardAnimationCallback(
61
+ view = rootView,
62
+ eventPropagationView = view,
63
+ context = reactContext,
64
+ config = config(),
65
+ )
55
66
 
67
+ rootView.addView(eventView)
68
+ // on Android < 12 all events for `WindowInsetsAnimationCallback`
69
+ // go through main `rootView`, so we don't need to stop main
70
+ // callback - otherwise keyboard transitions will not be animated
71
+ this.callback()?.suspend(areEventsComingFromOwnWindow)
56
72
  ViewCompat.setWindowInsetsAnimationCallback(rootView, callback)
57
- ViewCompat.setOnApplyWindowInsetsListener(rootView, callback)
73
+ ViewCompat.setOnApplyWindowInsetsListener(eventView, callback)
74
+
75
+ if (areEventsComingFromOwnWindow) {
76
+ // when modal is shown then keyboard will be hidden by default
77
+ //
78
+ // - if events are coming from main window - then keyboard position
79
+ // will be synchronized from main window callback
80
+ // - if events are coming from modal window - then we need to update
81
+ // position ourself, because callback can be attached after keyboard
82
+ // auto-dismissal and we may miss some events and keyboard position
83
+ // will be outdated
84
+ callback.syncKeyboardPosition(0.0, false)
85
+ }
58
86
 
59
- dialog.setOnDismissListener {
87
+ dialog?.setOnDismissListener {
60
88
  callback.syncKeyboardPosition()
61
89
  callback.destroy()
90
+ eventView.removeSelf()
91
+ this.callback()?.suspend(false)
62
92
  }
63
93
 
64
94
  // imitating edge-to-edge mode behavior
65
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
95
+ window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
66
96
  }
67
97
  }
68
98
 
@@ -9,7 +9,9 @@ import com.facebook.react.bridge.UiThreadUtil
9
9
  import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder
10
10
  import com.reactnativekeyboardcontroller.traversal.ViewHierarchyNavigator
11
11
 
12
- class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationContext) {
12
+ class KeyboardControllerModuleImpl(
13
+ private val mReactContext: ReactApplicationContext,
14
+ ) {
13
15
  private val mDefaultMode: Int = getCurrentMode()
14
16
 
15
17
  fun setInputMode(mode: Int) {
@@ -22,10 +24,10 @@ class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationCo
22
24
 
23
25
  fun dismiss() {
24
26
  val activity = mReactContext.currentActivity
25
- val view: View? = activity?.currentFocus
27
+ val view: View? = FocusedInputHolder.get()
26
28
 
27
29
  if (view != null) {
28
- val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
30
+ val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
29
31
  imm?.hideSoftInputFromWindow(view.windowToken, 0)
30
32
  }
31
33
  }
@@ -35,8 +37,7 @@ class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationCo
35
37
  return FocusedInputHolder.focus()
36
38
  }
37
39
 
38
- val activity = mReactContext.currentActivity
39
- val view: View? = activity?.currentFocus
40
+ val view: View? = FocusedInputHolder.get()
40
41
 
41
42
  if (view != null) {
42
43
  ViewHierarchyNavigator.setFocusTo(direction, view)
@@ -51,14 +52,13 @@ class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationCo
51
52
  }
52
53
  }
53
54
 
54
- private fun getCurrentMode(): Int {
55
- return mReactContext
55
+ private fun getCurrentMode(): Int =
56
+ mReactContext
56
57
  .currentActivity
57
58
  ?.window
58
59
  ?.attributes
59
60
  ?.softInputMode
60
61
  ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
61
- }
62
62
 
63
63
  companion object {
64
64
  const val NAME = "KeyboardController"
@@ -2,6 +2,7 @@ package com.reactnativekeyboardcontroller.modules
2
2
 
3
3
  import android.animation.ArgbEvaluator
4
4
  import android.animation.ValueAnimator
5
+ import android.app.Activity
5
6
  import android.os.Build
6
7
  import androidx.annotation.RequiresApi
7
8
  import androidx.core.view.WindowInsetsCompat
@@ -11,11 +12,15 @@ import com.facebook.react.bridge.UiThreadUtil
11
12
  import com.reactnativekeyboardcontroller.extensions.rootView
12
13
  import com.reactnativekeyboardcontroller.log.Logger
13
14
  import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
15
+ import java.lang.ref.WeakReference
14
16
 
15
17
  private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName
16
18
 
17
- class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicationContext) {
19
+ class StatusBarManagerCompatModuleImpl(
20
+ private val mReactContext: ReactApplicationContext,
21
+ ) {
18
22
  private var controller: WindowInsetsControllerCompat? = null
23
+ private var lastActivity = WeakReference<Activity?>(null)
19
24
 
20
25
  fun setHidden(hidden: Boolean) {
21
26
  UiThreadUtil.runOnUiThread {
@@ -28,7 +33,10 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
28
33
  }
29
34
 
30
35
  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
31
- fun setColor(color: Int, animated: Boolean) {
36
+ fun setColor(
37
+ color: Int,
38
+ animated: Boolean,
39
+ ) {
32
40
  val activity = mReactContext.currentActivity
33
41
  if (activity == null) {
34
42
  Logger.w(TAG, "StatusBarManagerCompatModule: Ignored status bar change, current activity is null.")
@@ -66,8 +74,9 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
66
74
  }
67
75
 
68
76
  private fun getController(): WindowInsetsControllerCompat? {
69
- if (this.controller == null) {
70
- val activity = mReactContext.currentActivity
77
+ val activity = mReactContext.currentActivity
78
+
79
+ if (this.controller == null || activity != lastActivity.get()) {
71
80
  if (activity == null) {
72
81
  Logger.w(
73
82
  TAG,
@@ -77,6 +86,7 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
77
86
  }
78
87
 
79
88
  val window = activity.window
89
+ lastActivity = WeakReference(activity)
80
90
 
81
91
  this.controller = WindowInsetsControllerCompat(window, window.decorView)
82
92
  }
@@ -11,9 +11,7 @@ object FocusedInputHolder {
11
11
  input = WeakReference(textInput)
12
12
  }
13
13
 
14
- fun get(): EditText? {
15
- return input?.get()
16
- }
14
+ fun get(): EditText? = input?.get()
17
15
 
18
16
  fun focus() {
19
17
  input?.get()?.focus()
@@ -7,7 +7,10 @@ import com.facebook.react.bridge.UiThreadUtil
7
7
  import com.reactnativekeyboardcontroller.extensions.focus
8
8
 
9
9
  object ViewHierarchyNavigator {
10
- fun setFocusTo(direction: String, view: View) {
10
+ fun setFocusTo(
11
+ direction: String,
12
+ view: View,
13
+ ) {
11
14
  val input = if (direction == "next") findNextEditText(view) else findPreviousEditText(view)
12
15
 
13
16
  UiThreadUtil.runOnUiThread {
@@ -35,16 +38,15 @@ object ViewHierarchyNavigator {
35
38
  return editTexts
36
39
  }
37
40
 
38
- private fun findNextEditText(currentFocus: View): EditText? {
39
- return findEditTextInDirection(currentFocus, 1)
40
- }
41
+ private fun findNextEditText(currentFocus: View): EditText? = findEditTextInDirection(currentFocus, 1)
41
42
 
42
- private fun findPreviousEditText(currentFocus: View): EditText? {
43
- return findEditTextInDirection(currentFocus, -1)
44
- }
43
+ private fun findPreviousEditText(currentFocus: View): EditText? = findEditTextInDirection(currentFocus, -1)
45
44
 
46
45
  @Suppress("detekt:ReturnCount")
47
- private fun findEditTextInDirection(currentFocus: View, direction: Int): EditText? {
46
+ private fun findEditTextInDirection(
47
+ currentFocus: View,
48
+ direction: Int,
49
+ ): EditText? {
48
50
  // Attempt to find the parent view group, return null if not found or not a ViewGroup
49
51
  val parentViewGroup = currentFocus.parent as? ViewGroup ?: return null
50
52
 
@@ -66,7 +68,10 @@ object ViewHierarchyNavigator {
66
68
  return findEditTextInDirection(parentViewGroup, direction)
67
69
  }
68
70
 
69
- private fun findEditTextInHierarchy(viewGroup: ViewGroup, direction: Int): EditText? {
71
+ private fun findEditTextInHierarchy(
72
+ viewGroup: ViewGroup,
73
+ direction: Int,
74
+ ): EditText? {
70
75
  val range = if (direction > 0) 0 until viewGroup.childCount else viewGroup.childCount - 1 downTo 0
71
76
 
72
77
  for (i in range) {
@@ -78,7 +83,10 @@ object ViewHierarchyNavigator {
78
83
  return null
79
84
  }
80
85
 
81
- private fun findEditTextOrGoDeeper(child: View, direction: Int): EditText? {
86
+ private fun findEditTextOrGoDeeper(
87
+ child: View,
88
+ direction: Int,
89
+ ): EditText? {
82
90
  var result: EditText? = null
83
91
 
84
92
  if (isValidTextInput(child)) {
@@ -91,7 +99,5 @@ object ViewHierarchyNavigator {
91
99
  return result
92
100
  }
93
101
 
94
- private fun isValidTextInput(view: View?): Boolean {
95
- return view is EditText && view.isEnabled
96
- }
102
+ private fun isValidTextInput(view: View?): Boolean = view is EditText && view.isEnabled
97
103
  }
@@ -7,20 +7,23 @@ import android.view.Choreographer
7
7
  *
8
8
  * @property callback The function to be executed on each frame.
9
9
  */
10
- class FrameScheduler(private val callback: () -> Unit) {
10
+ class FrameScheduler(
11
+ private val callback: () -> Unit,
12
+ ) {
11
13
  /**
12
14
  * A FrameCallback instance responsible for running the provided callback
13
15
  * on each frame and rescheduling itself for the next frame.
14
16
  */
15
- private val frameCallback = object : Choreographer.FrameCallback {
16
- override fun doFrame(frameTimeNanoSeconds: Long) {
17
- // Execute the callback
18
- callback()
17
+ private val frameCallback =
18
+ object : Choreographer.FrameCallback {
19
+ override fun doFrame(frameTimeNanoSeconds: Long) {
20
+ // Execute the callback
21
+ callback()
19
22
 
20
- // Re-post the callback to the next frame
21
- Choreographer.getInstance().postFrameCallback(this)
23
+ // Re-post the callback to the next frame
24
+ Choreographer.getInstance().postFrameCallback(this)
25
+ }
22
26
  }
23
- }
24
27
 
25
28
  /**
26
29
  * Starts the frame callback, which will continuously call the provided function
@@ -25,7 +25,9 @@ private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
25
25
 
26
26
  @Suppress("detekt:TooManyFunctions")
27
27
  @SuppressLint("ViewConstructor")
28
- class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
28
+ class EdgeToEdgeReactViewGroup(
29
+ private val reactContext: ThemedReactContext,
30
+ ) : ReactViewGroup(reactContext) {
29
31
  // props
30
32
  private var isStatusBarTranslucent = false
31
33
  private var isNavigationBarTranslucent = false
@@ -36,15 +38,16 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
36
38
  private var wasMounted = false
37
39
  private var callback: KeyboardAnimationCallback? = null
38
40
  private val config: KeyboardAnimationCallbackConfig
39
- get() = KeyboardAnimationCallbackConfig(
40
- persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
41
- deferredInsetTypes = WindowInsetsCompat.Type.ime(),
42
- dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
43
- hasTranslucentNavigationBar = isNavigationBarTranslucent,
44
- )
41
+ get() =
42
+ KeyboardAnimationCallbackConfig(
43
+ persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
44
+ deferredInsetTypes = WindowInsetsCompat.Type.ime(),
45
+ dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
46
+ hasTranslucentNavigationBar = isNavigationBarTranslucent,
47
+ )
45
48
 
46
49
  // managers/watchers
47
- private val modalAttachedWatcher = ModalAttachedWatcher(this, reactContext, ::config)
50
+ private val modalAttachedWatcher = ModalAttachedWatcher(this, reactContext, ::config, ::getKeyboardCallback)
48
51
 
49
52
  init {
50
53
  reactContext.setupWindowDimensionsListener()
@@ -81,10 +84,11 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
81
84
  if (rootView != null) {
82
85
  ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
83
86
  val content = reactContext.content
84
- val params = FrameLayout.LayoutParams(
85
- FrameLayout.LayoutParams.MATCH_PARENT,
86
- FrameLayout.LayoutParams.MATCH_PARENT,
87
- )
87
+ val params =
88
+ FrameLayout.LayoutParams(
89
+ FrameLayout.LayoutParams.MATCH_PARENT,
90
+ FrameLayout.LayoutParams.MATCH_PARENT,
91
+ )
88
92
 
89
93
  val shouldApplyZeroPaddingTop = !active || this.isStatusBarTranslucent
90
94
  val shouldApplyZeroPaddingBottom = !active || this.isNavigationBarTranslucent
@@ -136,12 +140,13 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
136
140
  val root = reactContext.content
137
141
  root?.addView(eventView)
138
142
 
139
- callback = KeyboardAnimationCallback(
140
- view = this,
141
- eventPropagationView = this,
142
- context = reactContext,
143
- config = config,
144
- )
143
+ callback =
144
+ KeyboardAnimationCallback(
145
+ view = this,
146
+ eventPropagationView = this,
147
+ context = reactContext,
148
+ config = config,
149
+ )
145
150
 
146
151
  eventView?.let {
147
152
  ViewCompat.setWindowInsetsAnimationCallback(it, callback)
@@ -190,6 +195,10 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
190
195
  }
191
196
  // endregion
192
197
 
198
+ // region Helpers
199
+ private fun getKeyboardCallback(): KeyboardAnimationCallback? = this.callback
200
+ // endregion
201
+
193
202
  // region Props setters
194
203
  fun setStatusBarTranslucent(isStatusBarTranslucent: Boolean) {
195
204
  this.isStatusBarTranslucent = isStatusBarTranslucent