From 5f7d16eae620c9e318ee6a261da3220326763e33 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 24 Sep 2025 11:25:05 +0200 Subject: [PATCH 1/3] fix: double keyboard height --- .../views/EdgeToEdgeReactViewGroup.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt index 1ebad6ca29..16339a5fd7 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt @@ -21,6 +21,7 @@ import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConf import com.reactnativekeyboardcontroller.log.Logger import com.reactnativekeyboardcontroller.modal.ModalAttachedWatcher import java.lang.ref.WeakReference +import kotlin.math.min private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName @@ -136,7 +137,14 @@ class EdgeToEdgeReactViewGroup( defaultInsets.systemWindowInsetLeft, if (this.isStatusBarTranslucent) 0 else defaultInsets.systemWindowInsetTop, defaultInsets.systemWindowInsetRight, - defaultInsets.systemWindowInsetBottom, + if (active) { + min( + defaultInsets.systemWindowInsetBottom, + navBarInsets.bottom, + ) + } else { + defaultInsets.systemWindowInsetBottom + }, ) } } From 02f76f39e421f63583fc02b17c04a5f95b0261c6 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 24 Sep 2025 16:55:51 +0200 Subject: [PATCH 2/3] fix: avoid deprecated API usage --- .github/workflows/android-e2e-test.yml | 2 +- .../views/EdgeToEdgeReactViewGroup.kt | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/android-e2e-test.yml b/.github/workflows/android-e2e-test.yml index acc10cf729..dba222a258 100644 --- a/.github/workflows/android-e2e-test.yml +++ b/.github/workflows/android-e2e-test.yml @@ -80,7 +80,7 @@ jobs: e2e-test: name: ⚙️ Automated test cases runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 90 env: WORKING_DIRECTORY: example concurrency: diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt index 16339a5fd7..9f1eb5f200 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt @@ -6,6 +6,7 @@ import android.os.Handler import android.os.Looper import android.view.WindowManager import android.widget.FrameLayout +import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat @@ -131,21 +132,20 @@ class EdgeToEdgeReactViewGroup( ) content?.layoutParams = params - val defaultInsets = ViewCompat.onApplyWindowInsets(v, insets) - - defaultInsets.replaceSystemWindowInsets( - defaultInsets.systemWindowInsetLeft, - if (this.isStatusBarTranslucent) 0 else defaultInsets.systemWindowInsetTop, - defaultInsets.systemWindowInsetRight, - if (active) { - min( - defaultInsets.systemWindowInsetBottom, - navBarInsets.bottom, - ) - } else { - defaultInsets.systemWindowInsetBottom - }, - ) + val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) + val adjustedTop = if (isStatusBarTranslucent) 0 else sysBars.top + // pick bottom: use IME if present, otherwise nav bar bottom (respect translucency) + val bottomFromImeOrNav = if (ime.bottom > 0) ime.bottom else navBars.bottom + val adjustedInsets = WindowInsetsCompat.Builder(insets) + .setInsets( + WindowInsetsCompat.Type.systemBars(), + Insets.of(sysBars.left, adjustedTop, sysBars.right, if (active) sysBars.bottom else bottomFromImeOrNav) + ) + .build() + + ViewCompat.onApplyWindowInsets(v, adjustedInsets) } } } From 1363dc8fb981f8dd0b40c0fd33d0f04a2238295c Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 24 Sep 2025 18:40:15 +0200 Subject: [PATCH 3/3] fix: keep using deprecated API on old Android versions --- .../extensions/View.kt | 41 +++++++++++++++++++ .../views/EdgeToEdgeReactViewGroup.kt | 18 +------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt b/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt index 047bc85fee..8cf1ff5eba 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt @@ -4,6 +4,9 @@ import android.annotation.SuppressLint import android.graphics.Rect import android.os.Build import android.view.View +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import com.reactnativekeyboardcontroller.log.Logger /** @@ -58,3 +61,41 @@ val View.screenLocation get(): IntArray { return point } + +/** + * Safely replaces status bar insets so that when we edge-to-edge mode gets disabled/enabled + * the app content is not jumping/resizing a window. + * */ +@Suppress("DEPRECATION") +fun View.replaceStatusBarInsets( + insets: WindowInsetsCompat, + isStatusBarTranslucent: Boolean, + active: Boolean, +): WindowInsetsCompat { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) + val adjustedTop = if (isStatusBarTranslucent) 0 else sysBars.top + // pick bottom: use IME if present, otherwise nav bar bottom (respect translucency) + val bottomFromImeOrNav = if (ime.bottom > 0) ime.bottom else navBars.bottom + val adjustedInsets = + WindowInsetsCompat + .Builder(insets) + .setInsets( + WindowInsetsCompat.Type.systemBars(), + Insets.of(sysBars.left, adjustedTop, sysBars.right, if (active) sysBars.bottom else bottomFromImeOrNav), + ).build() + + return ViewCompat.onApplyWindowInsets(this, adjustedInsets) + } else { + val defaultInsets = ViewCompat.onApplyWindowInsets(this, insets) + + return defaultInsets.replaceSystemWindowInsets( + defaultInsets.systemWindowInsetLeft, + if (isStatusBarTranslucent) 0 else defaultInsets.systemWindowInsetTop, + defaultInsets.systemWindowInsetRight, + defaultInsets.systemWindowInsetBottom, + ) + } +} diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt index 9f1eb5f200..46b01278b1 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt @@ -6,7 +6,6 @@ import android.os.Handler import android.os.Looper import android.view.WindowManager import android.widget.FrameLayout -import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat @@ -15,6 +14,7 @@ import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.views.view.ReactViewGroup import com.reactnativekeyboardcontroller.extensions.content import com.reactnativekeyboardcontroller.extensions.removeSelf +import com.reactnativekeyboardcontroller.extensions.replaceStatusBarInsets import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached import com.reactnativekeyboardcontroller.extensions.rootView import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback @@ -22,7 +22,6 @@ import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConf import com.reactnativekeyboardcontroller.log.Logger import com.reactnativekeyboardcontroller.modal.ModalAttachedWatcher import java.lang.ref.WeakReference -import kotlin.math.min private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName @@ -132,20 +131,7 @@ class EdgeToEdgeReactViewGroup( ) content?.layoutParams = params - val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - val navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) - val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) - val adjustedTop = if (isStatusBarTranslucent) 0 else sysBars.top - // pick bottom: use IME if present, otherwise nav bar bottom (respect translucency) - val bottomFromImeOrNav = if (ime.bottom > 0) ime.bottom else navBars.bottom - val adjustedInsets = WindowInsetsCompat.Builder(insets) - .setInsets( - WindowInsetsCompat.Type.systemBars(), - Insets.of(sysBars.left, adjustedTop, sysBars.right, if (active) sysBars.bottom else bottomFromImeOrNav) - ) - .build() - - ViewCompat.onApplyWindowInsets(v, adjustedInsets) + v.replaceStatusBarInsets(insets, this.isStatusBarTranslucent, active) } } }