From 72d7ad62c145fd54e1cda53fd949357401d4dac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Mon, 17 Nov 2025 16:11:39 +0100 Subject: [PATCH 01/45] Working, but why? --- .../java/com/swmansion/rnscreens/Screen.kt | 10 +- .../rnscreens/ScreenStackFragment.kt | 7 ++ .../BottomSheetTransitionCoordinator.kt | 102 ++++++++++++++++++ .../rnscreens/bottomsheet/SheetDelegate.kt | 25 ++++- .../rnscreens/bottomsheet/SheetUtils.kt | 7 ++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 257439e63c..dcc2313c38 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -212,7 +212,9 @@ class Screen( } } - private fun triggerPostponedEnterTransitionIfNeeded() { + // TODO(@t0maboro): + // 1. It cannot be public, prepare some better solution for BottomSheetTransitionCoordinator use-case + public fun triggerPostponedEnterTransitionIfNeeded() { if (shouldTriggerPostponedTransitionAfterLayout) { shouldTriggerPostponedTransitionAfterLayout = false // This will trigger enter transition only if one was requested by ScreenStack @@ -235,10 +237,14 @@ class Screen( ) } + // TODO(@t0maboro): + // 1. It cannot be public + // 2. Figure out whether I need to call this from BottomSheetTransitionCoordinator + /** * @param offsetY ignored on old architecture */ - private fun dispatchShadowStateUpdate( + public fun dispatchShadowStateUpdate( width: Int, height: Int, offsetY: Int, diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index f7f654ac98..618c59d556 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -27,6 +27,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel +import com.swmansion.rnscreens.bottomsheet.BottomSheetTransitionCoordinator import com.swmansion.rnscreens.bottomsheet.DimmingViewManager import com.swmansion.rnscreens.bottomsheet.SheetDelegate import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation @@ -55,6 +56,8 @@ class ScreenStackFragment : private var isToolbarShadowHidden = false private var isToolbarTranslucent = false + private lateinit var sheetTransitionCoordinator: BottomSheetTransitionCoordinator + private var lastFocusedChild: View? = null var searchView: CustomSearchView? = null @@ -229,6 +232,10 @@ class ScreenStackFragment : dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout) dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!) + // TODO(@t0maboro): + // 1. We should expose methods from BottomSheetTransitionCoordinator and pass attributes directly to methods + sheetTransitionCoordinator = BottomSheetTransitionCoordinator(screen, sheetDelegate, coordinatorLayout) + // Pre-layout the content for the sake of enter transition. val container = screen.container!! diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt new file mode 100644 index 0000000000..33d1602215 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -0,0 +1,102 @@ +package com.swmansion.rnscreens.bottomsheet + +import android.os.Build +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import androidx.annotation.RequiresApi +import androidx.core.view.WindowInsetsCompat +import com.swmansion.rnscreens.Screen + +class BottomSheetTransitionCoordinator( + private val screen: Screen, + private val sheetDelegate: SheetDelegate, + private val coordinatorLayout: ViewGroup, +) { + private var isLayoutComplete = false + private var areInsetsApplied = false + + init { + screen.container?.apply { + // TODO(@t0maboro): + // 1. It requires API level 30 - we need to add support for lower API levels + setOnApplyWindowInsetsListener(::onScreenContainerInsetsApplied) + addOnLayoutChangeListener(::onScreenContainerLayoutChanged) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun onScreenContainerInsetsApplied( + view: View, + insets: WindowInsets, + ): WindowInsets { + // TODO(@t0maboro): + // 1. Without this line, FormSheet with TextInput is reconfiguring bottom sheet with state.collapsed for some reason + // 2. TextInput with medium/large detent is causing some reconfiguration and flicker on dismissing with swipe with keyboard open + // 3. We should check here whether insets has changed + if (areInsetsApplied) return insets + + val prevInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + + // TODO(@t0maboro): + // 1. Reconfiguration is crucial, because we're updating the maximum height for the screen with the system bars insets + sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) + + // TODO(@t0maboro): + // 1. We need to copy these calls from ScreenStackFragment + // 2. forceLayout is crucial for repeating the same logic for measure as in ScreenStackFragment + screen.container?.let { container -> + coordinatorLayout.forceLayout() + coordinatorLayout.measure( + View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY), + ) + coordinatorLayout.layout(0, 0, container.width, container.height) + } + + // TODO(@t0maboro): + // 1. Without this call, ScreenContentWrapper size doesn't adapt to screen size + // 2. Figure out the proper value for `prevInset.top` + screen.dispatchShadowStateUpdate( + screen.width, + screen.height, + prevInsets.top, + ) + + areInsetsApplied = true + triggerSheetEnterTransitionIfReady() + + // TODO(@t0maboro): + // 1. SheetDelegate has onApplyWindowInsets method - we should ensure that both are coordinated well + return WindowInsets + .Builder(insets) + .setInsets( + WindowInsetsCompat.Type.systemBars(), + android.graphics.Insets.of(prevInsets.left, 0, prevInsets.right, prevInsets.bottom), + ).build() + } + + private fun onScreenContainerLayoutChanged( + view: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int, + ) { + isLayoutComplete = true + triggerSheetEnterTransitionIfReady() + } + + private fun triggerSheetEnterTransitionIfReady() { + if (!isLayoutComplete || !areInsetsApplied) return + + // TODO(@t0maboro): + // 1. Calling some field that I don't want to use directly outside Screen class + screen.shouldTriggerPostponedTransitionAfterLayout = true + screen.triggerPostponedEnterTransitionIfNeeded() + } +} diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index ac86a9641a..89222791e8 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -6,6 +6,7 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context import android.os.Build +import android.util.Log import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager @@ -37,6 +38,7 @@ class SheetDelegate( private var isSheetAnimationInProgress: Boolean = false + private var lastTopInset: Int = 0 private var lastKeyboardBottomOffset: Int = 0 var lastStableDetentIndex: Int = screen.sheetInitialDetentIndex @@ -166,7 +168,7 @@ class SheetDelegate( firstHeight = screen.sheetDetents.firstHeight(containerHeight), halfExpandedRatio = screen.sheetDetents.halfExpandedRatio(), maxAllowedHeight = screen.sheetDetents.maxAllowedHeight(containerHeight), - expandedOffsetFromTop = screen.sheetDetents.expandedOffsetFromTop(containerHeight), + expandedOffsetFromTop = screen.sheetDetents.expandedOffsetFromTop(containerHeight, lastTopInset), ) else -> throw IllegalStateException( @@ -243,7 +245,7 @@ class SheetDelegate( firstHeight = screen.sheetDetents.firstHeight(containerHeight), halfExpandedRatio = screen.sheetDetents.halfExpandedRatio(), maxAllowedHeight = screen.sheetDetents.maxAllowedHeight(containerHeight), - expandedOffsetFromTop = screen.sheetDetents.maxAllowedHeight(containerHeight), + expandedOffsetFromTop = screen.sheetDetents.expandedOffsetFromTop(containerHeight, lastTopInset), ) else -> throw IllegalStateException( @@ -290,6 +292,10 @@ class SheetDelegate( val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime()) val prevSystemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + // TODO(@t0maboro): + // 1. lastTopInset needs to have some explanation why it's needed here + lastTopInset = prevSystemBarsInsets.top + if (isImeVisible) { isKeyboardVisible = true keyboardState = KeyboardVisible(imeInset.bottom) @@ -335,22 +341,31 @@ class SheetDelegate( * this is acceptable. */ private fun tryResolveContainerHeight(): Int? { - screen.container?.let { return it.height } + // TODO(@t0maboro): + // 1. lastTopInset needs to have some explanation why it's needed here + // 2. I shouldn't touch this method at all and subtract this value somewhere else + screen.container?.let { return it.height - lastTopInset } val context = screen.reactContext + // TODO(@t0maboro): + // 1. lastTopInset needs to have some explanation why it's needed here + // 2. I shouldn't touch this method at all and subtract this value somewhere else context .resources ?.displayMetrics ?.heightPixels - ?.let { return it } + ?.let { return it - lastTopInset } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // TODO(@t0maboro): + // 1. lastTopInset needs to have some explanation why it's needed here + // 2. I shouldn't touch this method at all and subtract this value somewhere else (context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager) ?.currentWindowMetrics ?.bounds ?.height() - ?.let { return it } + ?.let { return it - lastTopInset } } return null } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt index 8981cd2718..161be83120 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt @@ -144,6 +144,13 @@ fun Screen.requiresEnterTransitionPostponing(): Boolean { // there. Tween animations have some magic way to make this work (maybe they // postpone the transition internally, dunno). + // TODO(@t0maboro): + // 1. Only for testing purposes + // 2. We need to have a dedicated mechanism for Fabric + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && this.usesFormSheetPresentation()) { + return true + } + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED || !this.usesFormSheetPresentation()) { return false } From 350ffc6d3e0ecbfbb5753df78a6f2ebeb4c8dd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 09:56:11 +0100 Subject: [PATCH 02/45] Make function internal --- .../java/com/swmansion/rnscreens/Screen.kt | 21 ++++++++++++++----- .../BottomSheetTransitionCoordinator.kt | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index dcc2313c38..78ff13b9b6 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -89,9 +89,13 @@ class Screen( var sheetElevation: Float = 24F /** - * When using form sheet presentation we want to delay enter transition **on Paper** in order + * On Paper, when using form sheet presentation we want to delay enter transition in order * to wait for initial layout from React, otherwise the animator-based animation will look - * glitchy. *This is not needed on Fabric*. + * glitchy. + * + * On Fabric, the view layout is completed before window insets are applied. + * To ensure the BottomSheet correctly respects insets during its enter transition, + * we delay the transition until both layout and insets have been applied. */ var shouldTriggerPostponedTransitionAfterLayout = false @@ -212,9 +216,16 @@ class Screen( } } - // TODO(@t0maboro): - // 1. It cannot be public, prepare some better solution for BottomSheetTransitionCoordinator use-case - public fun triggerPostponedEnterTransitionIfNeeded() { + // On Fabric, the view layout is completed before window insets are applied. + // To ensure the BottomSheet correctly respects insets during its enter transition, + // we delay the transition until both layout and insets have been applied. + internal fun requestPostponingEnterTransition() { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + shouldTriggerPostponedTransitionAfterLayout = true + } + } + + internal fun triggerPostponedEnterTransitionIfNeeded() { if (shouldTriggerPostponedTransitionAfterLayout) { shouldTriggerPostponedTransitionAfterLayout = false // This will trigger enter transition only if one was requested by ScreenStack diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 33d1602215..3233050308 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -96,7 +96,7 @@ class BottomSheetTransitionCoordinator( // TODO(@t0maboro): // 1. Calling some field that I don't want to use directly outside Screen class - screen.shouldTriggerPostponedTransitionAfterLayout = true + screen.requestPostponingEnterTransition() screen.triggerPostponedEnterTransitionIfNeeded() } } From af5d90eb9e89b21b190e16692c464cdb98090cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 09:59:56 +0100 Subject: [PATCH 03/45] Make internal --- .../main/java/com/swmansion/rnscreens/Screen.kt | 6 +----- .../BottomSheetTransitionCoordinator.kt | 15 +++++++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 78ff13b9b6..bdbf61d894 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -248,14 +248,10 @@ class Screen( ) } - // TODO(@t0maboro): - // 1. It cannot be public - // 2. Figure out whether I need to call this from BottomSheetTransitionCoordinator - /** * @param offsetY ignored on old architecture */ - public fun dispatchShadowStateUpdate( + internal fun dispatchShadowStateUpdate( width: Int, height: Int, offsetY: Int, diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 3233050308..f0e8cfc71b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import android.view.WindowInsets import androidx.annotation.RequiresApi import androidx.core.view.WindowInsetsCompat +import com.swmansion.rnscreens.BuildConfig import com.swmansion.rnscreens.Screen class BottomSheetTransitionCoordinator( @@ -56,12 +57,14 @@ class BottomSheetTransitionCoordinator( // TODO(@t0maboro): // 1. Without this call, ScreenContentWrapper size doesn't adapt to screen size - // 2. Figure out the proper value for `prevInset.top` - screen.dispatchShadowStateUpdate( - screen.width, - screen.height, - prevInsets.top, - ) + // 2. Fix this for Paper + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + screen.dispatchShadowStateUpdate( + screen.width, + screen.height, + prevInsets.top, + ) + } areInsetsApplied = true triggerSheetEnterTransitionIfReady() From 8ada35810cdc15bc8053261630000f0e365ff8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 10:28:19 +0100 Subject: [PATCH 04/45] Add some comments --- android/src/main/java/com/swmansion/rnscreens/Screen.kt | 2 +- .../bottomsheet/BottomSheetTransitionCoordinator.kt | 4 +--- .../java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt | 6 ++++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index bdbf61d894..0bc32ea932 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -219,7 +219,7 @@ class Screen( // On Fabric, the view layout is completed before window insets are applied. // To ensure the BottomSheet correctly respects insets during its enter transition, // we delay the transition until both layout and insets have been applied. - internal fun requestPostponingEnterTransition() { + internal fun requestTriggeringPostponedEnterTransition() { if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { shouldTriggerPostponedTransitionAfterLayout = true } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index f0e8cfc71b..12dcda4ffb 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -97,9 +97,7 @@ class BottomSheetTransitionCoordinator( private fun triggerSheetEnterTransitionIfReady() { if (!isLayoutComplete || !areInsetsApplied) return - // TODO(@t0maboro): - // 1. Calling some field that I don't want to use directly outside Screen class - screen.requestPostponingEnterTransition() + screen.requestTriggeringPostponedEnterTransition() screen.triggerPostponedEnterTransitionIfNeeded() } } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt index 161be83120..c1fa2b400b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt @@ -143,6 +143,12 @@ fun Screen.requiresEnterTransitionPostponing(): Boolean { // This is used only for formSheet presentation, because we use value animators // there. Tween animations have some magic way to make this work (maybe they // postpone the transition internally, dunno). + // + // On Fabric, system insets are applied after the initial layout pass. However, + // the BottomSheet height might be measured earlier due to internal BottomSheet logic + // or layout callbacks, before those insets are applied. + // To ensure the BottomSheet height respects the top inset we delay starting the enter + // transition until both layout and insets are fully applied. // TODO(@t0maboro): // 1. Only for testing purposes From ca5a0f12d340c4091a402fa7681b7185689f0c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 10:28:34 +0100 Subject: [PATCH 05/45] Remove attributes --- .../rnscreens/ScreenStackFragment.kt | 5 +- .../BottomSheetTransitionCoordinator.kt | 52 ++++++++----------- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 618c59d556..9d76e16c80 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -232,9 +232,8 @@ class ScreenStackFragment : dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout) dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!) - // TODO(@t0maboro): - // 1. We should expose methods from BottomSheetTransitionCoordinator and pass attributes directly to methods - sheetTransitionCoordinator = BottomSheetTransitionCoordinator(screen, sheetDelegate, coordinatorLayout) + sheetTransitionCoordinator = BottomSheetTransitionCoordinator() + sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(screen, sheetDelegate, coordinatorLayout) // Pre-layout the content for the sake of enter transition. diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 12dcda4ffb..d2a0dd3583 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -9,27 +9,37 @@ import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.BuildConfig import com.swmansion.rnscreens.Screen -class BottomSheetTransitionCoordinator( - private val screen: Screen, - private val sheetDelegate: SheetDelegate, - private val coordinatorLayout: ViewGroup, -) { +class BottomSheetTransitionCoordinator() { private var isLayoutComplete = false private var areInsetsApplied = false - init { + fun attachInsetsAndLayoutListenersToBottomSheet( + screen: Screen, + sheetDelegate: SheetDelegate, + coordinatorLayout: ViewGroup + ) { screen.container?.apply { - // TODO(@t0maboro): - // 1. It requires API level 30 - we need to add support for lower API levels - setOnApplyWindowInsetsListener(::onScreenContainerInsetsApplied) - addOnLayoutChangeListener(::onScreenContainerLayoutChanged) + setOnApplyWindowInsetsListener { view, insets -> + onScreenContainerInsetsApplied(view, insets, screen, sheetDelegate, coordinatorLayout) + } + addOnLayoutChangeListener { view, l, t, r, b, ol, ot, or, ob -> + onScreenContainerLayoutChanged(screen) + } } } + private fun onScreenContainerLayoutChanged(screen: Screen) { + isLayoutComplete = true + triggerSheetEnterTransitionIfReady(screen) + } + @RequiresApi(Build.VERSION_CODES.R) private fun onScreenContainerInsetsApplied( view: View, insets: WindowInsets, + screen: Screen, + sheetDelegate: SheetDelegate, + coordinatorLayout: ViewGroup ): WindowInsets { // TODO(@t0maboro): // 1. Without this line, FormSheet with TextInput is reconfiguring bottom sheet with state.collapsed for some reason @@ -44,8 +54,7 @@ class BottomSheetTransitionCoordinator( sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) // TODO(@t0maboro): - // 1. We need to copy these calls from ScreenStackFragment - // 2. forceLayout is crucial for repeating the same logic for measure as in ScreenStackFragment + // 1. forceLayout is crucial for repeating the same logic for measure as in ScreenStackFragment screen.container?.let { container -> coordinatorLayout.forceLayout() coordinatorLayout.measure( @@ -67,7 +76,7 @@ class BottomSheetTransitionCoordinator( } areInsetsApplied = true - triggerSheetEnterTransitionIfReady() + triggerSheetEnterTransitionIfReady(screen) // TODO(@t0maboro): // 1. SheetDelegate has onApplyWindowInsets method - we should ensure that both are coordinated well @@ -79,22 +88,7 @@ class BottomSheetTransitionCoordinator( ).build() } - private fun onScreenContainerLayoutChanged( - view: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int, - ) { - isLayoutComplete = true - triggerSheetEnterTransitionIfReady() - } - - private fun triggerSheetEnterTransitionIfReady() { + private fun triggerSheetEnterTransitionIfReady(screen: Screen) { if (!isLayoutComplete || !areInsetsApplied) return screen.requestTriggeringPostponedEnterTransition() From 05fa419766b8ec76abed9fc27eac0910c2c6752c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 10:49:53 +0100 Subject: [PATCH 06/45] Add comments --- .../BottomSheetTransitionCoordinator.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index d2a0dd3583..fbbc85a685 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -1,22 +1,20 @@ package com.swmansion.rnscreens.bottomsheet -import android.os.Build import android.view.View import android.view.ViewGroup import android.view.WindowInsets -import androidx.annotation.RequiresApi import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.BuildConfig import com.swmansion.rnscreens.Screen -class BottomSheetTransitionCoordinator() { +class BottomSheetTransitionCoordinator { private var isLayoutComplete = false private var areInsetsApplied = false fun attachInsetsAndLayoutListenersToBottomSheet( screen: Screen, sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup + coordinatorLayout: ViewGroup, ) { screen.container?.apply { setOnApplyWindowInsetsListener { view, insets -> @@ -33,13 +31,12 @@ class BottomSheetTransitionCoordinator() { triggerSheetEnterTransitionIfReady(screen) } - @RequiresApi(Build.VERSION_CODES.R) private fun onScreenContainerInsetsApplied( view: View, insets: WindowInsets, screen: Screen, sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup + coordinatorLayout: ViewGroup, ): WindowInsets { // TODO(@t0maboro): // 1. Without this line, FormSheet with TextInput is reconfiguring bottom sheet with state.collapsed for some reason @@ -49,13 +46,14 @@ class BottomSheetTransitionCoordinator() { val prevInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - // TODO(@t0maboro): - // 1. Reconfiguration is crucial, because we're updating the maximum height for the screen with the system bars insets + // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. + // When insets are available, we can factor them in to update the maximum height accordingly. sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) - // TODO(@t0maboro): - // 1. forceLayout is crucial for repeating the same logic for measure as in ScreenStackFragment screen.container?.let { container -> + // Needs to be highlighted that nothing changes at the container level. + // However, calling additional measure will trigger BottomSheetBehavior's `onMeasureChild` logic. + // This method ensures that the bottom sheet respects the maxHeight we update in `configureBottomSheetBehavior`. coordinatorLayout.forceLayout() coordinatorLayout.measure( View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY), From cbc32b77858b9a661bdf86f7680b3e7f6ba2141c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 10:57:31 +0100 Subject: [PATCH 07/45] Add comment --- .../BottomSheetTransitionCoordinator.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index fbbc85a685..bbd7338b52 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -4,7 +4,6 @@ import android.view.View import android.view.ViewGroup import android.view.WindowInsets import androidx.core.view.WindowInsetsCompat -import com.swmansion.rnscreens.BuildConfig import com.swmansion.rnscreens.Screen class BottomSheetTransitionCoordinator { @@ -63,15 +62,12 @@ class BottomSheetTransitionCoordinator { } // TODO(@t0maboro): - // 1. Without this call, ScreenContentWrapper size doesn't adapt to screen size - // 2. Fix this for Paper - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - screen.dispatchShadowStateUpdate( - screen.width, - screen.height, - prevInsets.top, - ) - } + // 1. Check paper + + // Although the layout of the screen container and CoordinatorLayout hasn't changed, + // the BottomSheetBehavior has updated the maximum height. + // We manually trigger the callback to notify that the bottom sheet layout has been applied. + screen.onBottomSheetBehaviorDidLayout(true) areInsetsApplied = true triggerSheetEnterTransitionIfReady(screen) From 1db288eecadb2134e8a0a19a42cdcff41420bfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 11:49:32 +0100 Subject: [PATCH 08/45] Split logic between architectures --- .../com/swmansion/rnscreens/bottomsheet/SheetUtils.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt index c1fa2b400b..d3697907b0 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt @@ -150,14 +150,13 @@ fun Screen.requiresEnterTransitionPostponing(): Boolean { // To ensure the BottomSheet height respects the top inset we delay starting the enter // transition until both layout and insets are fully applied. - // TODO(@t0maboro): - // 1. Only for testing purposes - // 2. We need to have a dedicated mechanism for Fabric - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && this.usesFormSheetPresentation()) { - return true + // Fabric + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + return this.usesFormSheetPresentation() } - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED || !this.usesFormSheetPresentation()) { + // Paper + if (!this.usesFormSheetPresentation()) { return false } // Assumes that formSheet uses content wrapper From b9db77e3eaed7c4692a5802210d09b30ae01c47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 13:30:58 +0100 Subject: [PATCH 09/45] Make private --- android/src/main/java/com/swmansion/rnscreens/Screen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 0bc32ea932..c33885e051 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -251,7 +251,7 @@ class Screen( /** * @param offsetY ignored on old architecture */ - internal fun dispatchShadowStateUpdate( + private fun dispatchShadowStateUpdate( width: Int, height: Int, offsetY: Int, From 226fcafd52bd546ab9ce8741bc6041fa51f527c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 13:47:38 +0100 Subject: [PATCH 10/45] Add prop --- android/src/main/java/com/swmansion/rnscreens/Screen.kt | 1 + .../java/com/swmansion/rnscreens/ScreenStackFragment.kt | 4 ++++ .../main/java/com/swmansion/rnscreens/ScreenViewManager.kt | 5 +++++ .../react/viewmanagers/RNSScreenManagerDelegate.java | 3 +++ .../react/viewmanagers/RNSScreenManagerInterface.java | 1 + apps/src/tests/Test3336.tsx | 1 + native-stack/README.md | 4 ++++ src/components/Screen.tsx | 5 +++++ src/fabric/ModalScreenNativeComponent.ts | 1 + src/fabric/ScreenNativeComponent.ts | 1 + src/types.tsx | 6 ++++++ 11 files changed, 32 insertions(+) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index c33885e051..59f60acd65 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -87,6 +87,7 @@ class Screen( var sheetInitialDetentIndex: Int = 0 var sheetClosesOnTouchOutside = true var sheetElevation: Float = 24F + var sheetOverflowsSystemBars = false /** * On Paper, when using form sheet presentation we want to delay enter transition in order diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 9d76e16c80..3aef77850d 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -6,6 +6,7 @@ import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -224,6 +225,9 @@ class ScreenStackFragment : attachShapeToScreen(screen) screen.elevation = screen.sheetElevation + Log.d("tomaboro", "overflows? ${screen.sheetOverflowsSystemBars}") + // TODO(@t0maboro) - add overflows logic + // Lifecycle of sheet delegate is tied to fragment. val sheetDelegate = requireSheetDelegate() sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt index e52831e4fe..fcfd8eb524 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -269,6 +269,11 @@ open class ScreenViewManager : view?.sheetElevation = value.toFloat() } + @ReactProp(name = "sheetOverflowsSystemBars") + override fun setSheetOverflowsSystemBars(view: Screen?, sheetOverflowsSystemBars: Boolean) { + view?.sheetOverflowsSystemBars = sheetOverflowsSystemBars + } + // mark: iOS-only // these props are not available on Android, however we must override their setters override fun setFullScreenSwipeEnabled( diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index c33cb6e318..ec22b119be 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -50,6 +50,9 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "sheetElevation": mViewManager.setSheetElevation(view, value == null ? 24 : ((Double) value).intValue()); break; + case "sheetOverflowsSystemBars": + mViewManager.setSheetOverflowsSystemBars(view, value == null ? false : (boolean) value); + break; case "customAnimationOnSwipe": mViewManager.setCustomAnimationOnSwipe(view, value == null ? false : (boolean) value); break; diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java index beb5ef140d..7dea341441 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java @@ -24,6 +24,7 @@ public interface RNSScreenManagerInterface { void setSheetExpandsWhenScrolledToEdge(T view, boolean value); void setSheetInitialDetent(T view, int value); void setSheetElevation(T view, int value); + void setSheetOverflowsSystemBars(T view, boolean value); void setCustomAnimationOnSwipe(T view, boolean value); void setFullScreenSwipeEnabled(T view, @Nullable String value); void setFullScreenSwipeShadowEnabled(T view, boolean value); diff --git a/apps/src/tests/Test3336.tsx b/apps/src/tests/Test3336.tsx index 7668284e97..3000d342aa 100644 --- a/apps/src/tests/Test3336.tsx +++ b/apps/src/tests/Test3336.tsx @@ -129,6 +129,7 @@ const formSheetBaseOptions: NativeStackNavigationOptions = { contentStyle: { backgroundColor: Colors.GreenLight100, }, + // TODO(@t0maboro) - add overflows prop here }; function PressableBase() { diff --git a/native-stack/README.md b/native-stack/README.md index e0832d726c..c07142c11f 100644 --- a/native-stack/README.md +++ b/native-stack/README.md @@ -321,6 +321,10 @@ corresponding legacy prop values for `sheetAllowedDetents` prop. Defaults to `none`, indicating that the dimming view should be always present. +#### `sheetOverflowsSystemBars` + +TODO(@t0maboro) - add docs + #### `stackAnimation` How the given screen should appear/disappear when pushed or popped at the top of the stack. Possible values: diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx index 1fb3a3745b..2c82e65301 100644 --- a/src/components/Screen.tsx +++ b/src/components/Screen.tsx @@ -98,6 +98,10 @@ export const InnerScreen = React.forwardRef( sheetExpandsWhenScrolledToEdge = true, sheetElevation = 24, sheetInitialDetentIndex = 0, + // TODO(@t0maboro) + // 1. add prop in react-navigation + // 2. change the default value to false + sheetOverflowsSystemBars = true, // Other screenId, stackPresentation, @@ -229,6 +233,7 @@ export const InnerScreen = React.forwardRef( sheetAllowedDetents={resolvedSheetAllowedDetents} sheetLargestUndimmedDetent={resolvedSheetLargestUndimmedDetent} sheetElevation={sheetElevation} + sheetOverflowsSystemBars={sheetOverflowsSystemBars} sheetGrabberVisible={sheetGrabberVisible} sheetCornerRadius={sheetCornerRadius} sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge} diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index c67e3251e5..fcf1abf5af 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -89,6 +89,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; + sheetOverflowsSystemBars?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index 6186b34b07..a977499c0c 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -91,6 +91,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; + sheetOverflowsSystemBars?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/types.tsx b/src/types.tsx index 4d71750f5d..27797d3165 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -472,6 +472,12 @@ export interface ScreenProps extends ViewProps { * Defaults to `0` - which represents first detent in the detents array. */ sheetInitialDetentIndex?: number | 'last'; + /** + * TODO(@t0maboro) - add docs + * + * @platform android + */ + sheetOverflowsSystemBars?: boolean; /** * How the screen should appear/disappear when pushed or popped at the top of the stack. * The following values are currently supported: From d0c30063ce672536e6def6dfb0def556abce5c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 14:15:35 +0100 Subject: [PATCH 11/45] Fix after rebase --- .../bottomsheet/BottomSheetTransitionCoordinator.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index bbd7338b52..24bbe9fab0 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -3,7 +3,6 @@ package com.swmansion.rnscreens.bottomsheet import android.view.View import android.view.ViewGroup import android.view.WindowInsets -import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.Screen class BottomSheetTransitionCoordinator { @@ -43,8 +42,6 @@ class BottomSheetTransitionCoordinator { // 3. We should check here whether insets has changed if (areInsetsApplied) return insets - val prevInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) @@ -74,12 +71,7 @@ class BottomSheetTransitionCoordinator { // TODO(@t0maboro): // 1. SheetDelegate has onApplyWindowInsets method - we should ensure that both are coordinated well - return WindowInsets - .Builder(insets) - .setInsets( - WindowInsetsCompat.Type.systemBars(), - android.graphics.Insets.of(prevInsets.left, 0, prevInsets.right, prevInsets.bottom), - ).build() + return insets } private fun triggerSheetEnterTransitionIfReady(screen: Screen) { From d1c9b1f73d6c66d71b343cd976087a9528496fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 14:30:15 +0100 Subject: [PATCH 12/45] Add prop support --- .../src/main/java/com/swmansion/rnscreens/Screen.kt | 2 +- .../com/swmansion/rnscreens/ScreenStackFragment.kt | 10 ++++------ .../swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 1 - .../com/swmansion/rnscreens/bottomsheet/SheetUtils.kt | 8 +++----- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 59f60acd65..f2c7600410 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -221,7 +221,7 @@ class Screen( // To ensure the BottomSheet correctly respects insets during its enter transition, // we delay the transition until both layout and insets have been applied. internal fun requestTriggeringPostponedEnterTransition() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !sheetOverflowsSystemBars) { shouldTriggerPostponedTransitionAfterLayout = true } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 3aef77850d..715d4c597a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -6,7 +6,6 @@ import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -225,9 +224,6 @@ class ScreenStackFragment : attachShapeToScreen(screen) screen.elevation = screen.sheetElevation - Log.d("tomaboro", "overflows? ${screen.sheetOverflowsSystemBars}") - // TODO(@t0maboro) - add overflows logic - // Lifecycle of sheet delegate is tied to fragment. val sheetDelegate = requireSheetDelegate() sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) @@ -236,8 +232,10 @@ class ScreenStackFragment : dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout) dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!) - sheetTransitionCoordinator = BottomSheetTransitionCoordinator() - sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(screen, sheetDelegate, coordinatorLayout) + if(!screen.sheetOverflowsSystemBars) { + sheetTransitionCoordinator = BottomSheetTransitionCoordinator() + sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(screen, sheetDelegate, coordinatorLayout) + } // Pre-layout the content for the sake of enter transition. diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 89222791e8..ab264efd58 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -6,7 +6,6 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context import android.os.Build -import android.util.Log import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt index d3697907b0..52d0a6a9b7 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt @@ -150,13 +150,11 @@ fun Screen.requiresEnterTransitionPostponing(): Boolean { // To ensure the BottomSheet height respects the top inset we delay starting the enter // transition until both layout and insets are fully applied. - // Fabric - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - return this.usesFormSheetPresentation() + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !this.sheetOverflowsSystemBars && this.usesFormSheetPresentation()) { + return true } - // Paper - if (!this.usesFormSheetPresentation()) { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED || !this.usesFormSheetPresentation()) { return false } // Assumes that formSheet uses content wrapper From 7c3bd301886c3d8fee89757c1346598ddee0599f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 14:30:37 +0100 Subject: [PATCH 13/45] Update defaults --- src/components/Screen.tsx | 4 ++-- src/fabric/ModalScreenNativeComponent.ts | 2 +- src/fabric/ScreenNativeComponent.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx index 2c82e65301..e84974f736 100644 --- a/src/components/Screen.tsx +++ b/src/components/Screen.tsx @@ -100,8 +100,8 @@ export const InnerScreen = React.forwardRef( sheetInitialDetentIndex = 0, // TODO(@t0maboro) // 1. add prop in react-navigation - // 2. change the default value to false - sheetOverflowsSystemBars = true, + // 2. change the default value to true + sheetOverflowsSystemBars = false, // Other screenId, stackPresentation, diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index fcf1abf5af..69439fafbc 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -89,7 +89,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetOverflowsSystemBars?: WithDefault; + sheetOverflowsSystemBars?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index a977499c0c..245531b8da 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -91,7 +91,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetOverflowsSystemBars?: WithDefault; + sheetOverflowsSystemBars?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; From 01492e4dbc99a3690e3ff19b447a7a08345c0450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 14:30:57 +0100 Subject: [PATCH 14/45] Update defaults --- .../facebook/react/viewmanagers/RNSScreenManagerDelegate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index ec22b119be..19b544290c 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -51,7 +51,7 @@ public void setProperty(T view, String propName, @Nullable Object value) { mViewManager.setSheetElevation(view, value == null ? 24 : ((Double) value).intValue()); break; case "sheetOverflowsSystemBars": - mViewManager.setSheetOverflowsSystemBars(view, value == null ? false : (boolean) value); + mViewManager.setSheetOverflowsSystemBars(view, value == null ? true : (boolean) value); break; case "customAnimationOnSwipe": mViewManager.setCustomAnimationOnSwipe(view, value == null ? false : (boolean) value); From 7ad6c48946f6717346241a01310f147d6ae5b726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 14:55:08 +0100 Subject: [PATCH 15/45] Make keyboard state readonly --- .../bottomsheet/BottomSheetTransitionCoordinator.kt | 13 +++++++------ .../rnscreens/bottomsheet/SheetDelegate.kt | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 24bbe9fab0..75421d0984 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -9,6 +9,8 @@ class BottomSheetTransitionCoordinator { private var isLayoutComplete = false private var areInsetsApplied = false + private var lastInsets: WindowInsets? = null + fun attachInsetsAndLayoutListenersToBottomSheet( screen: Screen, sheetDelegate: SheetDelegate, @@ -36,15 +38,14 @@ class BottomSheetTransitionCoordinator { sheetDelegate: SheetDelegate, coordinatorLayout: ViewGroup, ): WindowInsets { - // TODO(@t0maboro): - // 1. Without this line, FormSheet with TextInput is reconfiguring bottom sheet with state.collapsed for some reason - // 2. TextInput with medium/large detent is causing some reconfiguration and flicker on dismissing with swipe with keyboard open - // 3. We should check here whether insets has changed - if (areInsetsApplied) return insets + if (lastInsets == insets) { + return insets + } + lastInsets = insets // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. - sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!) + sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!, sheetDelegate.keyboardState) screen.container?.let { container -> // Needs to be highlighted that nothing changes at the container level. diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index ab264efd58..b992ff5f9c 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -33,7 +33,8 @@ class SheetDelegate( ) : LifecycleEventObserver, OnApplyWindowInsetsListener { private var isKeyboardVisible: Boolean = false - private var keyboardState: KeyboardState = KeyboardNotVisible + var keyboardState: KeyboardState = KeyboardNotVisible + private set private var isSheetAnimationInProgress: Boolean = false From 669e8dd14185defcca2efdb251e519b364252609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 14:57:58 +0100 Subject: [PATCH 16/45] Add comment --- .../rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 75421d0984..3cf225813a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -70,8 +70,8 @@ class BottomSheetTransitionCoordinator { areInsetsApplied = true triggerSheetEnterTransitionIfReady(screen) - // TODO(@t0maboro): - // 1. SheetDelegate has onApplyWindowInsets method - we should ensure that both are coordinated well + // Our goal is to execute the side effect of delaying the animation, + // therefore we pass the unmodified insets in every case. return insets } From 6fe336bbef470fe8fe485e3fd23b6bdaabaab9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 15:06:59 +0100 Subject: [PATCH 17/45] Separate logic for resolving container height --- .../rnscreens/bottomsheet/SheetDelegate.kt | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index b992ff5f9c..9843e54c5d 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -120,7 +120,7 @@ class SheetDelegate( keyboardState: KeyboardState = KeyboardNotVisible, selectedDetentIndex: Int = lastStableDetentIndex, ): BottomSheetBehavior { - val containerHeight = tryResolveContainerHeight() + val containerHeight = if (screen.sheetOverflowsSystemBars) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } @@ -263,7 +263,7 @@ class SheetDelegate( // Otherwise, it shifts the sheet as high as possible, even if it means part of its content // will remain hidden behind the keyboard. internal fun computeSheetOffsetYWithIMEPresent(keyboardHeight: Int): Int { - val containerHeight = tryResolveContainerHeight() + val containerHeight = if (screen.sheetOverflowsSystemBars) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } @@ -292,8 +292,8 @@ class SheetDelegate( val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime()) val prevSystemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - // TODO(@t0maboro): - // 1. lastTopInset needs to have some explanation why it's needed here + // We save the top inset (status bar height) to later subtract it from the window height + // during sheet size calculations. This ensures the sheet respects the safe area. lastTopInset = prevSystemBarsInsets.top if (isImeVisible) { @@ -335,37 +335,34 @@ class SheetDelegate( @BottomSheetBehavior.State state: Int, ) = state == BottomSheetBehavior.STATE_HIDDEN + /** + * This method tries to resolve the maximum height available for the sheet content, + * accounting for the system top inset. + */ + private fun tryResolveSafeAreaSpaceForSheet(): Int? = tryResolveContainerHeight()?.let { it - lastTopInset } + /** * This method might return slightly different values depending on code path, * but during testing I've found this effect negligible. For practical purposes * this is acceptable. */ private fun tryResolveContainerHeight(): Int? { - // TODO(@t0maboro): - // 1. lastTopInset needs to have some explanation why it's needed here - // 2. I shouldn't touch this method at all and subtract this value somewhere else - screen.container?.let { return it.height - lastTopInset } + screen.container?.let { return it.height } val context = screen.reactContext - // TODO(@t0maboro): - // 1. lastTopInset needs to have some explanation why it's needed here - // 2. I shouldn't touch this method at all and subtract this value somewhere else context .resources ?.displayMetrics ?.heightPixels - ?.let { return it - lastTopInset } + ?.let { return it } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // TODO(@t0maboro): - // 1. lastTopInset needs to have some explanation why it's needed here - // 2. I shouldn't touch this method at all and subtract this value somewhere else (context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager) ?.currentWindowMetrics ?.bounds ?.height() - ?.let { return it - lastTopInset } + ?.let { return it } } return null } From ad7e97573f6ce8422c0b9c9f77fae654027876aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 15:09:30 +0100 Subject: [PATCH 18/45] Remove paper annotation --- .../rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 3cf225813a..8832a699f5 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -59,9 +59,6 @@ class BottomSheetTransitionCoordinator { coordinatorLayout.layout(0, 0, container.width, container.height) } - // TODO(@t0maboro): - // 1. Check paper - // Although the layout of the screen container and CoordinatorLayout hasn't changed, // the BottomSheetBehavior has updated the maximum height. // We manually trigger the callback to notify that the bottom sheet layout has been applied. From e30d8c62284750c54fb2ea7c26415d8d31d993fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 15:10:32 +0100 Subject: [PATCH 19/45] Set proper default --- src/components/Screen.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx index e84974f736..09262f4c3a 100644 --- a/src/components/Screen.tsx +++ b/src/components/Screen.tsx @@ -98,10 +98,7 @@ export const InnerScreen = React.forwardRef( sheetExpandsWhenScrolledToEdge = true, sheetElevation = 24, sheetInitialDetentIndex = 0, - // TODO(@t0maboro) - // 1. add prop in react-navigation - // 2. change the default value to true - sheetOverflowsSystemBars = false, + sheetOverflowsSystemBars = true, // Other screenId, stackPresentation, From f863611fbd5c7c75b5efb914261a2b2e214a3d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 15:24:05 +0100 Subject: [PATCH 20/45] Add docs --- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 9 +++++++++ native-stack/README.md | 4 ---- src/types.tsx | 7 ++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index 40397636ec..cda20e4914 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -233,6 +233,15 @@ corresponding legacy prop values for `sheetAllowedDetents` prop. Defaults to `none`, indicating that the dimming view should be always present. +### `sheetOverflowsSystemBars` (Android only) + +Whether the sheet content should be rendered behind the Status Bar + +When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the system bars. +When set to `false`, the sheet's layout will be constrained by the system insets. + +Defaults to `true`. + ### `stackAnimation` Allows for the customization of how the given screen should appear/disappear when pushed or popped at the top of the stack. The following values are currently supported: diff --git a/native-stack/README.md b/native-stack/README.md index c07142c11f..e0832d726c 100644 --- a/native-stack/README.md +++ b/native-stack/README.md @@ -321,10 +321,6 @@ corresponding legacy prop values for `sheetAllowedDetents` prop. Defaults to `none`, indicating that the dimming view should be always present. -#### `sheetOverflowsSystemBars` - -TODO(@t0maboro) - add docs - #### `stackAnimation` How the given screen should appear/disappear when pushed or popped at the top of the stack. Possible values: diff --git a/src/types.tsx b/src/types.tsx index 27797d3165..2a6372081c 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -473,7 +473,12 @@ export interface ScreenProps extends ViewProps { */ sheetInitialDetentIndex?: number | 'last'; /** - * TODO(@t0maboro) - add docs + * Whether the sheet content should be rendered behind the Status Bar + * + * When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the system bars. + * When set to `false`, the sheet's layout will be constrained by the system insets. + * + * Defaults to `true`. * * @platform android */ From e506b802fbcb92d6a6f6289877def461f5414d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 15:25:11 +0100 Subject: [PATCH 21/45] Update test --- apps/src/tests/Test3336.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/tests/Test3336.tsx b/apps/src/tests/Test3336.tsx index 3000d342aa..652c2cbf82 100644 --- a/apps/src/tests/Test3336.tsx +++ b/apps/src/tests/Test3336.tsx @@ -129,7 +129,7 @@ const formSheetBaseOptions: NativeStackNavigationOptions = { contentStyle: { backgroundColor: Colors.GreenLight100, }, - // TODO(@t0maboro) - add overflows prop here + // TODO(@t0maboro) - add `sheetOverflowsSystemBars` prop here when possible }; function PressableBase() { From 2cbbf01ff8cde4499f159c520bd7a98da594a408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 17:12:40 +0100 Subject: [PATCH 22/45] Support for API levels <30 --- .../java/com/swmansion/rnscreens/Screen.kt | 9 ++- .../BottomSheetTransitionCoordinator.kt | 80 +++++++++++++++++-- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index f2c7600410..03adb7f2e7 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -191,7 +191,8 @@ class Screen( val width = r - l val height = b - t - dispatchShadowStateUpdate(width, height, t) + // TODO(@t0maboro) this needs to be passed dynamically + // dispatchShadowStateUpdate(width, height - 168, t) // FormSheet has no header in current model. notifyHeaderHeightChange(t) @@ -204,7 +205,8 @@ class Screen( } if (coordinatorLayoutDidChange) { - dispatchShadowStateUpdate(width, height, top) + // TODO(@t0maboro) this needs to be passed dynamically + // dispatchShadowStateUpdate(width, height - 168, top) } footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height) @@ -488,7 +490,8 @@ class Screen( // There is no need to update shadow state for transient sheet states - // we are unsure of the exact sheet position anyway. if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && isStable) { - updateScreenSizeFabric(width, height, top) + // TODO(@t0maboro) this needs to be passed dynamically + // updateScreenSizeFabric(width, height - 168, top) } } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 8832a699f5..a0f0d5c0f1 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -1,8 +1,13 @@ package com.swmansion.rnscreens.bottomsheet +import android.os.Build +import android.util.Log import android.view.View import android.view.ViewGroup import android.view.WindowInsets +import androidx.annotation.RequiresApi +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.Screen class BottomSheetTransitionCoordinator { @@ -10,6 +15,7 @@ class BottomSheetTransitionCoordinator { private var areInsetsApplied = false private var lastInsets: WindowInsets? = null + private var lastInsetsCompat: WindowInsetsCompat? = null fun attachInsetsAndLayoutListenersToBottomSheet( screen: Screen, @@ -17,10 +23,28 @@ class BottomSheetTransitionCoordinator { coordinatorLayout: ViewGroup, ) { screen.container?.apply { - setOnApplyWindowInsetsListener { view, insets -> - onScreenContainerInsetsApplied(view, insets, screen, sheetDelegate, coordinatorLayout) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setOnApplyWindowInsetsListener { view, insets -> + onScreenContainerInsetsApplied( + insets, + screen, + sheetDelegate, + coordinatorLayout + ) + } + } else { + // TODO(@t0maboro) - not a big fan of it, but we already have a listener on screen level + ViewCompat.setOnApplyWindowInsetsListener(screen.contentWrapper!!) { _, insetsCompat -> + onScreenContainerInsetsAppliedLegacy( + insetsCompat, + screen, + sheetDelegate, + coordinatorLayout + ) + } } - addOnLayoutChangeListener { view, l, t, r, b, ol, ot, or, ob -> + + addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> onScreenContainerLayoutChanged(screen) } } @@ -31,18 +55,59 @@ class BottomSheetTransitionCoordinator { triggerSheetEnterTransitionIfReady(screen) } + @RequiresApi(Build.VERSION_CODES.R) private fun onScreenContainerInsetsApplied( - view: View, insets: WindowInsets, screen: Screen, sheetDelegate: SheetDelegate, coordinatorLayout: ViewGroup, ): WindowInsets { + // TODO(@t0maboro) - there's some flicker on reconfiguration with slide-out keyboard animation if (lastInsets == insets) { return insets } lastInsets = insets + val bottomInset = insets.getInsets(WindowInsets.Type.systemBars()).bottom + handleInsetsApplication( + bottomInset, + screen, + sheetDelegate, + coordinatorLayout + ) + + return insets + } + + private fun onScreenContainerInsetsAppliedLegacy( + insetsCompat: WindowInsetsCompat, + screen: Screen, + sheetDelegate: SheetDelegate, + coordinatorLayout: ViewGroup, + ): WindowInsetsCompat { + if (lastInsetsCompat == insetsCompat) { + return insetsCompat + } + lastInsetsCompat = insetsCompat + + val bottomInset = insetsCompat.getInsets(WindowInsetsCompat.Type.systemBars()).bottom + handleInsetsApplication( + bottomInset, + screen, + sheetDelegate, + coordinatorLayout + ) + + return insetsCompat + } + + private fun handleInsetsApplication( + bottomInset: Int, + screen: Screen, + sheetDelegate: SheetDelegate, + coordinatorLayout: ViewGroup + ) { + Log.d("tomaboro", "handleInsetsApplication with ${sheetDelegate.keyboardState}") // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!, sheetDelegate.keyboardState) @@ -59,6 +124,9 @@ class BottomSheetTransitionCoordinator { coordinatorLayout.layout(0, 0, container.width, container.height) } + // TODO(@t0maboro) - this paddding is needed but requires some updates with contentWrapper + // screen.setPadding(0, 0, 0, bottomInset) + // Although the layout of the screen container and CoordinatorLayout hasn't changed, // the BottomSheetBehavior has updated the maximum height. // We manually trigger the callback to notify that the bottom sheet layout has been applied. @@ -66,10 +134,6 @@ class BottomSheetTransitionCoordinator { areInsetsApplied = true triggerSheetEnterTransitionIfReady(screen) - - // Our goal is to execute the side effect of delaying the animation, - // therefore we pass the unmodified insets in every case. - return insets } private fun triggerSheetEnterTransitionIfReady(screen: Screen) { From 9ad3841aa6c000e5cd10f49e546e3c5d1aa89a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 17:21:52 +0100 Subject: [PATCH 23/45] Add todo --- .../BottomSheetTransitionCoordinator.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index a0f0d5c0f1..117a3988fe 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -29,7 +29,7 @@ class BottomSheetTransitionCoordinator { insets, screen, sheetDelegate, - coordinatorLayout + coordinatorLayout, ) } } else { @@ -39,7 +39,7 @@ class BottomSheetTransitionCoordinator { insetsCompat, screen, sheetDelegate, - coordinatorLayout + coordinatorLayout, ) } } @@ -73,7 +73,7 @@ class BottomSheetTransitionCoordinator { bottomInset, screen, sheetDelegate, - coordinatorLayout + coordinatorLayout, ) return insets @@ -95,7 +95,7 @@ class BottomSheetTransitionCoordinator { bottomInset, screen, sheetDelegate, - coordinatorLayout + coordinatorLayout, ) return insetsCompat @@ -105,11 +105,15 @@ class BottomSheetTransitionCoordinator { bottomInset: Int, screen: Screen, sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup + coordinatorLayout: ViewGroup, ) { Log.d("tomaboro", "handleInsetsApplication with ${sheetDelegate.keyboardState}") // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. + + // TODO(@t0maboro) there's some content flicker here while dismissing the keyboard with swipe gesture + // 1. maybe we should just update maxHeight/expandedOffsetFromTop values, as the no. of detents shouldn't change here + // 2. maybe we shouldn't do anything on exiting transition sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!, sheetDelegate.keyboardState) screen.container?.let { container -> From 57883733202f1a6423ed091a3d02bfdf2996ba4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 21:10:46 +0100 Subject: [PATCH 24/45] Aggregate listeners --- .../java/com/swmansion/rnscreens/Screen.kt | 9 ++--- .../rnscreens/ScreenStackFragment.kt | 16 +++++++-- .../BottomSheetTransitionCoordinator.kt | 15 ++++---- .../BottomSheetWindowInsetListenerChain.kt | 35 +++++++++++++++++++ 4 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 03adb7f2e7..f2c7600410 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -191,8 +191,7 @@ class Screen( val width = r - l val height = b - t - // TODO(@t0maboro) this needs to be passed dynamically - // dispatchShadowStateUpdate(width, height - 168, t) + dispatchShadowStateUpdate(width, height, t) // FormSheet has no header in current model. notifyHeaderHeightChange(t) @@ -205,8 +204,7 @@ class Screen( } if (coordinatorLayoutDidChange) { - // TODO(@t0maboro) this needs to be passed dynamically - // dispatchShadowStateUpdate(width, height - 168, top) + dispatchShadowStateUpdate(width, height, top) } footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height) @@ -490,8 +488,7 @@ class Screen( // There is no need to update shadow state for transient sheet states - // we are unsure of the exact sheet position anyway. if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && isStable) { - // TODO(@t0maboro) this needs to be passed dynamically - // updateScreenSizeFabric(width, height - 168, top) + updateScreenSizeFabric(width, height, top) } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 715d4c597a..2a560b8140 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -28,6 +28,7 @@ import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel import com.swmansion.rnscreens.bottomsheet.BottomSheetTransitionCoordinator +import com.swmansion.rnscreens.bottomsheet.BottomSheetWindowInsetListenerChain import com.swmansion.rnscreens.bottomsheet.DimmingViewManager import com.swmansion.rnscreens.bottomsheet.SheetDelegate import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation @@ -76,6 +77,8 @@ class ScreenStackFragment : internal var sheetDelegate: SheetDelegate? = null + internal var bottomSheetWindowInsetListenerChain: BottomSheetWindowInsetListenerChain? = null + @SuppressLint("ValidFragment") constructor(screenView: Screen) : super(screenView) @@ -234,7 +237,7 @@ class ScreenStackFragment : if(!screen.sheetOverflowsSystemBars) { sheetTransitionCoordinator = BottomSheetTransitionCoordinator() - sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(screen, sheetDelegate, coordinatorLayout) + sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(this, screen, sheetDelegate, coordinatorLayout) } // Pre-layout the content for the sake of enter transition. @@ -247,10 +250,12 @@ class ScreenStackFragment : coordinatorLayout.layout(0, 0, container.width, container.height) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - ViewCompat.setOnApplyWindowInsetsListener(screen) { _, windowInsets -> + val bottomSheetWindowInsetListenerChain = requireBottomSheetWindowInsetsListenerChain() + bottomSheetWindowInsetListenerChain.addListener { _, windowInsets -> sheetDelegate.handleKeyboardInsetsProgress(windowInsets) windowInsets } + ViewCompat.setOnApplyWindowInsetsListener(screen, bottomSheetWindowInsetListenerChain) } val insetsAnimationCallback = @@ -479,4 +484,11 @@ class ScreenStackFragment : } return sheetDelegate!! } + + internal fun requireBottomSheetWindowInsetsListenerChain(): BottomSheetWindowInsetListenerChain { + if (bottomSheetWindowInsetListenerChain == null) { + bottomSheetWindowInsetListenerChain = BottomSheetWindowInsetListenerChain() + } + return bottomSheetWindowInsetListenerChain!! + } } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 117a3988fe..9522b01cac 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -6,9 +6,9 @@ import android.view.View import android.view.ViewGroup import android.view.WindowInsets import androidx.annotation.RequiresApi -import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.Screen +import com.swmansion.rnscreens.ScreenStackFragment class BottomSheetTransitionCoordinator { private var isLayoutComplete = false @@ -18,6 +18,7 @@ class BottomSheetTransitionCoordinator { private var lastInsetsCompat: WindowInsetsCompat? = null fun attachInsetsAndLayoutListenersToBottomSheet( + screenStackFragment: ScreenStackFragment, screen: Screen, sheetDelegate: SheetDelegate, coordinatorLayout: ViewGroup, @@ -33,10 +34,10 @@ class BottomSheetTransitionCoordinator { ) } } else { - // TODO(@t0maboro) - not a big fan of it, but we already have a listener on screen level - ViewCompat.setOnApplyWindowInsetsListener(screen.contentWrapper!!) { _, insetsCompat -> + val bottomSheetWindowInsetListenerChain = screenStackFragment.requireBottomSheetWindowInsetsListenerChain() + bottomSheetWindowInsetListenerChain.addListener { _, windowInsets -> onScreenContainerInsetsAppliedLegacy( - insetsCompat, + windowInsets, screen, sheetDelegate, coordinatorLayout, @@ -62,7 +63,6 @@ class BottomSheetTransitionCoordinator { sheetDelegate: SheetDelegate, coordinatorLayout: ViewGroup, ): WindowInsets { - // TODO(@t0maboro) - there's some flicker on reconfiguration with slide-out keyboard animation if (lastInsets == insets) { return insets } @@ -107,7 +107,6 @@ class BottomSheetTransitionCoordinator { sheetDelegate: SheetDelegate, coordinatorLayout: ViewGroup, ) { - Log.d("tomaboro", "handleInsetsApplication with ${sheetDelegate.keyboardState}") // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. @@ -128,7 +127,9 @@ class BottomSheetTransitionCoordinator { coordinatorLayout.layout(0, 0, container.width, container.height) } - // TODO(@t0maboro) - this paddding is needed but requires some updates with contentWrapper + // TODO(@t0maboro) + // 1. padding doesn't work well with fitToContents + // 2. verify on paper // screen.setPadding(0, 0, 0, bottomInset) // Although the layout of the screen container and CoordinatorLayout hasn't changed, diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt new file mode 100644 index 0000000000..5b2a6073a2 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt @@ -0,0 +1,35 @@ +package com.swmansion.rnscreens.bottomsheet + +import android.view.View +import androidx.core.view.OnApplyWindowInsetsListener +import androidx.core.view.WindowInsetsCompat + +/** + * Aggregates and sequentially invokes many instances of OnApplyWindowInsetsListener + * + * In Android, only a single ViewCompat.setOnApplyWindowInsetsListener can be set on a view, + * which leads to listeners overwriting each other. This class solves the listener conflict + * by allowing different components to a common chain. As we do not consume or modify insets, the + * order is not important and the chain may work. + * + * In our case it's crucial, because we need to react on insets for both: + * - avoiding bottom/top insets by BottomSheet + * - keyboard handling + */ +class BottomSheetWindowInsetListenerChain : OnApplyWindowInsetsListener { + private val listeners = mutableListOf() + + fun addListener(listener: OnApplyWindowInsetsListener) { + listeners.add(listener) + } + + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + var currentInsets = insets + + for (listener in listeners) { + currentInsets = listener.onApplyWindowInsets(v, currentInsets) + } + + return currentInsets + } +} \ No newline at end of file From 96ad4931d8c4f8443b9a9e62b86caf159a7a021d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Tue, 18 Nov 2025 21:22:06 +0100 Subject: [PATCH 25/45] Rename --- .../bottomsheet/BottomSheetTransitionCoordinator.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 9522b01cac..b4e35ab002 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -1,7 +1,6 @@ package com.swmansion.rnscreens.bottomsheet import android.os.Build -import android.util.Log import android.view.View import android.view.ViewGroup import android.view.WindowInsets @@ -69,7 +68,7 @@ class BottomSheetTransitionCoordinator { lastInsets = insets val bottomInset = insets.getInsets(WindowInsets.Type.systemBars()).bottom - handleInsetsApplication( + handleInsets( bottomInset, screen, sheetDelegate, @@ -91,7 +90,7 @@ class BottomSheetTransitionCoordinator { lastInsetsCompat = insetsCompat val bottomInset = insetsCompat.getInsets(WindowInsetsCompat.Type.systemBars()).bottom - handleInsetsApplication( + handleInsets( bottomInset, screen, sheetDelegate, @@ -101,7 +100,7 @@ class BottomSheetTransitionCoordinator { return insetsCompat } - private fun handleInsetsApplication( + private fun handleInsets( bottomInset: Int, screen: Screen, sheetDelegate: SheetDelegate, From 680708441a4513e9acd11fa439609937df0a8776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 09:56:35 +0100 Subject: [PATCH 26/45] Drop bottom inset support from prop --- .../src/main/java/com/swmansion/rnscreens/Screen.kt | 4 ++-- .../com/swmansion/rnscreens/ScreenStackFragment.kt | 2 +- .../java/com/swmansion/rnscreens/ScreenViewManager.kt | 6 +++--- .../bottomsheet/BottomSheetTransitionCoordinator.kt | 10 ---------- .../swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 4 ++-- .../com/swmansion/rnscreens/bottomsheet/SheetUtils.kt | 2 +- .../react/viewmanagers/RNSScreenManagerDelegate.java | 4 ++-- .../react/viewmanagers/RNSScreenManagerInterface.java | 2 +- apps/src/tests/Test3336.tsx | 2 +- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 6 +++--- src/components/Screen.tsx | 4 ++-- src/fabric/ModalScreenNativeComponent.ts | 2 +- src/fabric/ScreenNativeComponent.ts | 2 +- src/types.tsx | 6 +++--- 14 files changed, 23 insertions(+), 33 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index f2c7600410..8c40ed799f 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -87,7 +87,7 @@ class Screen( var sheetInitialDetentIndex: Int = 0 var sheetClosesOnTouchOutside = true var sheetElevation: Float = 24F - var sheetOverflowsSystemBars = false + var sheetShouldOverflowStatusBar = false /** * On Paper, when using form sheet presentation we want to delay enter transition in order @@ -221,7 +221,7 @@ class Screen( // To ensure the BottomSheet correctly respects insets during its enter transition, // we delay the transition until both layout and insets have been applied. internal fun requestTriggeringPostponedEnterTransition() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !sheetOverflowsSystemBars) { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !sheetShouldOverflowStatusBar) { shouldTriggerPostponedTransitionAfterLayout = true } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 2a560b8140..851557af0b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -235,7 +235,7 @@ class ScreenStackFragment : dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout) dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!) - if(!screen.sheetOverflowsSystemBars) { + if(!screen.sheetShouldOverflowStatusBar) { sheetTransitionCoordinator = BottomSheetTransitionCoordinator() sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(this, screen, sheetDelegate, coordinatorLayout) } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt index fcfd8eb524..1df795fcbc 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -269,9 +269,9 @@ open class ScreenViewManager : view?.sheetElevation = value.toFloat() } - @ReactProp(name = "sheetOverflowsSystemBars") - override fun setSheetOverflowsSystemBars(view: Screen?, sheetOverflowsSystemBars: Boolean) { - view?.sheetOverflowsSystemBars = sheetOverflowsSystemBars + @ReactProp(name = "sheetShouldOverflowStatusBar") + override fun setSheetShouldOverflowStatusBar(view: Screen?, sheetShouldOverflowStatusBar: Boolean) { + view?.sheetShouldOverflowStatusBar = sheetShouldOverflowStatusBar } // mark: iOS-only diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index b4e35ab002..274a8aea96 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -67,9 +67,7 @@ class BottomSheetTransitionCoordinator { } lastInsets = insets - val bottomInset = insets.getInsets(WindowInsets.Type.systemBars()).bottom handleInsets( - bottomInset, screen, sheetDelegate, coordinatorLayout, @@ -89,9 +87,7 @@ class BottomSheetTransitionCoordinator { } lastInsetsCompat = insetsCompat - val bottomInset = insetsCompat.getInsets(WindowInsetsCompat.Type.systemBars()).bottom handleInsets( - bottomInset, screen, sheetDelegate, coordinatorLayout, @@ -101,7 +97,6 @@ class BottomSheetTransitionCoordinator { } private fun handleInsets( - bottomInset: Int, screen: Screen, sheetDelegate: SheetDelegate, coordinatorLayout: ViewGroup, @@ -126,11 +121,6 @@ class BottomSheetTransitionCoordinator { coordinatorLayout.layout(0, 0, container.width, container.height) } - // TODO(@t0maboro) - // 1. padding doesn't work well with fitToContents - // 2. verify on paper - // screen.setPadding(0, 0, 0, bottomInset) - // Although the layout of the screen container and CoordinatorLayout hasn't changed, // the BottomSheetBehavior has updated the maximum height. // We manually trigger the callback to notify that the bottom sheet layout has been applied. diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 9843e54c5d..619221d69e 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -120,7 +120,7 @@ class SheetDelegate( keyboardState: KeyboardState = KeyboardNotVisible, selectedDetentIndex: Int = lastStableDetentIndex, ): BottomSheetBehavior { - val containerHeight = if (screen.sheetOverflowsSystemBars) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() + val containerHeight = if (screen.sheetShouldOverflowStatusBar) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } @@ -263,7 +263,7 @@ class SheetDelegate( // Otherwise, it shifts the sheet as high as possible, even if it means part of its content // will remain hidden behind the keyboard. internal fun computeSheetOffsetYWithIMEPresent(keyboardHeight: Int): Int { - val containerHeight = if (screen.sheetOverflowsSystemBars) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() + val containerHeight = if (screen.sheetShouldOverflowStatusBar) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt index 52d0a6a9b7..9486edfe5d 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt @@ -150,7 +150,7 @@ fun Screen.requiresEnterTransitionPostponing(): Boolean { // To ensure the BottomSheet height respects the top inset we delay starting the enter // transition until both layout and insets are fully applied. - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !this.sheetOverflowsSystemBars && this.usesFormSheetPresentation()) { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !this.sheetShouldOverflowStatusBar && this.usesFormSheetPresentation()) { return true } diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index 19b544290c..fb208aec4a 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -50,8 +50,8 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "sheetElevation": mViewManager.setSheetElevation(view, value == null ? 24 : ((Double) value).intValue()); break; - case "sheetOverflowsSystemBars": - mViewManager.setSheetOverflowsSystemBars(view, value == null ? true : (boolean) value); + case "sheetShouldOverflowStatusBar": + mViewManager.setSheetShouldOverflowStatusBar(view, value == null ? true : (boolean) value); break; case "customAnimationOnSwipe": mViewManager.setCustomAnimationOnSwipe(view, value == null ? false : (boolean) value); diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java index 7dea341441..1f56f54332 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java @@ -24,7 +24,7 @@ public interface RNSScreenManagerInterface { void setSheetExpandsWhenScrolledToEdge(T view, boolean value); void setSheetInitialDetent(T view, int value); void setSheetElevation(T view, int value); - void setSheetOverflowsSystemBars(T view, boolean value); + void setSheetShouldOverflowStatusBar(T view, boolean value); void setCustomAnimationOnSwipe(T view, boolean value); void setFullScreenSwipeEnabled(T view, @Nullable String value); void setFullScreenSwipeShadowEnabled(T view, boolean value); diff --git a/apps/src/tests/Test3336.tsx b/apps/src/tests/Test3336.tsx index 652c2cbf82..4c048842a8 100644 --- a/apps/src/tests/Test3336.tsx +++ b/apps/src/tests/Test3336.tsx @@ -129,7 +129,7 @@ const formSheetBaseOptions: NativeStackNavigationOptions = { contentStyle: { backgroundColor: Colors.GreenLight100, }, - // TODO(@t0maboro) - add `sheetOverflowsSystemBars` prop here when possible + // TODO(@t0maboro) - add `sheetShouldOverflowStatusBar` prop here when possible }; function PressableBase() { diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index cda20e4914..76da6b6605 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -233,12 +233,12 @@ corresponding legacy prop values for `sheetAllowedDetents` prop. Defaults to `none`, indicating that the dimming view should be always present. -### `sheetOverflowsSystemBars` (Android only) +### `sheetShouldOverflowStatusBar` (Android only) Whether the sheet content should be rendered behind the Status Bar -When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the system bars. -When set to `false`, the sheet's layout will be constrained by the system insets. +When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the status bar. +When set to `false`, the sheet's layout will be constrained by the system insets from the top. Defaults to `true`. diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx index 09262f4c3a..929f2a3009 100644 --- a/src/components/Screen.tsx +++ b/src/components/Screen.tsx @@ -98,7 +98,7 @@ export const InnerScreen = React.forwardRef( sheetExpandsWhenScrolledToEdge = true, sheetElevation = 24, sheetInitialDetentIndex = 0, - sheetOverflowsSystemBars = true, + sheetShouldOverflowStatusBar = true, // Other screenId, stackPresentation, @@ -230,7 +230,7 @@ export const InnerScreen = React.forwardRef( sheetAllowedDetents={resolvedSheetAllowedDetents} sheetLargestUndimmedDetent={resolvedSheetLargestUndimmedDetent} sheetElevation={sheetElevation} - sheetOverflowsSystemBars={sheetOverflowsSystemBars} + sheetShouldOverflowStatusBar={sheetShouldOverflowStatusBar} sheetGrabberVisible={sheetGrabberVisible} sheetCornerRadius={sheetCornerRadius} sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge} diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index 69439fafbc..307247db9f 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -89,7 +89,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetOverflowsSystemBars?: WithDefault; + sheetShouldOverflowStatusBar?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index 245531b8da..88522509ee 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -91,7 +91,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetOverflowsSystemBars?: WithDefault; + sheetShouldOverflowStatusBar?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/types.tsx b/src/types.tsx index 2a6372081c..cce14066bc 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -475,14 +475,14 @@ export interface ScreenProps extends ViewProps { /** * Whether the sheet content should be rendered behind the Status Bar * - * When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the system bars. - * When set to `false`, the sheet's layout will be constrained by the system insets. + * When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the status bar. + * When set to `false`, the sheet's layout will be constrained by the system insets from the top. * * Defaults to `true`. * * @platform android */ - sheetOverflowsSystemBars?: boolean; + sheetShouldOverflowStatusBar?: boolean; /** * How the screen should appear/disappear when pushed or popped at the top of the stack. * The following values are currently supported: From 125f19db2106943336df71c9b0121bcd517f61ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 10:15:07 +0100 Subject: [PATCH 27/45] Fix keyboard flickering --- .../bottomsheet/BottomSheetBehaviorExt.kt | 29 +++++++++++----- .../BottomSheetTransitionCoordinator.kt | 2 +- .../rnscreens/bottomsheet/SheetDelegate.kt | 33 +++++++++++++++++++ 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetBehaviorExt.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetBehaviorExt.kt index dff322526e..3fa6541245 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetBehaviorExt.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetBehaviorExt.kt @@ -3,6 +3,19 @@ package com.swmansion.rnscreens.bottomsheet import android.view.View import com.google.android.material.bottomsheet.BottomSheetBehavior +internal fun BottomSheetBehavior.updateMetrics( + maxAllowedHeight: Int? = null, + expandedOffsetFromTop: Int? = null, +): BottomSheetBehavior { + maxAllowedHeight?.let { + this.maxHeight = maxAllowedHeight + } + expandedOffsetFromTop?.let { + this.expandedOffset = expandedOffsetFromTop + } + return this +} + internal fun BottomSheetBehavior.useSingleDetent( maxAllowedHeight: Int? = null, forceExpandedState: Boolean = true, @@ -13,7 +26,7 @@ internal fun BottomSheetBehavior.useSingleDetent( this.state = BottomSheetBehavior.STATE_EXPANDED } maxAllowedHeight?.let { - maxHeight = maxAllowedHeight + this.maxHeight = maxAllowedHeight } return this } @@ -23,11 +36,11 @@ internal fun BottomSheetBehavior.useTwoDetents( firstHeight: Int? = null, maxAllowedHeight: Int? = null, ): BottomSheetBehavior { - skipCollapsed = false - isFitToContents = true + this.skipCollapsed = false + this.isFitToContents = true state?.let { this.state = state } - firstHeight?.let { peekHeight = firstHeight } - maxAllowedHeight?.let { maxHeight = maxAllowedHeight } + firstHeight?.let { this.peekHeight = firstHeight } + maxAllowedHeight?.let { this.maxHeight = maxAllowedHeight } return this } @@ -38,12 +51,12 @@ internal fun BottomSheetBehavior.useThreeDetents( halfExpandedRatio: Float? = null, expandedOffsetFromTop: Int? = null, ): BottomSheetBehavior { - skipCollapsed = false - isFitToContents = false + this.skipCollapsed = false + this.isFitToContents = false state?.let { this.state = state } firstHeight?.let { this.peekHeight = firstHeight } halfExpandedRatio?.let { this.halfExpandedRatio = halfExpandedRatio } expandedOffsetFromTop?.let { this.expandedOffset = expandedOffsetFromTop } - maxAllowedHeight?.let { maxHeight = maxAllowedHeight } + maxAllowedHeight?.let { this.maxHeight = maxAllowedHeight } return this } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 274a8aea96..65a8540d5c 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -107,7 +107,7 @@ class BottomSheetTransitionCoordinator { // TODO(@t0maboro) there's some content flicker here while dismissing the keyboard with swipe gesture // 1. maybe we should just update maxHeight/expandedOffsetFromTop values, as the no. of detents shouldn't change here // 2. maybe we shouldn't do anything on exiting transition - sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!, sheetDelegate.keyboardState) + sheetDelegate.updateBottomSheetMetrics(screen.sheetBehavior!!) screen.container?.let { container -> // Needs to be highlighted that nothing changes at the container level. diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 619221d69e..dad54e7f7a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -115,6 +115,39 @@ class SheetDelegate( } } + internal fun updateBottomSheetMetrics(behavior: BottomSheetBehavior) { + val containerHeight = if (screen.sheetShouldOverflowStatusBar) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() + check(containerHeight != null) { + "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" + } + + val maxAllowedHeight = when(screen.isSheetFitToContents()) { + true -> screen.contentWrapper?.let { contentWrapper -> + contentWrapper.height.takeIf { + // subtree might not be laid out, e.g. after fragment reattachment + // and view recreation, however since it is retained by + // react-native it has its height cached. We want to use it. + // Otherwise we would have to trigger RN layout manually. + contentWrapper.isLaidOutOrHasCachedLayout() + } + } + false -> (screen.sheetDetents.last() * containerHeight).toInt() + } + + // For 3 detents, we need to add the top inset back here because we are calculating the offset + // from the absolute top of the view, but our calculated max height (containerHeight) + // has been reduced by this inset. + val expandedOffsetFromTop = when (screen.sheetDetents.count()) { + 3 -> ((1 - screen.sheetDetents[2]) * containerHeight).toInt() + lastTopInset + else -> null + } + + behavior.apply { + updateMetrics(maxAllowedHeight, expandedOffsetFromTop) + } + } + + internal fun configureBottomSheetBehaviour( behavior: BottomSheetBehavior, keyboardState: KeyboardState = KeyboardNotVisible, From a20c1b7fbb3de1cbab4d1dbaf9e49477c1e086aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 10:15:46 +0100 Subject: [PATCH 28/45] Remove todo --- .../rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 65a8540d5c..81bc0cba24 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -104,9 +104,6 @@ class BottomSheetTransitionCoordinator { // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. - // TODO(@t0maboro) there's some content flicker here while dismissing the keyboard with swipe gesture - // 1. maybe we should just update maxHeight/expandedOffsetFromTop values, as the no. of detents shouldn't change here - // 2. maybe we shouldn't do anything on exiting transition sheetDelegate.updateBottomSheetMetrics(screen.sheetBehavior!!) screen.container?.let { container -> From 6cd5c35d358df63afeae486ac8db92a4847f8298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 10:58:36 +0100 Subject: [PATCH 29/45] Cleanup --- .../rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt | 1 - .../bottomsheet/BottomSheetWindowInsetListenerChain.kt | 2 +- .../java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 81bc0cba24..745b08050a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -103,7 +103,6 @@ class BottomSheetTransitionCoordinator { ) { // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. - sheetDelegate.updateBottomSheetMetrics(screen.sheetBehavior!!) screen.container?.let { container -> diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt index 5b2a6073a2..ec41f896ae 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt @@ -32,4 +32,4 @@ class BottomSheetWindowInsetListenerChain : OnApplyWindowInsetsListener { return currentInsets } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index dad54e7f7a..b0bbd4cb94 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -33,8 +33,7 @@ class SheetDelegate( ) : LifecycleEventObserver, OnApplyWindowInsetsListener { private var isKeyboardVisible: Boolean = false - var keyboardState: KeyboardState = KeyboardNotVisible - private set + private var keyboardState: KeyboardState = KeyboardNotVisible private var isSheetAnimationInProgress: Boolean = false From b2fee50ba66fc3110eac235acf49ad64f3233ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 12:46:16 +0100 Subject: [PATCH 30/45] Fix after rebase --- .../rnscreens/bottomsheet/SheetDelegate.kt | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index b0bbd4cb94..fd0e376fd4 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -120,33 +120,35 @@ class SheetDelegate( "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } - val maxAllowedHeight = when(screen.isSheetFitToContents()) { - true -> screen.contentWrapper?.let { contentWrapper -> - contentWrapper.height.takeIf { - // subtree might not be laid out, e.g. after fragment reattachment - // and view recreation, however since it is retained by - // react-native it has its height cached. We want to use it. - // Otherwise we would have to trigger RN layout manually. - contentWrapper.isLaidOutOrHasCachedLayout() - } + val maxAllowedHeight = + when (screen.isSheetFitToContents()) { + true -> + screen.contentWrapper?.let { contentWrapper -> + contentWrapper.height.takeIf { + // subtree might not be laid out, e.g. after fragment reattachment + // and view recreation, however since it is retained by + // react-native it has its height cached. We want to use it. + // Otherwise we would have to trigger RN layout manually. + contentWrapper.isLaidOutOrHasCachedLayout() + } + } + false -> (screen.sheetDetents.last() * containerHeight).toInt() } - false -> (screen.sheetDetents.last() * containerHeight).toInt() - } // For 3 detents, we need to add the top inset back here because we are calculating the offset // from the absolute top of the view, but our calculated max height (containerHeight) // has been reduced by this inset. - val expandedOffsetFromTop = when (screen.sheetDetents.count()) { - 3 -> ((1 - screen.sheetDetents[2]) * containerHeight).toInt() + lastTopInset - else -> null - } + val expandedOffsetFromTop = + when (screen.sheetDetents.count()) { + 3 -> ((1 - screen.sheetDetents[2]) * containerHeight).toInt() + lastTopInset + else -> null + } behavior.apply { updateMetrics(maxAllowedHeight, expandedOffsetFromTop) } } - internal fun configureBottomSheetBehaviour( behavior: BottomSheetBehavior, keyboardState: KeyboardState = KeyboardNotVisible, From 31e76136031e8f37aebb81d80d22f790545fc995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 12:54:32 +0100 Subject: [PATCH 31/45] Update docs --- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 7 ++++--- src/types.tsx | 11 ++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index 76da6b6605..68c2308bf7 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -235,10 +235,11 @@ Defaults to `none`, indicating that the dimming view should be always present. ### `sheetShouldOverflowStatusBar` (Android only) -Whether the sheet content should be rendered behind the Status Bar +Whether the sheet content should be rendered behind the Status Bar. -When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the status bar. -When set to `false`, the sheet's layout will be constrained by the system insets from the top. +When set to `true`, the sheet will extend to the physical edges of the screen, allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents will be measured relative to the full screen height. + +When set to `false`, the sheet's layout will be constrained by the status bar insets from the top and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). This means that sheetAllowedDetents will result in different sheet heights depending on this prop. Defaults to `true`. diff --git a/src/types.tsx b/src/types.tsx index cce14066bc..f4a4e4b368 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -473,10 +473,15 @@ export interface ScreenProps extends ViewProps { */ sheetInitialDetentIndex?: number | 'last'; /** - * Whether the sheet content should be rendered behind the Status Bar + * Whether the sheet content should be rendered behind the Status Bar. * - * When set to `true`, the sheet will extend to the physical edges of the screen, allowing the content to be visible behind the status bar. - * When set to `false`, the sheet's layout will be constrained by the system insets from the top. + * When set to `true`, the sheet will extend to the physical edges of the screen, + * allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents + * will be measured relative to the full screen height. + * + * When set to `false`, the sheet's layout will be constrained by the status bar insets from the top + * and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). + * This means that sheetAllowedDetents will result in different sheet heights depending on this prop. * * Defaults to `true`. * From 2df618ba0fbe0a90690139218b1322b2b286f7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 12:57:19 +0100 Subject: [PATCH 32/45] Linter --- .../main/java/com/swmansion/rnscreens/ScreenStackFragment.kt | 2 +- .../main/java/com/swmansion/rnscreens/ScreenViewManager.kt | 5 ++++- .../bottomsheet/BottomSheetWindowInsetListenerChain.kt | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 851557af0b..83bddf88d2 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -235,7 +235,7 @@ class ScreenStackFragment : dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout) dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!) - if(!screen.sheetShouldOverflowStatusBar) { + if (!screen.sheetShouldOverflowStatusBar) { sheetTransitionCoordinator = BottomSheetTransitionCoordinator() sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(this, screen, sheetDelegate, coordinatorLayout) } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt index 1df795fcbc..938799cd4c 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -270,7 +270,10 @@ open class ScreenViewManager : } @ReactProp(name = "sheetShouldOverflowStatusBar") - override fun setSheetShouldOverflowStatusBar(view: Screen?, sheetShouldOverflowStatusBar: Boolean) { + override fun setSheetShouldOverflowStatusBar( + view: Screen?, + sheetShouldOverflowStatusBar: Boolean, + ) { view?.sheetShouldOverflowStatusBar = sheetShouldOverflowStatusBar } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt index ec41f896ae..7cb8e7a7e8 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt @@ -23,7 +23,10 @@ class BottomSheetWindowInsetListenerChain : OnApplyWindowInsetsListener { listeners.add(listener) } - override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + override fun onApplyWindowInsets( + v: View, + insets: WindowInsetsCompat, + ): WindowInsetsCompat { var currentInsets = insets for (listener in listeners) { From a46236fce94872038dc4e36fbef0d3ca65bca8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Wed, 19 Nov 2025 14:41:51 +0100 Subject: [PATCH 33/45] Correct docs --- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 2 +- src/types.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index 68c2308bf7..5632314e90 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -237,7 +237,7 @@ Defaults to `none`, indicating that the dimming view should be always present. Whether the sheet content should be rendered behind the Status Bar. -When set to `true`, the sheet will extend to the physical edges of the screen, allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents will be measured relative to the full screen height. +When set to `true`, the sheet will extend to the physical edges of the stack, allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents will be measured relative to the stack height. When set to `false`, the sheet's layout will be constrained by the status bar insets from the top and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). This means that sheetAllowedDetents will result in different sheet heights depending on this prop. diff --git a/src/types.tsx b/src/types.tsx index f4a4e4b368..3e2d8bdbdd 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -475,9 +475,9 @@ export interface ScreenProps extends ViewProps { /** * Whether the sheet content should be rendered behind the Status Bar. * - * When set to `true`, the sheet will extend to the physical edges of the screen, + * When set to `true`, the sheet will extend to the physical edges of the stack, * allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents - * will be measured relative to the full screen height. + * will be measured relative to the full stack height. * * When set to `false`, the sheet's layout will be constrained by the status bar insets from the top * and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). From 13010276ea39cee14fec94db9047dc5b2d7f5aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 15:55:14 +0100 Subject: [PATCH 34/45] Deduplicate insets --- .../BottomSheetTransitionCoordinator.kt | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 745b08050a..96d8f4c7d9 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -3,8 +3,6 @@ package com.swmansion.rnscreens.bottomsheet import android.os.Build import android.view.View import android.view.ViewGroup -import android.view.WindowInsets -import androidx.annotation.RequiresApi import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.Screen import com.swmansion.rnscreens.ScreenStackFragment @@ -13,7 +11,6 @@ class BottomSheetTransitionCoordinator { private var isLayoutComplete = false private var areInsetsApplied = false - private var lastInsets: WindowInsets? = null private var lastInsetsCompat: WindowInsetsCompat? = null fun attachInsetsAndLayoutListenersToBottomSheet( @@ -24,18 +21,20 @@ class BottomSheetTransitionCoordinator { ) { screen.container?.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - setOnApplyWindowInsetsListener { view, insets -> - onScreenContainerInsetsApplied( - insets, + setOnApplyWindowInsetsListener { _, insets -> + val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, this) + onScreenContainerInsetsAppliedCompat( + insetsCompat, screen, sheetDelegate, coordinatorLayout, ) + insets } } else { val bottomSheetWindowInsetListenerChain = screenStackFragment.requireBottomSheetWindowInsetsListenerChain() bottomSheetWindowInsetListenerChain.addListener { _, windowInsets -> - onScreenContainerInsetsAppliedLegacy( + onScreenContainerInsetsAppliedCompat( windowInsets, screen, sheetDelegate, @@ -55,28 +54,7 @@ class BottomSheetTransitionCoordinator { triggerSheetEnterTransitionIfReady(screen) } - @RequiresApi(Build.VERSION_CODES.R) - private fun onScreenContainerInsetsApplied( - insets: WindowInsets, - screen: Screen, - sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup, - ): WindowInsets { - if (lastInsets == insets) { - return insets - } - lastInsets = insets - - handleInsets( - screen, - sheetDelegate, - coordinatorLayout, - ) - - return insets - } - - private fun onScreenContainerInsetsAppliedLegacy( + private fun onScreenContainerInsetsAppliedCompat( insetsCompat: WindowInsetsCompat, screen: Screen, sheetDelegate: SheetDelegate, From caa610017b8ce264b54c7b9b78960a7bb4ec60ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 16:13:10 +0100 Subject: [PATCH 35/45] Move some logic to ScreenStackFragment --- .../rnscreens/ScreenStackFragment.kt | 80 ++++++++++++++++- .../BottomSheetTransitionCoordinator.kt | 90 +------------------ 2 files changed, 81 insertions(+), 89 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 83bddf88d2..366696e5ba 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -79,6 +79,8 @@ class ScreenStackFragment : internal var bottomSheetWindowInsetListenerChain: BottomSheetWindowInsetListenerChain? = null + private var lastInsetsCompat: WindowInsetsCompat? = null + @SuppressLint("ValidFragment") constructor(screenView: Screen) : super(screenView) @@ -237,7 +239,9 @@ class ScreenStackFragment : if (!screen.sheetShouldOverflowStatusBar) { sheetTransitionCoordinator = BottomSheetTransitionCoordinator() - sheetTransitionCoordinator.attachInsetsAndLayoutListenersToBottomSheet(this, screen, sheetDelegate, coordinatorLayout) + attachInsetsAndLayoutListenersToBottomSheet( + sheetTransitionCoordinator, + ) } // Pre-layout the content for the sake of enter transition. @@ -470,6 +474,80 @@ class ScreenStackFragment : screenStack.dismiss(this) } + // Mark: Avoiding top inset by BottomSheet + + private fun attachInsetsAndLayoutListenersToBottomSheet(sheetTransitionCoordinator: BottomSheetTransitionCoordinator) { + val sheetDelegate = requireSheetDelegate() + + screen.container?.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setOnApplyWindowInsetsListener { _, insets -> + val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, this) + handleInsetsUpdateAndNotifyTransition( + insetsCompat, + screen, + sheetDelegate, + coordinatorLayout, + sheetTransitionCoordinator, + ) + insets + } + } else { + val bottomSheetWindowInsetListenerChain = requireBottomSheetWindowInsetsListenerChain() + bottomSheetWindowInsetListenerChain.addListener { _, windowInsets -> + handleInsetsUpdateAndNotifyTransition( + windowInsets, + screen, + sheetDelegate, + coordinatorLayout, + sheetTransitionCoordinator, + ) + windowInsets + } + } + } + + screen.container?.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + sheetTransitionCoordinator.onScreenContainerLayoutChanged(screen) + } + } + + private fun handleInsetsUpdateAndNotifyTransition( + insetsCompat: WindowInsetsCompat, + screen: Screen, + sheetDelegate: SheetDelegate, + coordinatorLayout: ViewGroup, + sheetTransitionCoordinator: BottomSheetTransitionCoordinator, + ) { + if (lastInsetsCompat == insetsCompat) { + return + } + lastInsetsCompat = insetsCompat + + // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. + // When insets are available, we can factor them in to update the maximum height accordingly. + sheetDelegate.updateBottomSheetMetrics(screen.sheetBehavior!!) + + screen.container?.let { container -> + // Needs to be highlighted that nothing changes at the container level. + // However, calling additional measure will trigger BottomSheetBehavior's `onMeasureChild` logic. + // This method ensures that the bottom sheet respects the maxHeight we update in `configureBottomSheetBehavior`. + coordinatorLayout.forceLayout() + coordinatorLayout.measure( + View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY), + ) + coordinatorLayout.layout(0, 0, container.width, container.height) + } + + // Although the layout of the screen container and CoordinatorLayout hasn't changed, + // the BottomSheetBehavior has updated the maximum height. + // We manually trigger the callback to notify that the bottom sheet layout has been applied. + screen.onBottomSheetBehaviorDidLayout(true) + + sheetTransitionCoordinator.onScreenContainerInsetsApplied(screen) + } + private fun requireDimmingDelegate(forceCreation: Boolean = false): DimmingViewManager { if (dimmingDelegate == null || forceCreation) { dimmingDelegate?.invalidate(screen.sheetBehavior) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 96d8f4c7d9..6edeb677d4 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -1,105 +1,19 @@ package com.swmansion.rnscreens.bottomsheet -import android.os.Build -import android.view.View -import android.view.ViewGroup -import androidx.core.view.WindowInsetsCompat import com.swmansion.rnscreens.Screen -import com.swmansion.rnscreens.ScreenStackFragment class BottomSheetTransitionCoordinator { private var isLayoutComplete = false private var areInsetsApplied = false - private var lastInsetsCompat: WindowInsetsCompat? = null - - fun attachInsetsAndLayoutListenersToBottomSheet( - screenStackFragment: ScreenStackFragment, - screen: Screen, - sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup, - ) { - screen.container?.apply { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - setOnApplyWindowInsetsListener { _, insets -> - val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, this) - onScreenContainerInsetsAppliedCompat( - insetsCompat, - screen, - sheetDelegate, - coordinatorLayout, - ) - insets - } - } else { - val bottomSheetWindowInsetListenerChain = screenStackFragment.requireBottomSheetWindowInsetsListenerChain() - bottomSheetWindowInsetListenerChain.addListener { _, windowInsets -> - onScreenContainerInsetsAppliedCompat( - windowInsets, - screen, - sheetDelegate, - coordinatorLayout, - ) - } - } - - addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - onScreenContainerLayoutChanged(screen) - } - } - } - - private fun onScreenContainerLayoutChanged(screen: Screen) { + internal fun onScreenContainerLayoutChanged(screen: Screen) { isLayoutComplete = true triggerSheetEnterTransitionIfReady(screen) } - private fun onScreenContainerInsetsAppliedCompat( - insetsCompat: WindowInsetsCompat, - screen: Screen, - sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup, - ): WindowInsetsCompat { - if (lastInsetsCompat == insetsCompat) { - return insetsCompat - } - lastInsetsCompat = insetsCompat - - handleInsets( - screen, - sheetDelegate, - coordinatorLayout, - ) - - return insetsCompat - } - - private fun handleInsets( + internal fun onScreenContainerInsetsApplied( screen: Screen, - sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup, ) { - // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. - // When insets are available, we can factor them in to update the maximum height accordingly. - sheetDelegate.updateBottomSheetMetrics(screen.sheetBehavior!!) - - screen.container?.let { container -> - // Needs to be highlighted that nothing changes at the container level. - // However, calling additional measure will trigger BottomSheetBehavior's `onMeasureChild` logic. - // This method ensures that the bottom sheet respects the maxHeight we update in `configureBottomSheetBehavior`. - coordinatorLayout.forceLayout() - coordinatorLayout.measure( - View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY), - ) - coordinatorLayout.layout(0, 0, container.width, container.height) - } - - // Although the layout of the screen container and CoordinatorLayout hasn't changed, - // the BottomSheetBehavior has updated the maximum height. - // We manually trigger the callback to notify that the bottom sheet layout has been applied. - screen.onBottomSheetBehaviorDidLayout(true) - areInsetsApplied = true triggerSheetEnterTransitionIfReady(screen) } From cad91e85244339d58d394c64bb19f7bff6a6ac07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 16:15:51 +0100 Subject: [PATCH 36/45] Remove extra args --- .../rnscreens/ScreenStackFragment.kt | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 366696e5ba..a35197a9f5 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -477,18 +477,12 @@ class ScreenStackFragment : // Mark: Avoiding top inset by BottomSheet private fun attachInsetsAndLayoutListenersToBottomSheet(sheetTransitionCoordinator: BottomSheetTransitionCoordinator) { - val sheetDelegate = requireSheetDelegate() - screen.container?.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { setOnApplyWindowInsetsListener { _, insets -> val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, this) handleInsetsUpdateAndNotifyTransition( insetsCompat, - screen, - sheetDelegate, - coordinatorLayout, - sheetTransitionCoordinator, ) insets } @@ -497,10 +491,6 @@ class ScreenStackFragment : bottomSheetWindowInsetListenerChain.addListener { _, windowInsets -> handleInsetsUpdateAndNotifyTransition( windowInsets, - screen, - sheetDelegate, - coordinatorLayout, - sheetTransitionCoordinator, ) windowInsets } @@ -512,13 +502,7 @@ class ScreenStackFragment : } } - private fun handleInsetsUpdateAndNotifyTransition( - insetsCompat: WindowInsetsCompat, - screen: Screen, - sheetDelegate: SheetDelegate, - coordinatorLayout: ViewGroup, - sheetTransitionCoordinator: BottomSheetTransitionCoordinator, - ) { + private fun handleInsetsUpdateAndNotifyTransition(insetsCompat: WindowInsetsCompat) { if (lastInsetsCompat == insetsCompat) { return } @@ -526,6 +510,7 @@ class ScreenStackFragment : // Reconfigure BottomSheetBehavior with the same state and updated maxHeight. // When insets are available, we can factor them in to update the maximum height accordingly. + val sheetDelegate = requireSheetDelegate() sheetDelegate.updateBottomSheetMetrics(screen.sheetBehavior!!) screen.container?.let { container -> From f72dcb4ac0a29b84bc4f9aa485b452f0f920d4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 16:16:53 +0100 Subject: [PATCH 37/45] Remove rolling insets --- .../bottomsheet/BottomSheetWindowInsetListenerChain.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt index 7cb8e7a7e8..1bbc03aaec 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetWindowInsetListenerChain.kt @@ -27,12 +27,10 @@ class BottomSheetWindowInsetListenerChain : OnApplyWindowInsetsListener { v: View, insets: WindowInsetsCompat, ): WindowInsetsCompat { - var currentInsets = insets - for (listener in listeners) { - currentInsets = listener.onApplyWindowInsets(v, currentInsets) + listener.onApplyWindowInsets(v, insets) } - return currentInsets + return insets } } From ca1b3086e116a75eb638c66fc579a8532cc5ee8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 16:21:31 +0100 Subject: [PATCH 38/45] Formatting --- .../rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt index 6edeb677d4..8404d63683 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetTransitionCoordinator.kt @@ -11,9 +11,7 @@ class BottomSheetTransitionCoordinator { triggerSheetEnterTransitionIfReady(screen) } - internal fun onScreenContainerInsetsApplied( - screen: Screen, - ) { + internal fun onScreenContainerInsetsApplied(screen: Screen) { areInsetsApplied = true triggerSheetEnterTransitionIfReady(screen) } From 81a227a56b248f3f2f3fe81cdddb3d82c655f442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 16:48:39 +0100 Subject: [PATCH 39/45] Add display cutout --- .../com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index fd0e376fd4..0f2314f8db 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -325,10 +325,12 @@ class SheetDelegate( val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime()) val prevSystemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val prevDisplayCutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout()) - // We save the top inset (status bar height) to later subtract it from the window height - // during sheet size calculations. This ensures the sheet respects the safe area. - lastTopInset = prevSystemBarsInsets.top + // We save the top inset (status bar height + display cutout) to later + // subtract it from the window height during sheet size calculations. + // This ensures the sheet respects the safe area. + lastTopInset = prevSystemBarsInsets.top + prevDisplayCutoutInsets.top if (isImeVisible) { isKeyboardVisible = true From e6cfffafed18c94d4345a2ee318e8a1c807427c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 16:54:29 +0100 Subject: [PATCH 40/45] Rename --- .../src/main/java/com/swmansion/rnscreens/Screen.kt | 4 ++-- .../com/swmansion/rnscreens/ScreenStackFragment.kt | 2 +- .../com/swmansion/rnscreens/ScreenViewManager.kt | 8 ++++---- .../swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 6 +++--- .../swmansion/rnscreens/bottomsheet/SheetUtils.kt | 2 +- .../react/viewmanagers/RNSScreenManagerDelegate.java | 4 ++-- .../viewmanagers/RNSScreenManagerInterface.java | 2 +- apps/src/tests/Test3336.tsx | 2 +- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 10 +++++----- src/components/Screen.tsx | 4 ++-- src/fabric/ModalScreenNativeComponent.ts | 2 +- src/fabric/ScreenNativeComponent.ts | 2 +- src/types.tsx | 12 ++++++------ 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 8c40ed799f..46357a0971 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -87,7 +87,7 @@ class Screen( var sheetInitialDetentIndex: Int = 0 var sheetClosesOnTouchOutside = true var sheetElevation: Float = 24F - var sheetShouldOverflowStatusBar = false + var sheetShouldOverflowTopInset = false /** * On Paper, when using form sheet presentation we want to delay enter transition in order @@ -221,7 +221,7 @@ class Screen( // To ensure the BottomSheet correctly respects insets during its enter transition, // we delay the transition until both layout and insets have been applied. internal fun requestTriggeringPostponedEnterTransition() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !sheetShouldOverflowStatusBar) { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !sheetShouldOverflowTopInset) { shouldTriggerPostponedTransitionAfterLayout = true } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index a35197a9f5..2d9080a879 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -237,7 +237,7 @@ class ScreenStackFragment : dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout) dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!) - if (!screen.sheetShouldOverflowStatusBar) { + if (!screen.sheetShouldOverflowTopInset) { sheetTransitionCoordinator = BottomSheetTransitionCoordinator() attachInsetsAndLayoutListenersToBottomSheet( sheetTransitionCoordinator, diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt index 938799cd4c..269162412a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -269,12 +269,12 @@ open class ScreenViewManager : view?.sheetElevation = value.toFloat() } - @ReactProp(name = "sheetShouldOverflowStatusBar") - override fun setSheetShouldOverflowStatusBar( + @ReactProp(name = "sheetShouldOverflowTopInset") + override fun setSheetShouldOverflowTopInset( view: Screen?, - sheetShouldOverflowStatusBar: Boolean, + sheetShouldOverflowTopInset: Boolean, ) { - view?.sheetShouldOverflowStatusBar = sheetShouldOverflowStatusBar + view?.sheetShouldOverflowTopInset = sheetShouldOverflowTopInset } // mark: iOS-only diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 0f2314f8db..44a9a9cecf 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -115,7 +115,7 @@ class SheetDelegate( } internal fun updateBottomSheetMetrics(behavior: BottomSheetBehavior) { - val containerHeight = if (screen.sheetShouldOverflowStatusBar) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() + val containerHeight = if (screen.sheetShouldOverflowTopInset) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } @@ -154,7 +154,7 @@ class SheetDelegate( keyboardState: KeyboardState = KeyboardNotVisible, selectedDetentIndex: Int = lastStableDetentIndex, ): BottomSheetBehavior { - val containerHeight = if (screen.sheetShouldOverflowStatusBar) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() + val containerHeight = if (screen.sheetShouldOverflowTopInset) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } @@ -297,7 +297,7 @@ class SheetDelegate( // Otherwise, it shifts the sheet as high as possible, even if it means part of its content // will remain hidden behind the keyboard. internal fun computeSheetOffsetYWithIMEPresent(keyboardHeight: Int): Int { - val containerHeight = if (screen.sheetShouldOverflowStatusBar) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() + val containerHeight = if (screen.sheetShouldOverflowTopInset) tryResolveContainerHeight() else tryResolveSafeAreaSpaceForSheet() check(containerHeight != null) { "[RNScreens] Failed to find window height during bottom sheet behaviour configuration" } diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt index 9486edfe5d..26e5056ece 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt @@ -150,7 +150,7 @@ fun Screen.requiresEnterTransitionPostponing(): Boolean { // To ensure the BottomSheet height respects the top inset we delay starting the enter // transition until both layout and insets are fully applied. - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !this.sheetShouldOverflowStatusBar && this.usesFormSheetPresentation()) { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !this.sheetShouldOverflowTopInset && this.usesFormSheetPresentation()) { return true } diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index fb208aec4a..c39d9af86d 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -50,8 +50,8 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "sheetElevation": mViewManager.setSheetElevation(view, value == null ? 24 : ((Double) value).intValue()); break; - case "sheetShouldOverflowStatusBar": - mViewManager.setSheetShouldOverflowStatusBar(view, value == null ? true : (boolean) value); + case "sheetShouldOverflowTopInset": + mViewManager.setSheetShouldOverflowTopInset(view, value == null ? true : (boolean) value); break; case "customAnimationOnSwipe": mViewManager.setCustomAnimationOnSwipe(view, value == null ? false : (boolean) value); diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java index 1f56f54332..28245c546b 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java @@ -24,7 +24,7 @@ public interface RNSScreenManagerInterface { void setSheetExpandsWhenScrolledToEdge(T view, boolean value); void setSheetInitialDetent(T view, int value); void setSheetElevation(T view, int value); - void setSheetShouldOverflowStatusBar(T view, boolean value); + void setSheetShouldOverflowTopInset(T view, boolean value); void setCustomAnimationOnSwipe(T view, boolean value); void setFullScreenSwipeEnabled(T view, @Nullable String value); void setFullScreenSwipeShadowEnabled(T view, boolean value); diff --git a/apps/src/tests/Test3336.tsx b/apps/src/tests/Test3336.tsx index 4c048842a8..1c6049653b 100644 --- a/apps/src/tests/Test3336.tsx +++ b/apps/src/tests/Test3336.tsx @@ -129,7 +129,7 @@ const formSheetBaseOptions: NativeStackNavigationOptions = { contentStyle: { backgroundColor: Colors.GreenLight100, }, - // TODO(@t0maboro) - add `sheetShouldOverflowStatusBar` prop here when possible + // TODO(@t0maboro) - add `sheetShouldOverflowTopInset` prop here when possible }; function PressableBase() { diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index 5632314e90..9eaf19ee77 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -233,15 +233,15 @@ corresponding legacy prop values for `sheetAllowedDetents` prop. Defaults to `none`, indicating that the dimming view should be always present. -### `sheetShouldOverflowStatusBar` (Android only) +### `sheetShouldOverflowTopInset` (Android only) -Whether the sheet content should be rendered behind the Status Bar. +Whether the sheet content should be rendered behind the Status Bar or display cutouts. -When set to `true`, the sheet will extend to the physical edges of the stack, allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents will be measured relative to the stack height. +When set to `true`, the sheet will extend to the physical edges of the stack, allowing content to be visible behind the status bar or display cutouts. Detent ratios in sheetAllowedDetents will be measured relative to the full stack height. -When set to `false`, the sheet's layout will be constrained by the status bar insets from the top and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). This means that sheetAllowedDetents will result in different sheet heights depending on this prop. +When set to `false`, the sheet's layout will be constrained by the inset from the top and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). This means that sheetAllowedDetents will result in different sheet heights depending on this prop. -Defaults to `true`. +Defaults to `false`. ### `stackAnimation` diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx index 929f2a3009..f6da11b52b 100644 --- a/src/components/Screen.tsx +++ b/src/components/Screen.tsx @@ -98,7 +98,7 @@ export const InnerScreen = React.forwardRef( sheetExpandsWhenScrolledToEdge = true, sheetElevation = 24, sheetInitialDetentIndex = 0, - sheetShouldOverflowStatusBar = true, + sheetShouldOverflowTopInset = false, // Other screenId, stackPresentation, @@ -230,7 +230,7 @@ export const InnerScreen = React.forwardRef( sheetAllowedDetents={resolvedSheetAllowedDetents} sheetLargestUndimmedDetent={resolvedSheetLargestUndimmedDetent} sheetElevation={sheetElevation} - sheetShouldOverflowStatusBar={sheetShouldOverflowStatusBar} + sheetShouldOverflowTopInset={sheetShouldOverflowTopInset} sheetGrabberVisible={sheetGrabberVisible} sheetCornerRadius={sheetCornerRadius} sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge} diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index 307247db9f..8505a3ff95 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -89,7 +89,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetShouldOverflowStatusBar?: WithDefault; + sheetShouldOverflowTopInset?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index 88522509ee..3f3364589f 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -91,7 +91,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetShouldOverflowStatusBar?: WithDefault; + sheetShouldOverflowTopInset?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/types.tsx b/src/types.tsx index 3e2d8bdbdd..ea2dde4f05 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -473,21 +473,21 @@ export interface ScreenProps extends ViewProps { */ sheetInitialDetentIndex?: number | 'last'; /** - * Whether the sheet content should be rendered behind the Status Bar. + * Whether the sheet content should be rendered behind the Status Bar or display cutouts. * * When set to `true`, the sheet will extend to the physical edges of the stack, - * allowing content to be visible behind the status bar. Detent ratios in sheetAllowedDetents - * will be measured relative to the full stack height. + * allowing content to be visible behind the status bar or display cutouts. + * Detent ratios in sheetAllowedDetents will be measured relative to the full stack height. * - * When set to `false`, the sheet's layout will be constrained by the status bar insets from the top + * When set to `false`, the sheet's layout will be constrained by the inset from the top * and the detent ratios will then be measured relative to the adjusted height (excluding the top inset). * This means that sheetAllowedDetents will result in different sheet heights depending on this prop. * - * Defaults to `true`. + * Defaults to `false`. * * @platform android */ - sheetShouldOverflowStatusBar?: boolean; + sheetShouldOverflowTopInset?: boolean; /** * How the screen should appear/disappear when pushed or popped at the top of the stack. * The following values are currently supported: From 2ee4fb91b7831ef38c1c099ed64ebb8211327e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 20 Nov 2025 17:07:32 +0100 Subject: [PATCH 41/45] Fix default value --- .../facebook/react/viewmanagers/RNSScreenManagerDelegate.java | 2 +- src/fabric/ModalScreenNativeComponent.ts | 2 +- src/fabric/ScreenNativeComponent.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index c39d9af86d..f8481a2f46 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -51,7 +51,7 @@ public void setProperty(T view, String propName, @Nullable Object value) { mViewManager.setSheetElevation(view, value == null ? 24 : ((Double) value).intValue()); break; case "sheetShouldOverflowTopInset": - mViewManager.setSheetShouldOverflowTopInset(view, value == null ? true : (boolean) value); + mViewManager.setSheetShouldOverflowTopInset(view, value == null ? false : (boolean) value); break; case "customAnimationOnSwipe": mViewManager.setCustomAnimationOnSwipe(view, value == null ? false : (boolean) value); diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index 8505a3ff95..c6284a8abf 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -89,7 +89,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetShouldOverflowTopInset?: WithDefault; + sheetShouldOverflowTopInset?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index 3f3364589f..c173026b50 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -91,7 +91,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; sheetInitialDetent?: WithDefault; sheetElevation?: WithDefault; - sheetShouldOverflowTopInset?: WithDefault; + sheetShouldOverflowTopInset?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: WithDefault; fullScreenSwipeShadowEnabled?: WithDefault; From 3ca99f500b427a7eb818ba50896ef01e661c2ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Fri, 21 Nov 2025 09:46:41 +0100 Subject: [PATCH 42/45] Take max --- .../java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 4 ++-- src/types.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 44a9a9cecf..46e429aea6 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -327,10 +327,10 @@ class SheetDelegate( val prevSystemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val prevDisplayCutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout()) - // We save the top inset (status bar height + display cutout) to later + // We save the top inset (status bar height or display cutout) to later // subtract it from the window height during sheet size calculations. // This ensures the sheet respects the safe area. - lastTopInset = prevSystemBarsInsets.top + prevDisplayCutoutInsets.top + lastTopInset = maxOf(prevSystemBarsInsets.top, prevDisplayCutoutInsets.top) if (isImeVisible) { isKeyboardVisible = true diff --git a/src/types.tsx b/src/types.tsx index ea2dde4f05..0454dc7d90 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -476,7 +476,7 @@ export interface ScreenProps extends ViewProps { * Whether the sheet content should be rendered behind the Status Bar or display cutouts. * * When set to `true`, the sheet will extend to the physical edges of the stack, - * allowing content to be visible behind the status bar or display cutouts. + * allowing content to be visible behind the status bar or display cutouts. * Detent ratios in sheetAllowedDetents will be measured relative to the full stack height. * * When set to `false`, the sheet's layout will be constrained by the inset from the top From bcc52406e9c6be72c4073dc0295b763001cce4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Fri, 21 Nov 2025 09:49:36 +0100 Subject: [PATCH 43/45] Rename --- .../swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 46e429aea6..2a9d8016bd 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -324,13 +324,13 @@ class SheetDelegate( ): WindowInsetsCompat { val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime()) - val prevSystemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - val prevDisplayCutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout()) + val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val displayCutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout()) // We save the top inset (status bar height or display cutout) to later // subtract it from the window height during sheet size calculations. // This ensures the sheet respects the safe area. - lastTopInset = maxOf(prevSystemBarsInsets.top, prevDisplayCutoutInsets.top) + lastTopInset = maxOf(systemBarsInsets.top, displayCutoutInsets.top) if (isImeVisible) { isKeyboardVisible = true @@ -351,7 +351,7 @@ class SheetDelegate( isKeyboardVisible = false } - val newBottomInset = if (!isImeVisible) prevSystemBarsInsets.bottom else 0 + val newBottomInset = if (!isImeVisible) systemBarsInsets.bottom else 0 // Note: We do not manipulate the top inset manually. Therefore, if SafeAreaView has top insets enabled, // we must retain the top inset even if the formSheet does not currently overflow into the status bar. @@ -363,7 +363,7 @@ class SheetDelegate( .Builder(insets) .setInsets( WindowInsetsCompat.Type.systemBars(), - Insets.of(prevSystemBarsInsets.left, prevSystemBarsInsets.top, prevSystemBarsInsets.right, newBottomInset), + Insets.of(systemBarsInsets.left, systemBarsInsets.top, systemBarsInsets.right, newBottomInset), ).build() } From 1cbdabc8c3cfbc9db3ca1f37fc96f8a6a6af8b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Fri, 21 Nov 2025 12:34:41 +0100 Subject: [PATCH 44/45] Fixes after rebase --- .../com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 2a9d8016bd..6bd6dc7e57 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -132,15 +132,15 @@ class SheetDelegate( contentWrapper.isLaidOutOrHasCachedLayout() } } - false -> (screen.sheetDetents.last() * containerHeight).toInt() + false -> (screen.sheetDetents.highest() * containerHeight).toInt() } // For 3 detents, we need to add the top inset back here because we are calculating the offset // from the absolute top of the view, but our calculated max height (containerHeight) // has been reduced by this inset. val expandedOffsetFromTop = - when (screen.sheetDetents.count()) { - 3 -> ((1 - screen.sheetDetents[2]) * containerHeight).toInt() + lastTopInset + when (screen.sheetDetents.count) { + 3 -> screen.sheetDetents.expandedOffsetFromTop(containerHeight, lastTopInset) else -> null } From f0547ce71d304329ce218f52d6f718f09680900a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Boro=C5=84?= Date: Thu, 27 Nov 2025 16:54:38 +0100 Subject: [PATCH 45/45] Update android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt Co-authored-by: Kacper Kafara --- .../java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt index 6bd6dc7e57..9f94007343 100644 --- a/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +++ b/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt @@ -144,9 +144,7 @@ class SheetDelegate( else -> null } - behavior.apply { - updateMetrics(maxAllowedHeight, expandedOffsetFromTop) - } + behavior.updateMetrics(maxAllowedHeight, expandedOffsetFromTop) } internal fun configureBottomSheetBehaviour(