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/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 1ebad6ca29..46b01278b1 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt @@ -14,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 @@ -130,14 +131,7 @@ class EdgeToEdgeReactViewGroup( ) content?.layoutParams = params - val defaultInsets = ViewCompat.onApplyWindowInsets(v, insets) - - defaultInsets.replaceSystemWindowInsets( - defaultInsets.systemWindowInsetLeft, - if (this.isStatusBarTranslucent) 0 else defaultInsets.systemWindowInsetTop, - defaultInsets.systemWindowInsetRight, - defaultInsets.systemWindowInsetBottom, - ) + v.replaceStatusBarInsets(insets, this.isStatusBarTranslucent, active) } } }