diff --git a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/AppNavigationBar.kt b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/AppNavigationBar.kt index d4fb9ff92..35afd328b 100644 --- a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/AppNavigationBar.kt +++ b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/AppNavigationBar.kt @@ -27,6 +27,7 @@ import com.flipcash.app.core.ui.rememberNavigationBarState import com.flipcash.app.featureflags.FeatureFlag import com.flipcash.app.featureflags.LocalFeatureFlags import com.flipcash.app.session.LocalSessionController +import com.getcode.manager.BottomBarManager import com.getcode.navigation.core.CodeNavigator import com.getcode.theme.CodeTheme import kotlinx.coroutines.flow.flowOf @@ -54,6 +55,11 @@ internal fun AppNavigationBar( val selectedTab = navigator.backStack.firstNotNullOfOrNull { (it as? AppRoute)?.asNavBarTab() } val topTab = (navigator.currentRouteKey as? AppRoute)?.asNavBarTab() + // A BottomBar modal (e.g. Add Money) renders in the nav content, above this bar; hide the bar so + // it doesn't draw over the modal. Safe because the bar is a bottom overlay (not a scaffold + // bottomBar), so hiding it doesn't resize the content beneath (see NewAppContent). + val bottomBarMessages by BottomBarManager.messages.collectAsStateWithLifecycle() + // A bill/tip card renders at the app root above everything; hide the bar so it doesn't show // beneath the presented bill. val session = LocalSessionController.current @@ -66,11 +72,8 @@ internal fun AppNavigationBar( .then(modifier), contentAlignment = Alignment.BottomCenter, ) { - // Keep the bar in place for BottomBar modals (e.g. Add Money): the modal's scrim renders over - // it via z-order, and hiding it would collapse the scaffold's bottom slot and re-lay-out the - // tab content beneath (visibly re-fanning the wallet's collapsing card stack). AnimatedVisibility( - visible = topTab != null && !billUp, + visible = topTab != null && bottomBarMessages.isEmpty() && !billUp, enter = slideInVertically { it } + fadeIn(), exit = slideOutVertically { it } + fadeOut(), ) { diff --git a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppContent.kt b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppContent.kt index dd070471e..f13283eb7 100644 --- a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppContent.kt +++ b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppContent.kt @@ -9,9 +9,17 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavKey import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState @@ -30,6 +38,7 @@ import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.results.NavResultStateRegistry import com.getcode.navigation.scenes.ModalBottomSheetSceneStrategy import com.getcode.ui.components.bars.BarManager +import com.getcode.ui.core.measured import com.getcode.ui.theme.CodeScaffold import dev.theolm.rinku.DeepLink @@ -135,16 +144,22 @@ internal fun NewAppContent( onPendingAction: (DeeplinkAction) -> Unit = {}, ) { val hazeState = rememberHazeState() - CodeScaffold( - bottomBar = { - AppNavigationBar(navigator = codeNavigator, hazeState = hazeState) - } - ) { padding -> - CompositionLocalProvider(LocalTabBarPadding provides padding) { - // Mark the nav content as the haze source so the frosted tab bar blurs what scrolls - // beneath it. - Box(modifier = Modifier.hazeSource(hazeState)) { - AppNavHost( + // The tab bar is a bottom OVERLAY, not a scaffold bottomBar: the nav content stays full-height, so + // hiding the bar for a modal (or bill) never resizes it — which otherwise re-fanned the wallet's + // collapsing card stack — and content scrolls edge-to-edge under the frosted bar. Reserve space for + // it via LocalTabBarPadding, latched to the tallest measured height and only on tab homes, so a + // modal-driven hide doesn't shrink the inset. + var tabBarHeight by remember { mutableStateOf(0.dp) } + val onTabHome = (codeNavigator.currentRouteKey as? AppRoute)?.asNavBarTab() != null + val tabBarPadding = if (onTabHome) PaddingValues(bottom = tabBarHeight) else PaddingValues() + + CodeScaffold { _ -> + Box(modifier = Modifier.fillMaxSize()) { + CompositionLocalProvider(LocalTabBarPadding provides tabBarPadding) { + // Mark the nav content as the haze source so the frosted tab bar blurs what scrolls + // beneath it. + Box(modifier = Modifier.hazeSource(hazeState)) { + AppNavHost( navigator = codeNavigator, resultStateRegistry = resultStateRegistry, decorators = listOf( @@ -207,7 +222,18 @@ internal fun NewAppContent( onPendingAction = onPendingAction, ), ) + } } + + // Bottom overlay over the full-height nav content. Latch the tallest height so the + // reserved inset above stays stable when the bar hides for a modal/bill. + AppNavigationBar( + navigator = codeNavigator, + hazeState = hazeState, + modifier = Modifier + .align(Alignment.BottomCenter) + .measured { if (it.height > tabBarHeight) tabBarHeight = it.height }, + ) } } } \ No newline at end of file