From 708253e44527f15b00447b46b37763a89fecf809 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Thu, 4 Sep 2025 12:52:56 +0200 Subject: [PATCH 1/6] to_revert: turn off isTopInsetEnabled behaviour --- android/src/main/java/com/swmansion/rnscreens/Screen.kt | 2 ++ .../java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt | 3 ++- 2 files changed, 4 insertions(+), 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 2b7dcff55f..0e8675415b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -34,6 +34,7 @@ import com.swmansion.rnscreens.events.SheetDetentChangedEvent import com.swmansion.rnscreens.ext.asScreenStackFragment import com.swmansion.rnscreens.ext.parentAsViewGroup import com.swmansion.rnscreens.gamma.common.FragmentProviding +import com.swmansion.rnscreens.utils.RNSLog @SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated. class Screen( @@ -184,6 +185,7 @@ class Screen( val height = b - t dispatchShadowStateUpdate(width, height, t) + RNSLog.d("Screen", "Screen [$id] $height") // FormSheet has no header in current model. notifyHeaderHeightChange(t) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt index 89a9580121..0bb8ada269 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt @@ -45,7 +45,7 @@ class ScreenStackHeaderConfig( private var isShadowHidden = false private var isDestroyed = false private var backButtonInCustomView = false - var isTopInsetEnabled = true + var isTopInsetEnabled = false private set private var tintColor = 0 @@ -396,6 +396,7 @@ class ScreenStackHeaderConfig( fun setTopInsetEnabled(topInsetEnabled: Boolean) { isTopInsetEnabled = topInsetEnabled + isTopInsetEnabled = false } fun setBackgroundColor(color: Int?) { From 8d9839cac2399f1e5ac0bbbdb39fde692792306a Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sun, 7 Sep 2025 11:57:34 +0200 Subject: [PATCH 2/6] Stash debugging setup --- .../com/swmansion/rnscreens/CustomToolbar.kt | 5 ++ .../java/com/swmansion/rnscreens/Screen.kt | 2 +- .../rnscreens/ScreenStackHeaderConfig.kt | 3 +- apps/src/tests/TestFerran.tsx | 85 +++++++++++++++++++ apps/src/tests/index.ts | 1 + ...reenStackHeaderConfigComponentDescriptor.h | 53 +++++++----- 6 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 apps/src/tests/TestFerran.tsx diff --git a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt index ed43348f99..1c1cd8fa0e 100644 --- a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +++ b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt @@ -11,6 +11,7 @@ import androidx.core.view.WindowInsetsCompat import com.facebook.react.modules.core.ReactChoreographer import com.facebook.react.uimanager.ThemedReactContext import com.swmansion.rnscreens.utils.InsetsCompat +import com.swmansion.rnscreens.utils.RNSLog import com.swmansion.rnscreens.utils.resolveInsetsOrZero import kotlin.math.max @@ -153,6 +154,8 @@ open class CustomToolbar( ) { super.onLayout(hasSizeChanged, l, t, r, b) + RNSLog.d("Toolbar", "onLayout") + config.onNativeToolbarLayout( this, hasSizeChanged || isForceShadowStateUpdateOnLayoutRequested, @@ -172,10 +175,12 @@ open class CustomToolbar( bottom: Int, ) { requestForceShadowStateUpdateOnLayout() + RNSLog.d("Toolbar", "Toolbar: applyExactPadding($left, $top, $right, $bottom)") setPadding(left, top, right, bottom) } private fun requestForceShadowStateUpdateOnLayout() { + RNSLog.d("Toolbar", "Toolbar: Requesting shadow state update on custom toolbar padding $shouldAvoidDisplayCutout") isForceShadowStateUpdateOnLayoutRequested = shouldAvoidDisplayCutout } } diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 0e8675415b..a97a2274ec 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -185,7 +185,7 @@ class Screen( val height = b - t dispatchShadowStateUpdate(width, height, t) - RNSLog.d("Screen", "Screen [$id] $height") + RNSLog.d("Screen", "Screen [$id]: onLayout height=$height") // FormSheet has no header in current model. notifyHeaderHeightChange(t) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt index 0bb8ada269..89a9580121 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt @@ -45,7 +45,7 @@ class ScreenStackHeaderConfig( private var isShadowHidden = false private var isDestroyed = false private var backButtonInCustomView = false - var isTopInsetEnabled = false + var isTopInsetEnabled = true private set private var tintColor = 0 @@ -396,7 +396,6 @@ class ScreenStackHeaderConfig( fun setTopInsetEnabled(topInsetEnabled: Boolean) { isTopInsetEnabled = topInsetEnabled - isTopInsetEnabled = false } fun setBackgroundColor(color: Int?) { diff --git a/apps/src/tests/TestFerran.tsx b/apps/src/tests/TestFerran.tsx new file mode 100644 index 0000000000..d918ea06de --- /dev/null +++ b/apps/src/tests/TestFerran.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; + +import { View, Text, StyleSheet } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack'; + +type RootTabParamList = { + First: undefined; + Second: undefined; + Third: undefined +}; + +type InnerParamList = { + Inner: undefined; +}; + +type Props = NativeStackScreenProps; +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +const options = { + tabBarIcon: () => null, +}; + +function RegularScreen({ route }: Props) { + const { name } = route; + return ( + + {`${name} Screen`} + + ); +} + + +function FirstStack() { + return ( + + + + ); +} + +function SecondStack() { + return ( + + + + ); +} + +function ThirdStack() { + return ( + + + + ); +} + +function RootStack() { + return ( + + + + + + ); +} + +export default function App() { + return ( + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, +}); + diff --git a/apps/src/tests/index.ts b/apps/src/tests/index.ts index 622bbeb346..2a25332f77 100644 --- a/apps/src/tests/index.ts +++ b/apps/src/tests/index.ts @@ -161,3 +161,4 @@ export { default as TestAnimation } from './TestAnimation'; export { default as TestBottomTabs } from './TestBottomTabs'; export { default as TestScreenStack } from './TestScreenStack'; export { default as TestSplitView } from './TestSplitView'; +export { default as TestFerran } from './TestFerran'; diff --git a/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigComponentDescriptor.h b/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigComponentDescriptor.h index 6c1471a5c4..1b96df56e9 100644 --- a/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigComponentDescriptor.h +++ b/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigComponentDescriptor.h @@ -19,31 +19,35 @@ class RNSScreenStackHeaderConfigComponentDescriptor final using ConcreteComponentDescriptor::ConcreteComponentDescriptor; void adopt(ShadowNode &shadowNode) const override { - react_native_assert( - dynamic_cast(&shadowNode)); - auto &configShadowNode = - static_cast(shadowNode); - - react_native_assert( - dynamic_cast(&configShadowNode)); - auto &layoutableShadowNode = - dynamic_cast(configShadowNode); - - auto state = std::static_pointer_cast< - const RNSScreenStackHeaderConfigShadowNode::ConcreteState>( - shadowNode.getState()); - auto stateData = state->getData(); + // react_native_assert( + // dynamic_cast(&shadowNode)); + // auto &configShadowNode = + // static_cast(shadowNode); + // + // react_native_assert( + // dynamic_cast(&configShadowNode)); + // auto &layoutableShadowNode = + // dynamic_cast(configShadowNode); + // + // auto state = std::static_pointer_cast< + // const RNSScreenStackHeaderConfigShadowNode::ConcreteState>( + // shadowNode.getState()); + // auto stateData = state->getData(); #ifdef ANDROID - if (stateData.frameSize.width != 0) { - layoutableShadowNode.setSize({stateData.frameSize.width, YGUndefined}); - layoutableShadowNode.setPadding({ - stateData.paddingStart, - 0, - stateData.paddingEnd, - 0, - }); - } +// if (stateData.frameSize.width != 0) { +// initialHeaderHeightCandidate = stateData.frameSize.height; +// layoutableShadowNode.setSize({stateData.frameSize.width, YGUndefined}); +// layoutableShadowNode.setPadding({ +// stateData.paddingStart, +// 0, +// stateData.paddingEnd, +// 0, +// }); +// } else { +// layoutableShadowNode.setSize({YGUndefined, 80.f}); +// } #else if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { layoutableShadowNode.setSize(stateData.frameSize); @@ -58,6 +62,9 @@ class RNSScreenStackHeaderConfigComponentDescriptor final configShadowNode.setImageLoader(imageLoader); #endif // !ANDROID && !NDEBUG } + + private: + // mutable Float initialHeaderHeightCandidate{-1.0f}; }; } // namespace facebook::react From 42d1c550b67a26ddf6e6bd4172ed288c892c0c51 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sun, 7 Sep 2025 20:02:59 +0200 Subject: [PATCH 3/6] Even more debugging setup + notes Seems that this is a platform problem. This baffles me. It just can not be, that this can not be avoided in native app. I'm sure we have some specific setup that causes this kind of bug. Basically what happens is as follows (Pixel 3a, API 35, 1080px x 2160px): 1. Framework does measure pass. Toolbar is measured w/o taking status bar inset into consideration and receives `measuredHeight` of `140px`. 2. `Screen` receives initial dimensions of `1838px` (dunno where the rest of height is lost). 3. Framework dispatches window insets -> `CustomToolbar` applies `paddingTop = 60px`. 4. `CustomToolbar` layout where it receives `height=140px` from parent. 5. Another `CustomToolbar` layout where it receives `height=140px` from parent. 6. Yet another `CustomToolbar` layout, but it receives `height=200px` this time. 7. `Screen` layout with appropriate dimensions of `height=1778px`. What is jarring is that these intermediate states are visible leading to "flicker". I've tried to stabilize it by overriding `Toolbar.onMeasure` and increasing `measuredHeight` by `statusBarInset` until the inset is considered in `onLayout` for the first time & it **partially** fixed the problem - the header view was stable, however the text inside (screen title) still flickered. Would have to "stabilize" it manually & I really don't want to do that. This must be solvable in some reasonable way. --- .../com/swmansion/rnscreens/CustomToolbar.kt | 63 ++++++++++++++----- .../java/com/swmansion/rnscreens/Screen.kt | 4 +- .../rnscreens/ScreenStackHeaderConfig.kt | 12 ++-- .../stack/views/ScreensCoordinatorLayout.kt | 3 +- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt index 1c1cd8fa0e..34bd9d68ee 100644 --- a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +++ b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt @@ -35,6 +35,7 @@ open class CustomToolbar( get() = config.isTopInsetEnabled private var lastInsets = InsetsCompat.NONE + private var laidOutAfterInsetsDispatch = false private var isForceShadowStateUpdateOnLayoutRequested = false @@ -53,6 +54,21 @@ open class CustomToolbar( } } + private fun requestLayoutInCurrentLoop() { + @Suppress("SENSELESS_COMPARISON") // mLayoutCallback can be null here since this method can be called in init + if (!isLayoutEnqueued && layoutCallback != null) { + isLayoutEnqueued = true + // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current + // looper loop instead of enqueueing the update in the next loop causing a one frame delay. + ReactChoreographer + .getInstance() + .postFrameCallback( + ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, + layoutCallback, + ) + } + } + override fun requestLayout() { super.requestLayout() val softInputMode = @@ -67,23 +83,13 @@ open class CustomToolbar( // the position of each subview, even if Yoga has correctly set their width and height). // This is mostly the issue, when windowSoftInputMode is set to adjustPan in AndroidManifest. // Thus, we're manually calling the layout **after** the current layout. - @Suppress("SENSELESS_COMPARISON") // mLayoutCallback can be null here since this method can be called in init - if (!isLayoutEnqueued && layoutCallback != null) { - isLayoutEnqueued = true - // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current - // looper loop instead of enqueueing the update in the next loop causing a one frame delay. - ReactChoreographer - .getInstance() - .postFrameCallback( - ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, - layoutCallback, - ) - } + requestLayoutInCurrentLoop() } } override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets? { val unhandledInsets = super.onApplyWindowInsets(insets) + RNSLog.d("Toolbar", "onApplyWindowInsets $unhandledInsets") // There are few UI modes we could be running in // @@ -145,6 +151,31 @@ open class CustomToolbar( return unhandledInsets } + override fun onMeasure( + widthMeasureSpec: Int, + heightMeasureSpec: Int, + ) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val initialHeight = measuredHeight + + val effectiveHeight = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + measuredHeight + + rootWindowInsets + .getInsets( + WindowInsets.Type.statusBars(), + ).top + } else { + TODO("VERSION.SDK_INT < Q") + } + + if (lastInsets == InsetsCompat.NONE || !laidOutAfterInsetsDispatch) { + setMeasuredDimension(measuredWidth, effectiveHeight) + RNSLog.d("Toolbar", "Toolbar: onMeasure initial: $initialHeight effectiveHeight: $effectiveHeight") + } + } + override fun onLayout( hasSizeChanged: Boolean, l: Int, @@ -152,9 +183,12 @@ open class CustomToolbar( r: Int, b: Int, ) { + RNSLog.d("Toolbar", "onLayout height=${b - t} insets\n$rootWindowInsets") super.onLayout(hasSizeChanged, l, t, r, b) - RNSLog.d("Toolbar", "onLayout") + if (lastInsets != InsetsCompat.NONE) { + laidOutAfterInsetsDispatch = true + } config.onNativeToolbarLayout( this, @@ -174,9 +208,10 @@ open class CustomToolbar( right: Int, bottom: Int, ) { - requestForceShadowStateUpdateOnLayout() RNSLog.d("Toolbar", "Toolbar: applyExactPadding($left, $top, $right, $bottom)") + requestForceShadowStateUpdateOnLayout() setPadding(left, top, right, bottom) + requestLayoutInCurrentLoop() } private fun requestForceShadowStateUpdateOnLayout() { diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index a97a2274ec..7715bd4fa0 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -184,11 +184,11 @@ class Screen( val width = r - l val height = b - t - dispatchShadowStateUpdate(width, height, t) +// dispatchShadowStateUpdate(width, height, t) RNSLog.d("Screen", "Screen [$id]: onLayout height=$height") // FormSheet has no header in current model. - notifyHeaderHeightChange(t) +// notifyHeaderHeightChange(t) } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt index 89a9580121..47a0a17a59 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt @@ -130,12 +130,12 @@ class ScreenStackHeaderConfig( val contentInsetEnd = toolbar.currentContentInsetEnd + toolbar.paddingEnd // Note that implementation of the callee differs between architectures. - updateHeaderConfigState( - toolbar.width, - toolbar.height, - contentInsetStart, - contentInsetEnd, - ) +// updateHeaderConfigState( +// toolbar.width, +// toolbar.height, +// contentInsetStart, +// contentInsetEnd, +// ) } override fun onLayout( diff --git a/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt b/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt index ff1409d9b2..c502af5bd8 100644 --- a/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt +++ b/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt @@ -23,7 +23,8 @@ internal class ScreensCoordinatorLayout( PointerEventsBoxNoneImpl(), ) - override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets) + override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = + super.onApplyWindowInsets(insets) private val animationListener: Animation.AnimationListener = object : Animation.AnimationListener { From b26509f523b4ffaeef8de090b7b8b9ba231c3394 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sun, 7 Sep 2025 23:49:03 +0200 Subject: [PATCH 4/6] Try different approaches to mitigate the issue --- android/build.gradle | 6 +-- .../com/swmansion/rnscreens/CustomToolbar.kt | 50 +++++++------------ .../java/com/swmansion/rnscreens/Screen.kt | 5 ++ .../swmansion/rnscreens/ScreenContainer.kt | 3 ++ .../com/swmansion/rnscreens/ScreenStack.kt | 5 ++ .../rnscreens/ScreenStackFragment.kt | 2 + 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 114be81b3e..f2e8e8b3cc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -239,9 +239,9 @@ repositories { dependencies { implementation 'com.facebook.react:react-native:+' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.fragment:fragment-ktx:1.6.1' - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'androidx.fragment:fragment-ktx:1.8.9' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'com.google.android.material:material:1.12.0' implementation "androidx.core:core-ktx:1.8.0" diff --git a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt index 34bd9d68ee..225d5c635d 100644 --- a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +++ b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt @@ -26,6 +26,10 @@ open class CustomToolbar( context: Context, val config: ScreenStackHeaderConfig, ) : Toolbar(context) { + init { + fitsSystemWindows = true + } + // Switch this flag to enable/disable display cutout avoidance. // Currently this is controlled by isTopInsetEnabled prop. private val shouldAvoidDisplayCutout @@ -140,42 +144,17 @@ open class CustomToolbar( if (lastInsets != newInsets) { lastInsets = newInsets - applyExactPadding( - lastInsets.left, - lastInsets.top, - lastInsets.right, - lastInsets.bottom, - ) +// applyExactPadding( +// lastInsets.left, +// lastInsets.top, +// lastInsets.right, +// lastInsets.bottom, +// ) } return unhandledInsets } - override fun onMeasure( - widthMeasureSpec: Int, - heightMeasureSpec: Int, - ) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - val initialHeight = measuredHeight - - val effectiveHeight = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - measuredHeight + - rootWindowInsets - .getInsets( - WindowInsets.Type.statusBars(), - ).top - } else { - TODO("VERSION.SDK_INT < Q") - } - - if (lastInsets == InsetsCompat.NONE || !laidOutAfterInsetsDispatch) { - setMeasuredDimension(measuredWidth, effectiveHeight) - RNSLog.d("Toolbar", "Toolbar: onMeasure initial: $initialHeight effectiveHeight: $effectiveHeight") - } - } - override fun onLayout( hasSizeChanged: Boolean, l: Int, @@ -197,6 +176,11 @@ open class CustomToolbar( isForceShadowStateUpdateOnLayoutRequested = false } +// override fun onAttachedToWindow() { +// super.onAttachedToWindow() +// dispatchApplyWindowInsets(rootWindowInsets) +// } + fun updateContentInsets() { contentInsetStartWithNavigation = config.preferredContentInsetStartWithNavigation setContentInsetsRelative(config.preferredContentInsetStart, config.preferredContentInsetEnd) @@ -209,9 +193,9 @@ open class CustomToolbar( bottom: Int, ) { RNSLog.d("Toolbar", "Toolbar: applyExactPadding($left, $top, $right, $bottom)") - requestForceShadowStateUpdateOnLayout() setPadding(left, top, right, bottom) - requestLayoutInCurrentLoop() + requestForceShadowStateUpdateOnLayout() +// requestLayoutInCurrentLoop() } private fun requestForceShadowStateUpdateOnLayout() { diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 7715bd4fa0..e71a4b4a51 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -161,6 +161,11 @@ class Screen( wrapper.delegate = this } + internal fun onAddedToContainer(container: ScreenStack) { +// this.headerConfig!!.toolbar.setPadding(0, 60, 0, 0) + (fragment as ScreenStackFragment).setToolbar(this.headerConfig!!.toolbar) + } + override fun dispatchSaveInstanceState(container: SparseArray) { // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize // view's states. The side effect of restoring is that TextInput components would trigger diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt index 422b21bc60..62e7fd20d0 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt @@ -106,9 +106,12 @@ open class ScreenContainer( screen.fragmentWrapper = fragment screenWrappers.add(index, fragment) screen.container = this + onScreenAdded(screen) onScreenChanged() } + open fun onScreenAdded(screen: Screen) = Unit + open fun removeScreenAt(index: Int) { screenWrappers[index].screen.container = null screenWrappers.removeAt(index) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 9894cd4be0..e427ecfe06 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -312,6 +312,11 @@ class ScreenStack( stack.forEach { it.onContainerUpdate() } } + override fun onScreenAdded(screen: Screen) { + super.onScreenAdded(screen) + screen.onAddedToContainer(this) + } + private fun drawAndRelease() { // We make a copy of the drawingOps and use it to dispatch draws in order to be sure // that we do not modify the original list. There are cases when `op.draw` can call diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 1e77408dfc..bcc81f701e 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -13,6 +13,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.view.WindowInsets import android.view.animation.Animation import android.widget.LinearLayout import androidx.appcompat.widget.Toolbar @@ -210,6 +211,7 @@ class ScreenStackFragment : AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT, ) + this.clipToPadding = false } coordinatorLayout.addView(appBarLayout) From 625ec71de62d42975539059b5f537d1fa66ebb60 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Mon, 8 Sep 2025 09:05:39 +0200 Subject: [PATCH 5/6] More expoeriments --- android/src/main/java/com/swmansion/rnscreens/Screen.kt | 5 ++--- .../main/java/com/swmansion/rnscreens/ScreenStackFragment.kt | 2 +- 2 files changed, 3 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 e71a4b4a51..6597220e1d 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -162,7 +162,6 @@ class Screen( } internal fun onAddedToContainer(container: ScreenStack) { -// this.headerConfig!!.toolbar.setPadding(0, 60, 0, 0) (fragment as ScreenStackFragment).setToolbar(this.headerConfig!!.toolbar) } @@ -189,11 +188,11 @@ class Screen( val width = r - l val height = b - t -// dispatchShadowStateUpdate(width, height, t) + dispatchShadowStateUpdate(width, height, t) RNSLog.d("Screen", "Screen [$id]: onLayout height=$height") // FormSheet has no header in current model. -// notifyHeaderHeightChange(t) + notifyHeaderHeightChange(t) } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index bcc81f701e..09a9082702 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -211,7 +211,7 @@ class ScreenStackFragment : AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT, ) - this.clipToPadding = false +// this.clipToPadding = false } coordinatorLayout.addView(appBarLayout) From 93d53b7ef2f84352f916021f6049eda0266c1ad9 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Mon, 8 Sep 2025 14:26:07 +0200 Subject: [PATCH 6/6] Update example --- apps/src/tests/TestFerran.tsx | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/src/tests/TestFerran.tsx b/apps/src/tests/TestFerran.tsx index d918ea06de..c1b8a29ef1 100644 --- a/apps/src/tests/TestFerran.tsx +++ b/apps/src/tests/TestFerran.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; +import { View, Text, StyleSheet, Button } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack'; @@ -13,6 +13,7 @@ type RootTabParamList = { type InnerParamList = { Inner: undefined; + Second: undefined; }; type Props = NativeStackScreenProps; @@ -25,9 +26,22 @@ const options = { function RegularScreen({ route }: Props) { const { name } = route; + const navigation = useNavigation(); + return ( + + {`${name} Screen`} +