Show/hide bottom tab bar while using Voyager in Compose multiplatform

745 views Asked by At

I am using Voyager in my compose multiplatform project and I have a question regarding how we can show/hide the bottom tab bar based on the scrolling direction of a LazyColumn inside HomeTab. I have handled the Scroll listener to provide me the value (show/hide) but I am not sure how to set up listener within App screen and handle data between HomeTab and App.

Please go through my code and let me know if I have any other ways where I can setup or how I can handle this issue with following code. Thanks for any help.


@OptIn(ExperimentalMaterialApi::class)
@Composable
fun App() {
    MoviesTheme {
        TabNavigator(HomeTab) {
            BottomSheetNavigator(
                modifier = Modifier.animateContentSize(),
                sheetShape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp),
                skipHalfExpanded = true
            ) {
                Navigator(Application()) { navigator -> SlideTransition(navigator) }
            }
        }
    }
}

class Application : Screen {

    @Composable
    override fun Content() {

        Scaffold(
            modifier = Modifier,
            scaffoldState = rememberScaffoldState(),
            bottomBar = {
                NavigationBar(tonalElevation = 4.dp) {
                    TabNavigationItemM3(HomeTab)
                    TabNavigationItemM3(FavoritesTab)
                    TabNavigationItemM3(InterestsTab)
                    TabNavigationItemM3(ProfileTab)
                }
            },
        ) {
            CurrentTab()
        }
    }
}


@Composable
private fun RowScope.TabNavigationItemM3(tab: Tab) {
    val tabNavigator = LocalTabNavigator.current

    NavigationBarItem(
        selected = tabNavigator.current.key == tab.key,
        onClick = { tabNavigator.current = tab },
        icon = {
            tab.options.icon?.let { painter ->
                androidx.compose.material3.Icon(
                    painter = painter,
                    contentDescription = tab.options.title
                )
            }
        }
    )
}


object HomeTab : Tab {

    @Composable
    override fun Content() {
        HomeScreen(onScrollDirectionChanged =  {show -> }, onScrollStopped = {})
    }

    override val options: TabOptions
        @Composable
        get() {
            val icon = rememberVectorPainter(Icons.Rounded.Home)

            return remember { TabOptions(index = 0u, title = "Home", icon = icon) }
        }

}



@Composable
fun HomeScreen(
    screenModel: HomeScreenViewModel = koinInject(),
    navigator: Navigator = LocalNavigator.currentOrThrow,
    onScrollDirectionChanged: (Boolean) -> Unit,
    onScrollStopped: () -> Unit = {}
) {
    LaunchedEffect(Unit) { screenModel.onLaunch() }
    val pagingItems =
        screenModel.allMoviesPagingState.collectAsLazyPagingItemsForRecipe(screenModel::onLoadMore)
    val trendingPagingItems =
        screenModel.trendingMoviesPagingState.collectAsLazyPagingItemsForMovies(screenModel::onLoadMoreTrending)
    val listState = rememberLazyListState()
    val previousScrollOffset by rememberUpdatedState(listState.firstVisibleItemScrollOffset)
    var isScrollingDown by remember { mutableStateOf(false) }
    var isScrollingUp by remember { mutableStateOf(false) }

   LaunchedEffect(listState) {
        snapshotFlow { listState.isScrollInProgress }
            .collect {
                if (it) {
                    isScrollingDown = listState.firstVisibleItemScrollOffset > previousScrollOffset
                    isScrollingUp = listState.firstVisibleItemScrollOffset < previousScrollOffset
                    onScrollDirectionChanged(isScrollingUp)
                } else {
                    onScrollStopped()
                }
            }
    }

    if (listState.firstVisibleItemIndex == listState.layoutInfo.totalItemsCount - 1) {
        onScrollDirectionChanged(true)
    }

    Box {
        LazyColumn(
            state = listState,
            horizontalAlignment = Alignment.Start,
            verticalArrangement = Arrangement.Top
        ) {
            trendingMovies(trendingPagingItems, { id -> navigator.push(DetailsScreen(id)) }, {})
            allMovies(pagingItems, { id -> navigator.push(DetailsScreen(id)) }, {})
        }

        MoviesSearchBar { }
    }
}



2

There are 2 answers

1
Sadulla Ubaydullayev On

you can show:

        val bottomSheetNavigator = LocalBottomSheetNavigator.current

        Button(
            onClick = { 
                bottomSheetNavigator.show(BottomSheetScreen())
            }
        ) {
            Text(text = "Show BottomSheet")
        }

and close:

        val bottomSheetNavigator = LocalBottomSheetNavigator.current

        Button(
            onClick = { 
                bottomSheetNavigator.hide()
            }
        ) {
            Text(text = "hide BottomSheet")
        }

you need to add this voyager library:

implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion")

1
Evans Mutwiri On

I was able to get around this challenge myself by using the parent navigator. For example, in your code:

@Composable
fun HomeScreen(
    screenModel: HomeScreenViewModel = koinInject(),
    navigator: Navigator = LocalNavigator.currentOrThrow.parent,
    onScrollDirectionChanged: (Boolean) -> Unit,
    onScrollStopped: () -> Unit = {}
) {
   // rest of your code
}