diff --git a/FabricExample/package.json b/FabricExample/package.json
index 3dbb7d2347..9027ca87c6 100644
--- a/FabricExample/package.json
+++ b/FabricExample/package.json
@@ -28,7 +28,7 @@
"react": "19.0.0",
"react-native": "0.78.0-rc.5",
"react-native-gesture-handler": "2.22.0",
- "react-native-reanimated": "^4.0.0-nightly-20250218-e5a0cdf69",
+ "react-native-reanimated": "4.0.0-nightly-20250218-e5a0cdf69",
"react-native-restart": "^0.0.27",
"react-native-safe-area-context": "5.2.0",
"react-native-screens": "link:../"
diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt
index a1e24fd6b0..cdcd19f11d 100644
--- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt
+++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.graphics.Paint
import android.os.Parcelable
+import android.util.Log
import android.util.SparseArray
import android.view.MotionEvent
import android.view.View
@@ -132,6 +133,8 @@ class Screen(
) {
val height = bottom - top
+ Log.i("HT", "Screen [$id] onContentWrapperLayout - $height")
+
if (usesFormSheetPresentation()) {
if (isSheetFitToContents()) {
sheetBehavior?.useSingleDetent(height)
@@ -139,11 +142,13 @@ class Screen(
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// On old architecture we delay enter transition in order to wait for initial frame.
+ Log.i("HT", "Screen [$id] onContentWrapperLayout - request transition trigger")
shouldTriggerPostponedTransitionAfterLayout = true
val parent = parentAsViewGroup()
if (parent != null && !parent.isInLayout) {
// There are reported cases (irreproducible) when Screen is not laid out after
// maxHeight is set on behaviour.
+ Log.i("HT", "Screen [$id] onContentWrapperLayout - request parent layout")
parent.requestLayout()
}
}
@@ -171,6 +176,7 @@ class Screen(
r: Int,
b: Int,
) {
+ Log.i("HT", "Screen [$id] received layout ${b - t}")
// In case of form sheet we get layout notification a bit later, in `onBottomSheetBehaviorDidLayout`
// after the attached behaviour laid out this view.
if (changed && isNativeStackScreen && !usesFormSheetPresentation()) {
@@ -193,6 +199,7 @@ class Screen(
}
footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height)
+ Log.i("HT", "Screen [$id] behavior layout")
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// When using form sheet presentation we want to delay enter transition **on Paper** in order
@@ -206,6 +213,7 @@ class Screen(
if (shouldTriggerPostponedTransitionAfterLayout) {
shouldTriggerPostponedTransitionAfterLayout = false
// This will trigger enter transition only if one was requested by ScreenStack
+ Log.i("HT", "Screen [$id] triggering postponed transition")
fragment?.startPostponedEnterTransition()
}
}
@@ -307,6 +315,7 @@ class Screen(
throw IllegalStateException("[RNScreens] activityState can only progress in NativeStack")
}
this.activityState = activityState
+ Log.i("HT", "Screen [$id] activityState change to $activityState")
container?.onChildUpdate()
}
diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
index d5f940ea91..6a70eb40a8 100644
--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
@@ -3,6 +3,7 @@ package com.swmansion.rnscreens
import android.content.Context
import android.graphics.Canvas
import android.os.Build
+import android.util.Log
import android.view.View
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerHelper
@@ -213,6 +214,7 @@ class ScreenStack(
}
createTransaction().let { transaction ->
+ Log.i("HT", "ScreenStack createTransaction")
if (stackAnimation != null) {
transaction.setTweenAnimations(stackAnimation, shouldUseOpenAnimation)
}
@@ -226,14 +228,18 @@ class ScreenStack(
dismissedWrappers.contains(
wrapper,
)
- }.forEach { wrapper -> transaction.remove(wrapper.fragment) }
+ }.forEach { wrapper ->
+ Log.i("HT", "ScreenStack remove 1 ${wrapper.fragment.id}")
+ transaction.remove(wrapper.fragment) }
// Remove all screens underneath visibleBottom && these marked for preload, but keep newTop.
screenWrappers
.asSequence()
.takeWhile { it !== visibleBottom }
.filter { (it !== newTop && !dismissedWrappers.contains(it)) || it.screen.activityState === Screen.ActivityState.INACTIVE }
- .forEach { wrapper -> transaction.remove(wrapper.fragment) }
+ .forEach { wrapper ->
+ Log.i("HT", "ScreenStack remove 2 ${wrapper.fragment.id}")
+ transaction.remove(wrapper.fragment) }
// attach screens that just became visible
if (visibleBottom != null && !visibleBottom.fragment.isAdded) {
@@ -243,14 +249,17 @@ class ScreenStack(
.dropWhile { it !== visibleBottom } // ignore all screens beneath the visible bottom
.forEach { wrapper ->
// TODO: It should be enough to dispatch this on commit action once.
+ Log.i("HT", "ScreenStack add 1 ${wrapper.fragment.id}")
transaction.add(id, wrapper.fragment).runOnCommit {
top?.screen?.bringToFront()
}
}
} else if (newTop != null && !newTop.fragment.isAdded) {
if (newTop.screen.requiresEnterTransitionPostponing()) {
+ Log.i("HT", "ScreenStack postponeEnterTransition of ${newTop.screen.id}")
newTop.fragment.postponeEnterTransition()
}
+ Log.i("HT", "ScreenStack add 2 ${newTop.screen.id}")
transaction.add(id, newTop.fragment)
}
@@ -259,6 +268,7 @@ class ScreenStack(
stack.addAll(screenWrappers.asSequence().map { it as ScreenStackFragmentWrapper })
turnOffA11yUnderTransparentScreen(visibleBottom)
+ Log.i("HT", "ScreenStack commitTransaction")
transaction.commitNowAllowingStateLoss()
}
}
diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
index 8b357404d7..3326e2a13f 100644
--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
@@ -7,6 +7,7 @@ import android.annotation.SuppressLint
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -285,6 +286,7 @@ class ScreenStackFragment :
ValueAnimator.ofObject(evaluator, screen.height.toFloat(), 0f).apply {
addUpdateListener { anim ->
val animatedValue = anim.animatedValue as? Float
+ Log.i("HT", "transitionProgress $animatedValue")
animatedValue?.let { screen.translationY = it }
}
}
diff --git a/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt
index 34fedac164..bfd1df7187 100644
--- a/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt
+++ b/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt
@@ -30,6 +30,7 @@ class ScreenAnimationDelegate(
override fun onAnimationStart(animation: Animator) {
if (currentState === LifecycleState.INITIALIZED) {
progressState()
+ Log.i("HT", "AnimationDelegate onAnimationStart")
// These callbacks do not work as expected from this call site, TODO: investigate it.
// To fix it quickly we emit required events manually
@@ -53,6 +54,7 @@ class ScreenAnimationDelegate(
if (currentState === LifecycleState.START_DISPATCHED) {
progressState()
animation.removeListener(this)
+ Log.i("HT", "AnimationDelegate onAnimationEnd")
// wrapper.onViewAnimationEnd()
diff --git a/apps/App.tsx b/apps/App.tsx
index 8ed1f92441..0b5c449eb1 100644
--- a/apps/App.tsx
+++ b/apps/App.tsx
@@ -1,11 +1,11 @@
import React from 'react';
import { enableFreeze } from 'react-native-screens';
-import Example from './Example';
-//import * as Test from './src/tests';
+// import Example from './Example';
+import * as Test from './src/tests';
enableFreeze(true);
export default function App() {
- return ;
- //return ;
+ // return ;
+ return ;
}
diff --git a/apps/src/tests/TestRepro.tsx b/apps/src/tests/TestRepro.tsx
new file mode 100644
index 0000000000..7358cc744a
--- /dev/null
+++ b/apps/src/tests/TestRepro.tsx
@@ -0,0 +1,141 @@
+import React from 'react';
+import { NavigationContainer, useNavigation } from '@react-navigation/native';
+import { Button, Text, View } from 'react-native';
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+
+const RootStack = createNativeStackNavigator();
+const Tabs = createBottomTabNavigator();
+const TabThreeStack = createNativeStackNavigator();
+
+function RootStackHost() {
+ return (
+
+
+
+
+ );
+}
+
+function TabsHost() {
+ const navigation = useNavigation();
+
+ React.useEffect(() => {
+ navigation.preload('RootSheet');
+ }, [navigation]);
+
+ return (
+
+
+
+ {
+ return {
+ tabPress: (event) => {
+ event.preventDefault();
+ navigation.preload('TabThree');
+ navigation.navigate('RootSheet');
+ },
+ };
+ }} />
+
+ );
+}
+
+function RootHome() {
+ const navigation = useNavigation();
+
+ return (
+
+ RootHome
+
+ );
+}
+
+function RootSheet() {
+ const navigation = useNavigation();
+
+ return (
+
+ RootSheet
+
+ );
+}
+
+function TabOne() {
+ return (
+
+ TabOne
+
+ );
+}
+
+function TabTwo() {
+ return (
+
+ TabTwo
+
+ );
+}
+
+function TabThree() {
+ // return (
+ //
+ // TabThree
+ //
+ // );
+ return (
+
+ );
+}
+
+function TabThreeStackScreenOne() {
+ return (
+
+ TabThreeStackScreenOne
+
+ );
+}
+
+function TabThreeStackScreenTwo() {
+ return (
+
+ TabThreeStackScreenTwo
+
+ );
+}
+
+function TabThreeStackHost() {
+ return (
+
+
+
+
+
+ );
+}
+
+function App() {
+ return (
+
+
+
+ );
+}
+export default App;
diff --git a/apps/src/tests/index.ts b/apps/src/tests/index.ts
index 593558a311..2c52a697d4 100644
--- a/apps/src/tests/index.ts
+++ b/apps/src/tests/index.ts
@@ -129,6 +129,7 @@ export { default as Test2767 } from './Test2767';
export { default as Test2789 } from './Test2789';
export { default as Test2811 } from './Test2811';
export { default as Test2819 } from './Test2819';
+export { default as TestRepro } from './TestRepro';
export { default as TestScreenAnimation } from './TestScreenAnimation';
export { default as TestScreenAnimationV5 } from './TestScreenAnimationV5';
export { default as TestHeader } from './TestHeader';