diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt index 3916fdc2ac..4d003a61cb 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt @@ -10,10 +10,12 @@ import android.view.WindowInsets import android.widget.FrameLayout import androidx.appcompat.view.ContextThemeWrapper import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.children import androidx.core.view.size import androidx.fragment.app.FragmentManager +import com.facebook.react.uimanager.ThemedReactContext import com.google.android.material.R import com.google.android.material.bottomnavigation.BottomNavigationView import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme @@ -127,6 +129,9 @@ class TabsContainer internal constructor( internal var colorScheme: ColorScheme by colorSchemeCoordinator::colorScheme internal var tabBarRespectsIMEInsets: Boolean = false + internal var tabBarShouldApplyInsetsSynchronously: Boolean = true + private var insetsAppliedBySystem = false + private val contentView: FrameLayout = FrameLayout(context).apply { layoutParams = @@ -274,6 +279,8 @@ class TabsContainer internal constructor( RNSLog.d(TAG, "TabsContainer [$id] attached to window") super.onAttachedToWindow() + + maybeApplyDecorViewInsetsSynchronously() setupFragmentManager() // When TabsContainer is reattached to window, it might find new fragment manager (other @@ -306,6 +313,19 @@ class TabsContainer internal constructor( colorSchemeCoordinator.onConfigurationChanged(newConfig) } + private fun maybeApplyDecorViewInsetsSynchronously() { + if (insetsAppliedBySystem || !tabBarShouldApplyInsetsSynchronously) return + + val activity = (context as? ThemedReactContext)?.currentActivity ?: return + val decorView = activity.window.decorView + val insetsCompat = ViewCompat.getRootWindowInsets(decorView) ?: return + + val windowInsets = insetsCompat.toWindowInsets() + if (windowInsets != null) { + dispatchApplyWindowInsets(windowInsets) + } + } + override fun dispatchApplyWindowInsets(insets: WindowInsets?): WindowInsets? { // On Android versions prior to R, insets dispatch is broken. // In order to mitigate this, we override dispatchApplyWindowInsets with @@ -316,6 +336,8 @@ class TabsContainer internal constructor( return insets } + insetsAppliedBySystem = true + for (child in children) { if (child === bottomNavigationView) { val insetsForBottomNavigationView = getInsetsForBottomNavigationView(insets) diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt index 8da0489f1b..f165a4b3d2 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt @@ -58,6 +58,8 @@ class TabsHost( internal var colorScheme: ColorScheme by container::colorScheme var tabBarRespectsIMEInsets: Boolean by container::tabBarRespectsIMEInsets + internal var tabBarShouldApplyInsetsSynchronously: Boolean by container::tabBarShouldApplyInsetsSynchronously + init { addView(container) check(container.addNavigationStateObserver(this)) { diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt index c640543d2f..4defab87f1 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt @@ -134,6 +134,13 @@ class TabsHostViewManager : } } + override fun setTabBarShouldApplyInsetsSynchronously( + view: TabsHost, + shouldApply: Boolean, + ) { + view.tabBarShouldApplyInsetsSynchronously = shouldApply + } + companion object { const val REACT_CLASS = "RNSTabsHostAndroid" } diff --git a/apps/src/tests/single-feature-tests/tabs/index.ts b/apps/src/tests/single-feature-tests/tabs/index.ts index 39584ef9dc..031210d657 100644 --- a/apps/src/tests/single-feature-tests/tabs/index.ts +++ b/apps/src/tests/single-feature-tests/tabs/index.ts @@ -15,6 +15,7 @@ import TestTabsStaleStateUpdateRejection from './test-tabs-stale-update-rejectio import TestTabsTabBarMinimizeBehavior from './test-tabs-tab-bar-minimize-behavior-ios'; import TestTabsTabBarControllerMode from './test-tabs-tab-bar-controller-mode-ios'; import TestTabsSpecialEffectsScrollToTop from './test-tabs-special-effects-scroll-to-top'; +import TestTabsSynchronousInsetsAndroid from './test-tabs-synchronous-insets-android'; import TestTabsTabBarExperimentalUserInterfaceStyle from './test-tabs-tab-bar-experimental-user-interface-style-ios'; import TestTabsLifecycleEvents from './test-tabs-lifecycle-events'; import TestTabsItemTitle from './test-tabs-item-title-ios'; @@ -35,6 +36,7 @@ const scenarios = { TestTabsTabBarMinimizeBehavior, TestTabsTabBarControllerMode, TestTabsSpecialEffectsScrollToTop, + TestTabsSynchronousInsetsAndroid, TestTabsTabBarExperimentalUserInterfaceStyle, TestTabsLifecycleEvents, TestTabsItemTitle, diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-synchronous-insets-android/index.tsx b/apps/src/tests/single-feature-tests/tabs/test-tabs-synchronous-insets-android/index.tsx new file mode 100644 index 0000000000..e357f55d74 --- /dev/null +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-synchronous-insets-android/index.tsx @@ -0,0 +1,153 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import { Button, StyleSheet, Text, View } from 'react-native'; + +import { scenarioDescription } from './scenario-description'; +import { createScenario } from '@apps/tests/shared/helpers'; +import { SettingsSwitch } from '@apps/shared'; +import { + TabsContainerWithHostConfigContext, + type TabRouteConfig, + useTabsHostConfig, + DEFAULT_TAB_ROUTE_OPTIONS, +} from '@apps/shared/gamma/containers/tabs'; +import { + StackContainer, + useStackNavigationContext, +} from '@apps/shared/gamma/containers/stack'; +import { Colors } from '@apps/shared/styling'; + +const TestConfigContext = createContext({ + syncInsets: true, + setSyncInsets: (_: boolean) => {}, +}); + +export function SetupScreen() { + const { syncInsets, setSyncInsets } = useContext(TestConfigContext); + const { push } = useStackNavigationContext(); + + return ( + + Test Configuration + + + + + +