Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing Tab's navigator through TabNavigator #83

Closed
4nk1r opened this issue Sep 9, 2022 · 4 comments
Closed

Accessing Tab's navigator through TabNavigator #83

4nk1r opened this issue Sep 9, 2022 · 4 comments

Comments

@4nk1r
Copy link

4nk1r commented Sep 9, 2022

I've been using your library for a month or something, and I'm pretty satisfied of it. It's cool!

But there's a thing. I want my app to have the following behaviour: when user clicks on a tab in BottomNavigation and the tab isn't selected yet, the library just switches Tabs, but when the tab is already selected, I want my app to reset navigation state of the tab (i.e. pop navigator to root).

I tried to implement the behaviour myself though, but the problem is that I can't access almost anything from TabNavigator...
image

Did I miss something? Any help will be appreciated. Thanks in advance!

@huyto-rh
Copy link

If you call LocalNavigator.current, it'll actually give you the TabNavigator's Navigator unless you have another nested Navigator call inside of the Tab's content. Not sure if this is what you're asking for.

@4nk1r
Copy link
Author

4nk1r commented Sep 29, 2022

Yes, that is what I'm asking for. But sadly that doesn't work, I guess it's because of Navigator's CompositionLocalProvider placed inside CurrentTab

This code didn't work for me:
image

@Tolriq
Copy link

Tolriq commented Oct 1, 2022

TabNavigator is a very small module you can build your own easily :)

Here's a version that allows what you want. (You probably have to tweak things as it's for my needs)

import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.SaveableStateHolder
import androidx.compose.runtime.staticCompositionLocalOf
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

typealias TabNavigatorContent = @Composable (tabNavigator: TabNavigator) -> Unit

val LocalTabNavigator: ProvidableCompositionLocal<TabNavigator> =
    staticCompositionLocalOf { error("TabNavigator not initialized") }

@Composable
fun TabNavigator(
    tab: Tab,
    backToFirst: Boolean = false,
    content: TabNavigatorContent = { CurrentTab() }
) {
    Navigator(tab, disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = false), onBackPressed = null) { navigator ->
        val scope = rememberCoroutineScope()
        val tabNavigator = remember(navigator) {
            TabNavigator(navigator, scope)
        }

        CompositionLocalProvider(LocalTabNavigator provides tabNavigator) {
            if (backToFirst) {
                BackHandler(
                    enabled = tabNavigator.current != tab,
                    onBack = { tabNavigator.current = tab }
                )
            }
            content(tabNavigator)
        }
    }
}

private val tabReselectHandlers = mutableListOf<suspend () -> Boolean>()

class TabNavigator internal constructor(
    private val navigator: Navigator,
    private val coroutineScope: CoroutineScope,
    val stateHolder: SaveableStateHolder = navigator.stateHolder
) {

    var current: Tab
        get() = navigator.lastItem as Tab
        set(tab) {
            if (navigator.lastItem == tab) {
                coroutineScope.launch {
                    tabReselectHandlers.reversed().firstOrNull { it.invoke() }
                }
            } else {
                navigator.replaceAll(tab)
            }
        }
}

@Composable
fun TabReselectHandler(handler: suspend () -> Boolean) {
    DisposableEffect(Unit) {
        tabReselectHandlers.add(handler)
        onDispose { tabReselectHandlers.remove(handler) }
    }
}

You can then do what you want easily for example I do in deep screen to first allow scroll to top

    TabReselectHandler {
        if (lazyGridState.firstVisibleItemIndex > 0) {
            lazyGridState.scrollToItem(0)
            return@TabReselectHandler true
        }
        false
    }

And in the root tabs

        TabReselectHandler {
            activeNavigator?.let { childNavigator ->
                if (childNavigator.stackSize > 1) {
                    childNavigator.goRoot()
                    return@TabReselectHandler true
                }
            }
            false
        }

@4nk1r
Copy link
Author

4nk1r commented Oct 14, 2022

The message above solves the problem (thanks you Tolriq!)

@4nk1r 4nk1r closed this as completed Oct 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants