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 ed43348f99..225d5c635d 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 @@ -25,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 @@ -34,6 +39,7 @@ open class CustomToolbar( get() = config.isTopInsetEnabled private var lastInsets = InsetsCompat.NONE + private var laidOutAfterInsetsDispatch = false private var isForceShadowStateUpdateOnLayoutRequested = false @@ -52,6 +58,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 = @@ -66,23 +87,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 // @@ -133,12 +144,12 @@ 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 @@ -151,8 +162,13 @@ open class CustomToolbar( r: Int, b: Int, ) { + RNSLog.d("Toolbar", "onLayout height=${b - t} insets\n$rootWindowInsets") super.onLayout(hasSizeChanged, l, t, r, b) + if (lastInsets != InsetsCompat.NONE) { + laidOutAfterInsetsDispatch = true + } + config.onNativeToolbarLayout( this, hasSizeChanged || isForceShadowStateUpdateOnLayoutRequested, @@ -160,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) @@ -171,11 +192,14 @@ open class CustomToolbar( right: Int, bottom: Int, ) { - requestForceShadowStateUpdateOnLayout() + RNSLog.d("Toolbar", "Toolbar: applyExactPadding($left, $top, $right, $bottom)") setPadding(left, top, right, bottom) + requestForceShadowStateUpdateOnLayout() +// requestLayoutInCurrentLoop() } 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 2b7dcff55f..6597220e1d 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( @@ -160,6 +161,10 @@ class Screen( wrapper.delegate = this } + internal fun onAddedToContainer(container: ScreenStack) { + (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 @@ -184,6 +189,7 @@ class Screen( val height = b - t dispatchShadowStateUpdate(width, height, t) + 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/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..09a9082702 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) 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 { diff --git a/apps/src/tests/TestFerran.tsx b/apps/src/tests/TestFerran.tsx new file mode 100644 index 0000000000..c1b8a29ef1 --- /dev/null +++ b/apps/src/tests/TestFerran.tsx @@ -0,0 +1,102 @@ +import * as React from 'react'; + +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'; + +type RootTabParamList = { + First: undefined; + Second: undefined; + Third: undefined +}; + +type InnerParamList = { + Inner: undefined; + Second: undefined; +}; + +type Props = NativeStackScreenProps; +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +const options = { + tabBarIcon: () => null, +}; + +function RegularScreen({ route }: Props) { + const { name } = route; + const navigation = useNavigation(); + return ( + + {`${name} Screen`} +