react-native-keyboard-controller 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +72 -3
- package/android/gradle.properties +1 -1
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerComponentsRegistry.kt +31 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +25 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +32 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +25 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationCallback.kt +188 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/{KeyboardControllerModule.kt → KeyboardControllerModuleImpl.kt} +5 -17
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +5 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManagerImpl.kt +71 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/{StatusBarManagerCompatModule.kt → StatusBarManagerCompatImpl.kt} +10 -14
- package/android/src/main/jni/Android.mk +42 -0
- package/android/src/main/jni/KeyboardControllerComponentsRegistry.cpp +71 -0
- package/android/src/main/jni/KeyboardControllerComponentsRegistry.h +35 -0
- package/android/src/main/jni/OnLoad.cpp +9 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +31 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +26 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +34 -0
- package/ios/.clang-format +90 -0
- package/ios/KeyboardController.xcodeproj/project.pbxproj +22 -14
- package/ios/KeyboardControllerModule-Header.h +6 -2
- package/ios/KeyboardControllerModule.mm +105 -0
- package/ios/KeyboardControllerView.h +23 -0
- package/ios/KeyboardControllerView.mm +81 -0
- package/ios/{KeyboardControllerViewManager.m → KeyboardControllerViewManager.mm} +1 -1
- package/ios/KeyboardControllerViewManager.swift +13 -64
- package/ios/KeyboardMoveEvent.h +18 -0
- package/ios/KeyboardMoveEvent.m +74 -0
- package/ios/KeyboardMovementObserver.swift +90 -0
- package/lib/commonjs/KeyboardControllerViewNativeComponent.js +16 -0
- package/lib/commonjs/KeyboardControllerViewNativeComponent.js.map +1 -0
- package/lib/commonjs/NativeKeyboardController.js +13 -0
- package/lib/commonjs/NativeKeyboardController.js.map +1 -0
- package/lib/commonjs/NativeStatusBarManagerCompat.js +13 -0
- package/lib/commonjs/NativeStatusBarManagerCompat.js.map +1 -0
- package/lib/commonjs/animated.js.map +1 -1
- package/lib/commonjs/architecture.js +13 -0
- package/lib/commonjs/architecture.js.map +1 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/monkey-patch.js +3 -1
- package/lib/commonjs/monkey-patch.js.map +1 -1
- package/lib/commonjs/native.js +4 -2
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/replicas.js.map +1 -1
- package/lib/module/KeyboardControllerViewNativeComponent.js +4 -0
- package/lib/module/KeyboardControllerViewNativeComponent.js.map +1 -0
- package/lib/module/NativeKeyboardController.js +3 -0
- package/lib/module/NativeKeyboardController.js.map +1 -0
- package/lib/module/NativeStatusBarManagerCompat.js +3 -0
- package/lib/module/NativeStatusBarManagerCompat.js.map +1 -0
- package/lib/module/animated.js.map +1 -1
- package/lib/module/architecture.js +5 -0
- package/lib/module/architecture.js.map +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/monkey-patch.js +2 -1
- package/lib/module/monkey-patch.js.map +1 -1
- package/lib/module/native.js +3 -2
- package/lib/module/native.js.map +1 -1
- package/lib/module/replicas.js.map +1 -1
- package/lib/typescript/KeyboardControllerViewNativeComponent.d.ts +13 -0
- package/lib/typescript/NativeKeyboardController.d.ts +10 -0
- package/lib/typescript/NativeStatusBarManagerCompat.d.ts +10 -0
- package/lib/typescript/architecture.d.ts +2 -0
- package/lib/typescript/native.d.ts +1 -1
- package/lib/typescript/replicas.d.ts +3 -5
- package/package.json +23 -6
- package/react-native-keyboard-controller.podspec +19 -0
- package/src/KeyboardControllerViewNativeComponent.ts +23 -0
- package/src/NativeKeyboardController.ts +16 -0
- package/src/NativeStatusBarManagerCompat.ts +13 -0
- package/src/architecture.ts +4 -0
- package/src/monkey-patch.tsx +5 -1
- package/src/native.ts +12 -7
- package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +0 -77
- package/android/src/main/java/com/reactnativekeyboardcontroller/TranslateDeferringInsetsAnimationCallback.kt +0 -158
- package/ios/KeyboardControllerModule.m +0 -21
- package/ios/KeyboardControllerModule.swift +0 -53
- package/ios/KeyboardMoveEvent.swift +0 -41
package/android/build.gradle
CHANGED
|
@@ -14,9 +14,6 @@ buildscript {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
apply plugin: 'com.android.library'
|
|
18
|
-
apply plugin: 'kotlin-android'
|
|
19
|
-
|
|
20
17
|
def getExtOrDefault(name) {
|
|
21
18
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['KeyboardController_' + name]
|
|
22
19
|
}
|
|
@@ -25,6 +22,22 @@ def getExtOrIntegerDefault(name) {
|
|
|
25
22
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['KeyboardController_' + name]).toInteger()
|
|
26
23
|
}
|
|
27
24
|
|
|
25
|
+
def isNewArchitectureEnabled() {
|
|
26
|
+
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def reactNativeArchitectures() {
|
|
30
|
+
def value = project.getProperties().get("reactNativeArchitectures")
|
|
31
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
apply plugin: 'com.android.library'
|
|
35
|
+
apply plugin: 'kotlin-android'
|
|
36
|
+
|
|
37
|
+
if (isNewArchitectureEnabled()) {
|
|
38
|
+
apply plugin: 'com.facebook.react'
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
android {
|
|
29
42
|
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
|
|
30
43
|
defaultConfig {
|
|
@@ -32,7 +45,53 @@ android {
|
|
|
32
45
|
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
|
|
33
46
|
versionCode 1
|
|
34
47
|
versionName "1.0"
|
|
48
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
49
|
+
if (isNewArchitectureEnabled()) {
|
|
50
|
+
var appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
|
|
51
|
+
externalNativeBuild {
|
|
52
|
+
ndkBuild {
|
|
53
|
+
arguments "APP_PLATFORM=android-21",
|
|
54
|
+
"APP_STL=c++_shared",
|
|
55
|
+
"NDK_TOOLCHAIN_VERSION=clang",
|
|
56
|
+
"GENERATED_SRC_DIR=${appProject.buildDir}/generated/source",
|
|
57
|
+
"PROJECT_BUILD_DIR=${appProject.buildDir}",
|
|
58
|
+
"REACT_ANDROID_DIR=${appProject.rootDir}/../node_modules/react-native/ReactAndroid",
|
|
59
|
+
"REACT_ANDROID_BUILD_DIR=${appProject.rootDir}/../node_modules/react-native/ReactAndroid/build"
|
|
60
|
+
cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1"
|
|
61
|
+
cppFlags "-std=c++17"
|
|
62
|
+
targets "reactnativekeyboardcontroller_modules"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
ndk {
|
|
67
|
+
abiFilters (*reactNativeArchitectures())
|
|
68
|
+
}
|
|
69
|
+
}
|
|
35
70
|
|
|
71
|
+
if (isNewArchitectureEnabled()) {
|
|
72
|
+
externalNativeBuild {
|
|
73
|
+
ndkBuild {
|
|
74
|
+
path "src/main/jni/Android.mk"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
packagingOptions {
|
|
79
|
+
exclude "**/libreact_render_*.so"
|
|
80
|
+
exclude "**/librrc_root.so"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
sourceSets {
|
|
85
|
+
main {
|
|
86
|
+
if (isNewArchitectureEnabled()) {
|
|
87
|
+
java.srcDirs += [
|
|
88
|
+
'src/fabric',
|
|
89
|
+
'build/generated/source/codegen/java'
|
|
90
|
+
]
|
|
91
|
+
} else {
|
|
92
|
+
java.srcDirs += ['src/paper']
|
|
93
|
+
}
|
|
94
|
+
}
|
|
36
95
|
}
|
|
37
96
|
|
|
38
97
|
buildTypes {
|
|
@@ -127,3 +186,13 @@ dependencies {
|
|
|
127
186
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
128
187
|
implementation 'androidx.core:core-ktx:1.5.0-beta03'
|
|
129
188
|
}
|
|
189
|
+
|
|
190
|
+
if (isNewArchitectureEnabled()) {
|
|
191
|
+
react {
|
|
192
|
+
reactNativeDir = rootProject.file("../node_modules/react-native/")
|
|
193
|
+
jsRootDir = file("../src/")
|
|
194
|
+
codegenDir = rootProject.file("../node_modules/react-native-codegen/")
|
|
195
|
+
libraryName = "reactnativekeyboardcontroller"
|
|
196
|
+
codegenJavaPackageName = "com.reactnativekeyboardcontroller"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import com.facebook.jni.HybridData
|
|
4
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
5
|
+
import com.facebook.react.fabric.ComponentFactory
|
|
6
|
+
import com.facebook.soloader.SoLoader
|
|
7
|
+
|
|
8
|
+
@DoNotStrip
|
|
9
|
+
class KeyboardControllerComponentsRegistry @DoNotStrip private constructor(componentFactory: ComponentFactory) {
|
|
10
|
+
companion object {
|
|
11
|
+
@DoNotStrip
|
|
12
|
+
fun register(componentFactory: ComponentFactory): KeyboardControllerComponentsRegistry {
|
|
13
|
+
return KeyboardControllerComponentsRegistry(componentFactory)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
init {
|
|
17
|
+
SoLoader.loadLibrary("fabricjni")
|
|
18
|
+
SoLoader.loadLibrary("reactnativekeyboardcontroller_modules")
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@DoNotStrip
|
|
23
|
+
private val mHybridData: HybridData
|
|
24
|
+
|
|
25
|
+
@DoNotStrip
|
|
26
|
+
private external fun initHybrid(componentFactory: ComponentFactory): HybridData
|
|
27
|
+
|
|
28
|
+
init {
|
|
29
|
+
mHybridData = initHybrid(componentFactory)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
|
|
5
|
+
class KeyboardControllerModule(mReactContext: ReactApplicationContext) : NativeKeyboardControllerSpec(mReactContext) {
|
|
6
|
+
private val module = KeyboardControllerModuleImpl(mReactContext)
|
|
7
|
+
|
|
8
|
+
override fun getName(): String = KeyboardControllerModuleImpl.NAME
|
|
9
|
+
|
|
10
|
+
override fun setInputMode(mode: Double) {
|
|
11
|
+
module.setInputMode(mode.toInt())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun setDefaultMode() {
|
|
15
|
+
module.setDefaultMode()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun addListener(eventName: String?) {
|
|
19
|
+
/* Required for RN built-in Event Emitter Calls. */
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override fun removeListeners(count: Double) {
|
|
23
|
+
/* Required for RN built-in Event Emitter Calls. */
|
|
24
|
+
}
|
|
25
|
+
}
|
package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
6
|
+
import com.facebook.react.viewmanagers.KeyboardControllerViewManagerDelegate
|
|
7
|
+
import com.facebook.react.viewmanagers.KeyboardControllerViewManagerInterface
|
|
8
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
9
|
+
import com.facebook.react.views.view.ReactViewManager
|
|
10
|
+
|
|
11
|
+
class KeyboardControllerViewManager(mReactContext: ReactApplicationContext) : ReactViewManager(), KeyboardControllerViewManagerInterface<ReactViewGroup> {
|
|
12
|
+
private val manager = KeyboardControllerViewManagerImpl(mReactContext)
|
|
13
|
+
private val mDelegate = KeyboardControllerViewManagerDelegate(this)
|
|
14
|
+
|
|
15
|
+
override fun getDelegate(): ViewManagerDelegate<ReactViewGroup?> {
|
|
16
|
+
return mDelegate
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getName(): String = KeyboardControllerViewManagerImpl.NAME
|
|
20
|
+
|
|
21
|
+
override fun createViewInstance(context: ThemedReactContext): ReactViewGroup {
|
|
22
|
+
return manager.createViewInstance(context)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override fun setStatusBarTranslucent(view: ReactViewGroup, value: Boolean) {
|
|
26
|
+
return manager.setStatusBarTranslucent(view, value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
30
|
+
return manager.getExportedCustomDirectEventTypeConstants()
|
|
31
|
+
}
|
|
32
|
+
}
|
package/android/src/fabric/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
|
|
5
|
+
class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationContext) : NativeStatusBarManagerCompatSpec(mReactContext) {
|
|
6
|
+
private val module = StatusBarManagerCompatImpl(mReactContext)
|
|
7
|
+
|
|
8
|
+
override fun getName(): String = StatusBarManagerCompatImpl.NAME
|
|
9
|
+
|
|
10
|
+
override fun setHidden(hidden: Boolean) {
|
|
11
|
+
module.setHidden(hidden)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun setColor(color: Double, animated: Boolean) {
|
|
15
|
+
module.setColor(color.toInt(), animated)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun setTranslucent(translucent: Boolean) {
|
|
19
|
+
module.setTranslucent(translucent)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override fun setStyle(style: String) {
|
|
23
|
+
module.setStyle(style)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import android.animation.ValueAnimator
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import android.view.View
|
|
8
|
+
import androidx.core.animation.doOnEnd
|
|
9
|
+
import androidx.core.graphics.Insets
|
|
10
|
+
import androidx.core.view.OnApplyWindowInsetsListener
|
|
11
|
+
import androidx.core.view.ViewCompat
|
|
12
|
+
import androidx.core.view.WindowInsetsAnimationCompat
|
|
13
|
+
import androidx.core.view.WindowInsetsCompat
|
|
14
|
+
import com.facebook.react.bridge.Arguments
|
|
15
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
16
|
+
import com.facebook.react.bridge.WritableMap
|
|
17
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
18
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
19
|
+
import com.facebook.react.uimanager.events.Event
|
|
20
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
21
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
22
|
+
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
|
|
23
|
+
|
|
24
|
+
fun toDp(px: Float, context: Context?): Int {
|
|
25
|
+
if (context == null) {
|
|
26
|
+
return 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (px / context.resources.displayMetrics.density).toInt()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class KeyboardAnimationCallback(
|
|
33
|
+
val view: ReactViewGroup,
|
|
34
|
+
val persistentInsetTypes: Int,
|
|
35
|
+
val deferredInsetTypes: Int,
|
|
36
|
+
dispatchMode: Int = DISPATCH_MODE_STOP,
|
|
37
|
+
val context: ReactApplicationContext?,
|
|
38
|
+
val onApplyWindowInsetsListener: OnApplyWindowInsetsListener
|
|
39
|
+
) : WindowInsetsAnimationCompat.Callback(dispatchMode), OnApplyWindowInsetsListener {
|
|
40
|
+
private val TAG = KeyboardAnimationCallback::class.qualifiedName
|
|
41
|
+
private var persistentKeyboardHeight = 0
|
|
42
|
+
private var isKeyboardVisible = false
|
|
43
|
+
private var isTransitioning = false
|
|
44
|
+
|
|
45
|
+
init {
|
|
46
|
+
require(persistentInsetTypes and deferredInsetTypes == 0) {
|
|
47
|
+
"persistentInsetTypes and deferredInsetTypes can not contain any of " +
|
|
48
|
+
" same WindowInsetsCompat.Type values"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* When keyboard changes its size we have different behavior per APIs.
|
|
54
|
+
* On 21<=API<30 - WindowInsetsAnimationCompat dispatches onStart/onProgress/onEnd events.
|
|
55
|
+
* On API>=30 - WindowInsetsAnimationCompat doesn't dispatch anything. As a result behavior
|
|
56
|
+
* between different Android versions is not consistent. On old Android versions we have a
|
|
57
|
+
* reaction, on newer versions - not. In my understanding it's a bug in core library and the
|
|
58
|
+
* behavior should be consistent across all versions of platform. To level the difference we
|
|
59
|
+
* have to implement `onApplyWindowInsets` listener and simulate onStart/onProgress/onEnd
|
|
60
|
+
* events when keyboard changes its size.
|
|
61
|
+
* In the method below we fully recreate the logic that is implemented on old android versions:
|
|
62
|
+
* - we dispatch `keyboardWillShow` (onStart);
|
|
63
|
+
* - we dispatch change height/progress as animated values (onProgress);
|
|
64
|
+
* - we dispatch `keyboardDidShow` (onEnd).
|
|
65
|
+
*/
|
|
66
|
+
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
|
|
67
|
+
// when keyboard appears values will be (false && true)
|
|
68
|
+
// when keyboard disappears values will be (true && false)
|
|
69
|
+
// `!isTransitioning` check is needed to avoid calls of `onApplyWindowInsets` during keyboard animation
|
|
70
|
+
// having such check allows us not to dispatch unnecessary incorrect events
|
|
71
|
+
// the condition will be executed only when keyboard is opened and changes its size
|
|
72
|
+
// (for example it happens when user changes keyboard type from 'text' to 'emoji' input
|
|
73
|
+
if (isKeyboardVisible && isKeyboardVisible() && !isTransitioning && Build.VERSION.SDK_INT >= 30) {
|
|
74
|
+
val keyboardHeight = getCurrentKeyboardHeight()
|
|
75
|
+
|
|
76
|
+
this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
|
|
77
|
+
|
|
78
|
+
val animation = ValueAnimator.ofInt(-this.persistentKeyboardHeight, -keyboardHeight)
|
|
79
|
+
animation.addUpdateListener { animator ->
|
|
80
|
+
val toValue = animator.animatedValue as Int
|
|
81
|
+
this.sendEventToJS(KeyboardTransitionEvent(view.id, toValue, -toValue.toDouble() / keyboardHeight))
|
|
82
|
+
}
|
|
83
|
+
animation.doOnEnd {
|
|
84
|
+
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
|
|
85
|
+
}
|
|
86
|
+
animation.setDuration(250).startDelay = 0
|
|
87
|
+
animation.start()
|
|
88
|
+
|
|
89
|
+
this.persistentKeyboardHeight = keyboardHeight
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return onApplyWindowInsetsListener.onApplyWindowInsets(v, insets)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override fun onStart(
|
|
96
|
+
animation: WindowInsetsAnimationCompat,
|
|
97
|
+
bounds: WindowInsetsAnimationCompat.BoundsCompat
|
|
98
|
+
): WindowInsetsAnimationCompat.BoundsCompat {
|
|
99
|
+
isTransitioning = true
|
|
100
|
+
isKeyboardVisible = isKeyboardVisible()
|
|
101
|
+
val keyboardHeight = getCurrentKeyboardHeight()
|
|
102
|
+
|
|
103
|
+
if (isKeyboardVisible) {
|
|
104
|
+
// do not update it on hide, since back progress will be invalid
|
|
105
|
+
this.persistentKeyboardHeight = keyboardHeight
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.emitEvent("KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow", getEventParams(keyboardHeight))
|
|
109
|
+
|
|
110
|
+
Log.i(TAG, "HEIGHT:: $keyboardHeight")
|
|
111
|
+
|
|
112
|
+
return super.onStart(animation, bounds)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override fun onProgress(
|
|
116
|
+
insets: WindowInsetsCompat,
|
|
117
|
+
runningAnimations: List<WindowInsetsAnimationCompat>
|
|
118
|
+
): WindowInsetsCompat {
|
|
119
|
+
// onProgress() is called when any of the running animations progress...
|
|
120
|
+
|
|
121
|
+
// First we get the insets which are potentially deferred
|
|
122
|
+
val typesInset = insets.getInsets(deferredInsetTypes)
|
|
123
|
+
// Then we get the persistent inset types which are applied as padding during layout
|
|
124
|
+
val otherInset = insets.getInsets(persistentInsetTypes)
|
|
125
|
+
|
|
126
|
+
// Now that we subtract the two insets, to calculate the difference. We also coerce
|
|
127
|
+
// the insets to be >= 0, to make sure we don't use negative insets.
|
|
128
|
+
val diff = Insets.subtract(typesInset, otherInset).let {
|
|
129
|
+
Insets.max(it, Insets.NONE)
|
|
130
|
+
}
|
|
131
|
+
val diffY = (diff.top - diff.bottom).toFloat()
|
|
132
|
+
val height = toDp(diffY, context)
|
|
133
|
+
|
|
134
|
+
var progress = 0.0
|
|
135
|
+
try {
|
|
136
|
+
progress = Math.abs((height.toDouble() / persistentKeyboardHeight)).let { if (it.isNaN()) 0.0 else it }
|
|
137
|
+
} catch (e: ArithmeticException) {
|
|
138
|
+
// do nothing, send progress as 0
|
|
139
|
+
}
|
|
140
|
+
Log.i(TAG, "DiffY: $diffY $height $progress")
|
|
141
|
+
|
|
142
|
+
this.sendEventToJS(KeyboardTransitionEvent(view.id, height, progress))
|
|
143
|
+
|
|
144
|
+
return insets
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
|
148
|
+
super.onEnd(animation)
|
|
149
|
+
|
|
150
|
+
isTransitioning = false
|
|
151
|
+
this.persistentKeyboardHeight = getCurrentKeyboardHeight()
|
|
152
|
+
this.emitEvent("KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun isKeyboardVisible(): Boolean {
|
|
156
|
+
val insets = ViewCompat.getRootWindowInsets(view)
|
|
157
|
+
|
|
158
|
+
return insets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun getCurrentKeyboardHeight(): Int {
|
|
162
|
+
val insets = ViewCompat.getRootWindowInsets(view)
|
|
163
|
+
val keyboardHeight = insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
|
|
164
|
+
val navigationBar = insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
|
|
165
|
+
|
|
166
|
+
// on hide it will be negative value, so we are using max function
|
|
167
|
+
return Math.max(toDp((keyboardHeight - navigationBar).toFloat(), context), 0)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun sendEventToJS(event: Event<*>) {
|
|
171
|
+
val eventDispatcher: EventDispatcher? =
|
|
172
|
+
UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
173
|
+
eventDispatcher?.dispatchEvent(event)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private fun emitEvent(event: String, params: WritableMap) {
|
|
177
|
+
context?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit(event, params)
|
|
178
|
+
|
|
179
|
+
Log.i(TAG, event)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fun getEventParams(height: Int): WritableMap {
|
|
183
|
+
val params: WritableMap = Arguments.createMap()
|
|
184
|
+
params.putInt("height", height)
|
|
185
|
+
|
|
186
|
+
return params
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -2,38 +2,26 @@ package com.reactnativekeyboardcontroller
|
|
|
2
2
|
|
|
3
3
|
import android.view.WindowManager
|
|
4
4
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
-
import com.facebook.react.bridge.ReactMethod
|
|
7
5
|
import com.facebook.react.bridge.UiThreadUtil
|
|
8
6
|
|
|
9
|
-
class
|
|
7
|
+
class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationContext) {
|
|
10
8
|
private val mDefaultMode: Int = mReactContext.currentActivity?.window?.attributes?.softInputMode ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
|
|
11
9
|
|
|
12
|
-
override fun getName(): String = "KeyboardController"
|
|
13
|
-
|
|
14
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
15
10
|
fun setInputMode(mode: Int) {
|
|
16
11
|
setSoftInputMode(mode)
|
|
17
12
|
}
|
|
18
13
|
|
|
19
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
20
14
|
fun setDefaultMode() {
|
|
21
15
|
setSoftInputMode(mDefaultMode)
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
@ReactMethod
|
|
25
|
-
fun addListener(eventName: String?) {
|
|
26
|
-
/* Required for RN built-in Event Emitter Calls. */
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@ReactMethod
|
|
30
|
-
fun removeListeners(count: Int?) {
|
|
31
|
-
/* Required for RN built-in Event Emitter Calls. */
|
|
32
|
-
}
|
|
33
|
-
|
|
34
18
|
private fun setSoftInputMode(mode: Int) {
|
|
35
19
|
UiThreadUtil.runOnUiThread {
|
|
36
20
|
mReactContext.currentActivity?.window?.setSoftInputMode(mode)
|
|
37
21
|
}
|
|
38
22
|
}
|
|
23
|
+
|
|
24
|
+
companion object {
|
|
25
|
+
const val NAME = "KeyboardController"
|
|
26
|
+
}
|
|
39
27
|
}
|
package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt
CHANGED
|
@@ -4,6 +4,7 @@ import com.facebook.react.ReactPackage
|
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import com.facebook.soloader.SoLoader
|
|
7
8
|
|
|
8
9
|
class KeyboardControllerPackage : ReactPackage {
|
|
9
10
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
@@ -11,6 +12,10 @@ class KeyboardControllerPackage : ReactPackage {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
15
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
16
|
+
SoLoader.loadLibrary("reactnativekeyboardcontroller_modules")
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
return listOf(KeyboardControllerViewManager(reactContext))
|
|
15
20
|
}
|
|
16
21
|
}
|
package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManagerImpl.kt
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import androidx.appcompat.widget.FitWindowsLinearLayout
|
|
5
|
+
import androidx.core.view.ViewCompat
|
|
6
|
+
import androidx.core.view.WindowInsetsAnimationCompat
|
|
7
|
+
import androidx.core.view.WindowInsetsCompat
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.common.MapBuilder
|
|
10
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
11
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
12
|
+
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
|
|
13
|
+
|
|
14
|
+
class KeyboardControllerViewManagerImpl(reactContext: ReactApplicationContext) {
|
|
15
|
+
private val TAG = KeyboardControllerViewManagerImpl::class.qualifiedName
|
|
16
|
+
private var mReactContext = reactContext
|
|
17
|
+
private var isStatusBarTranslucent = false
|
|
18
|
+
|
|
19
|
+
fun createViewInstance(reactContext: ThemedReactContext): ReactViewGroup {
|
|
20
|
+
val view = EdgeToEdgeReactViewGroup(reactContext)
|
|
21
|
+
val activity = mReactContext.currentActivity
|
|
22
|
+
|
|
23
|
+
if (activity == null) {
|
|
24
|
+
Log.w(TAG, "Can not setup keyboard animation listener, since `currentActivity` is null")
|
|
25
|
+
return view
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
val callback = KeyboardAnimationCallback(
|
|
29
|
+
view = view,
|
|
30
|
+
persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
|
|
31
|
+
deferredInsetTypes = WindowInsetsCompat.Type.ime(),
|
|
32
|
+
// We explicitly allow dispatch to continue down to binding.messageHolder's
|
|
33
|
+
// child views, so that step 2.5 below receives the call
|
|
34
|
+
dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
|
|
35
|
+
context = mReactContext,
|
|
36
|
+
onApplyWindowInsetsListener = { v, insets ->
|
|
37
|
+
val content =
|
|
38
|
+
mReactContext.currentActivity?.window?.decorView?.rootView?.findViewById<FitWindowsLinearLayout>(
|
|
39
|
+
R.id.action_bar_root
|
|
40
|
+
)
|
|
41
|
+
content?.setPadding(
|
|
42
|
+
0, if (this.isStatusBarTranslucent) 0 else insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0, 0,
|
|
43
|
+
insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
insets
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
ViewCompat.setWindowInsetsAnimationCallback(view, callback)
|
|
50
|
+
ViewCompat.setOnApplyWindowInsetsListener(view, callback)
|
|
51
|
+
|
|
52
|
+
return view
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun setStatusBarTranslucent(view: ReactViewGroup, isStatusBarTranslucent: Boolean) {
|
|
56
|
+
this.isStatusBarTranslucent = isStatusBarTranslucent
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
60
|
+
val map: MutableMap<String, Any> = MapBuilder.of(
|
|
61
|
+
KeyboardTransitionEvent.EVENT_NAME,
|
|
62
|
+
MapBuilder.of("registrationName", "onKeyboardMove")
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return map
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
companion object {
|
|
69
|
+
const val NAME = "KeyboardControllerView"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -8,18 +8,13 @@ import androidx.annotation.RequiresApi
|
|
|
8
8
|
import androidx.core.view.WindowInsetsCompat
|
|
9
9
|
import androidx.core.view.WindowInsetsControllerCompat
|
|
10
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
12
|
-
import com.facebook.react.bridge.ReactMethod
|
|
13
11
|
import com.facebook.react.bridge.UiThreadUtil
|
|
14
12
|
|
|
15
|
-
class
|
|
16
|
-
private val TAG =
|
|
13
|
+
class StatusBarManagerCompatImpl(private val mReactContext: ReactApplicationContext) {
|
|
14
|
+
private val TAG = StatusBarManagerCompatImpl::class.qualifiedName
|
|
17
15
|
private var controller: WindowInsetsControllerCompat? = null
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@ReactMethod
|
|
22
|
-
private fun setHidden(hidden: Boolean) {
|
|
17
|
+
fun setHidden(hidden: Boolean) {
|
|
23
18
|
UiThreadUtil.runOnUiThread {
|
|
24
19
|
if (hidden) {
|
|
25
20
|
getController()?.hide(WindowInsetsCompat.Type.statusBars())
|
|
@@ -30,8 +25,7 @@ class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationCo
|
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
|
33
|
-
|
|
34
|
-
private fun setColor(color: Int, animated: Boolean) {
|
|
28
|
+
fun setColor(color: Int, animated: Boolean) {
|
|
35
29
|
val activity = mReactContext.currentActivity
|
|
36
30
|
if (activity == null) {
|
|
37
31
|
Log.w(TAG, "StatusBarManagerCompatModule: Ignored status bar change, current activity is null.")
|
|
@@ -55,8 +49,7 @@ class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationCo
|
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
private fun setTranslucent(translucent: Boolean) {
|
|
52
|
+
fun setTranslucent(translucent: Boolean) {
|
|
60
53
|
// the status bar is translucent by default (once you wrapped App in Provider,
|
|
61
54
|
// and EdgeToEdgeReactViewGroup has been mounted and called
|
|
62
55
|
// `setDecorFitsSystemWindows(window, false)`. By default this library applies default padding
|
|
@@ -70,8 +63,7 @@ class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationCo
|
|
|
70
63
|
// app requires to dynamically manage it - just shoot an issue and I will try to add a support fot that.
|
|
71
64
|
}
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
private fun setStyle(style: String) {
|
|
66
|
+
fun setStyle(style: String) {
|
|
75
67
|
UiThreadUtil.runOnUiThread {
|
|
76
68
|
getController()?.isAppearanceLightStatusBars = style == "dark-content"
|
|
77
69
|
}
|
|
@@ -92,4 +84,8 @@ class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationCo
|
|
|
92
84
|
|
|
93
85
|
return this.controller
|
|
94
86
|
}
|
|
87
|
+
|
|
88
|
+
companion object {
|
|
89
|
+
const val NAME = "StatusBarManagerCompat"
|
|
90
|
+
}
|
|
95
91
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
KEYBOARD_CONTROLLER_MAIN_THIS_DIR := $(call my-dir)
|
|
2
|
+
|
|
3
|
+
include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
|
|
4
|
+
|
|
5
|
+
include $(KEYBOARD_CONTROLLER_MAIN_THIS_DIR)/../../../build/generated/source/codegen/jni/Android.mk
|
|
6
|
+
|
|
7
|
+
include $(CLEAR_VARS)
|
|
8
|
+
|
|
9
|
+
LOCAL_PATH := $(KEYBOARD_CONTROLLER_MAIN_THIS_DIR)
|
|
10
|
+
LOCAL_MODULE := reactnativekeyboardcontroller_modules
|
|
11
|
+
|
|
12
|
+
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(GENERATED_SRC_DIR)/codegen/jni
|
|
13
|
+
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
|
|
14
|
+
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) $(GENERATED_SRC_DIR)/codegen/jni
|
|
15
|
+
|
|
16
|
+
# Please note as one of the library listed is libreact_codegen_samplelibrary
|
|
17
|
+
# This name will be generated as libreact_codegen_<library-name>
|
|
18
|
+
# where <library-name> is the one you specified in the Gradle configuration
|
|
19
|
+
LOCAL_SHARED_LIBRARIES := libjsi \
|
|
20
|
+
libfbjni \
|
|
21
|
+
libglog \
|
|
22
|
+
libfolly_runtime \
|
|
23
|
+
libyoga \
|
|
24
|
+
libreact_nativemodule_core \
|
|
25
|
+
libturbomodulejsijni \
|
|
26
|
+
librrc_view \
|
|
27
|
+
libreact_render_core \
|
|
28
|
+
libreact_render_graphics \
|
|
29
|
+
libfabricjni \
|
|
30
|
+
libreact_debug \
|
|
31
|
+
libreact_render_componentregistry \
|
|
32
|
+
libreact_render_debug \
|
|
33
|
+
libruntimeexecutor \
|
|
34
|
+
libreact_render_mapbuffer \
|
|
35
|
+
libreact_codegen_rncore \
|
|
36
|
+
libreact_codegen_reactnativekeyboardcontroller
|
|
37
|
+
|
|
38
|
+
LOCAL_CFLAGS := \
|
|
39
|
+
-DLOG_TAG=\"ReactNative\"
|
|
40
|
+
LOCAL_CFLAGS += -fexceptions -frtti -std=c++17 -Wall
|
|
41
|
+
|
|
42
|
+
include $(BUILD_SHARED_LIBRARY)
|