Skip to content

fix: react-native-navigation integration #149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Keyboard manager which works in identical way on both iOS and Android.
- module for changing soft input mode on Android 🤔
- reanimated support 🚀
- interactive keyboard dismissing 👆📱
- works with any navigation library 🧭
- and more is coming... Stay tuned! 😊

## Installation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.reactnativekeyboardcontroller.extensions

import android.view.ViewGroup

fun ViewGroup?.removeSelf() {
this ?: return
val parent = parent as? ViewGroup ?: return

parent.removeView(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil

class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationContext) {
private val mDefaultMode: Int = mReactContext
.currentActivity
?.window
?.attributes
?.softInputMode
?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
private val mDefaultMode: Int = getCurrentMode()

fun setInputMode(mode: Int) {
setSoftInputMode(mode)
Expand All @@ -22,10 +17,21 @@ class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationCo

private fun setSoftInputMode(mode: Int) {
UiThreadUtil.runOnUiThread {
mReactContext.currentActivity?.window?.setSoftInputMode(mode)
if (getCurrentMode() != mode) {
mReactContext.currentActivity?.window?.setSoftInputMode(mode)
}
}
}

private fun getCurrentMode(): Int {
return mReactContext
.currentActivity
?.window
?.attributes
?.softInputMode
?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
}

companion object {
const val NAME = "KeyboardController"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.core.view.WindowInsetsCompat
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.KeyboardAnimationCallback
import com.reactnativekeyboardcontroller.extensions.removeSelf
import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached
import com.reactnativekeyboardcontroller.extensions.rootView

Expand All @@ -22,36 +23,72 @@ private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
private var isStatusBarTranslucent = false
private var isNavigationBarTranslucent = false
private var eventView: ReactViewGroup? = null

init {
val activity = reactContext.currentActivity
// region View lifecycles
override fun onAttachedToWindow() {
super.onAttachedToWindow()

if (activity != null) {
val callback = KeyboardAnimationCallback(
view = this,
persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
deferredInsetTypes = WindowInsetsCompat.Type.ime(),
// We explicitly allow dispatch to continue down to binding.messageHolder's
// child views, so that step 2.5 below receives the call
dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
context = reactContext,
)
ViewCompat.setWindowInsetsAnimationCallback(this, callback)
ViewCompat.setOnApplyWindowInsetsListener(this, callback)
this.requestApplyInsetsWhenAttached()
} else {
val activity = reactContext.currentActivity
if (activity == null) {
Log.w(TAG, "Can not setup keyboard animation listener, since `currentActivity` is null")
return
}

Handler(Looper.getMainLooper()).post(this::setupWindowInsets)
WindowCompat.setDecorFitsSystemWindows(
activity.window,
false,
)

eventView = ReactViewGroup(context)
val root = this.getContentView()
root?.addView(eventView)

val callback = KeyboardAnimationCallback(
view = this,
persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
deferredInsetTypes = WindowInsetsCompat.Type.ime(),
dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
context = reactContext,
)

eventView?.let {
ViewCompat.setWindowInsetsAnimationCallback(it, callback)
ViewCompat.setOnApplyWindowInsetsListener(it, callback)
it.requestApplyInsetsWhenAttached()
}
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()

eventView.removeSelf()
}
// endregion

// region Props setters
fun setStatusBarTranslucent(isStatusBarTranslucent: Boolean) {
this.isStatusBarTranslucent = isStatusBarTranslucent
}

fun setNavigationBarTranslucent(isNavigationBarTranslucent: Boolean) {
this.isNavigationBarTranslucent = isNavigationBarTranslucent
}
// endregion

// region Private functions/class helpers
private fun getContentView(): FitWindowsLinearLayout? {
return reactContext.currentActivity?.window?.decorView?.rootView?.findViewById(
androidx.appcompat.R.id.action_bar_root,
)
}

private fun setupWindowInsets() {
val rootView = reactContext.rootView
if (rootView != null) {
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
val content =
reactContext.rootView?.findViewById<FitWindowsLinearLayout>(
androidx.appcompat.R.id.action_bar_root,
)
val content = getContentView()
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT,
Expand Down Expand Up @@ -80,24 +117,5 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
}
}
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()

Handler(Looper.getMainLooper()).post(this::setupWindowInsets)
reactContext.currentActivity?.let {
WindowCompat.setDecorFitsSystemWindows(
it.window,
false,
)
}
}

fun setStatusBarTranslucent(isStatusBarTranslucent: Boolean) {
this.isStatusBarTranslucent = isStatusBarTranslucent
}

fun setNavigationBarTranslucent(isNavigationBarTranslucent: Boolean) {
this.isNavigationBarTranslucent = isNavigationBarTranslucent
}
// endregion
}