123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package fr.forum_thalie.tsumugi
-
-
- import android.content.Intent
- import android.util.SparseArray
- import androidx.core.util.forEach
- import androidx.core.util.set
- import androidx.fragment.app.FragmentManager
- import androidx.lifecycle.LiveData
- import androidx.lifecycle.MutableLiveData
- import androidx.navigation.NavController
- import androidx.navigation.fragment.NavHostFragment
- import com.google.android.material.bottomnavigation.BottomNavigationView
-
- /**
- * YATTOZ' NOTE : this file has been scavenged from Android's "architecture-components-samples" repo.
- * See it here:
- * https://github.com/android/architecture-components-samples/blob/master/NavigationAdvancedSample/app/src/main/java/com/example/android/navigationadvancedsample/NavigationExtensions.kt
- * it allows, among other things, to keep and restore fragments' state when they're swapped. Useful for IRC.
- */
-
-
- /**
- * Manages the various graphs needed for a [BottomNavigationView].
- *
- * This sample is a workaround until the Navigation Component supports multiple back stacks.
- */
- fun BottomNavigationView.setupWithNavController(
- navGraphIds: List<Int>,
- fragmentManager: FragmentManager,
- containerId: Int,
- intent: Intent
- ): LiveData<NavController> {
-
- // Map of tags
- val graphIdToTagMap = SparseArray<String>()
- // Result. Mutable live data with the selected controlled
- val selectedNavController = MutableLiveData<NavController>()
-
- var firstFragmentGraphId = 0
-
- // First create a NavHostFragment for each NavGraph ID
- navGraphIds.forEachIndexed { index, navGraphId ->
- val fragmentTag = getFragmentTag(index)
-
- // Find or create the Navigation host fragment
- val navHostFragment = obtainNavHostFragment(
- fragmentManager,
- fragmentTag,
- navGraphId,
- containerId
- )
-
- // Obtain its id
- val graphId = navHostFragment.navController.graph.id
-
- if (index == 0) {
- firstFragmentGraphId = graphId
- }
-
- // Save to the map
- graphIdToTagMap[graphId] = fragmentTag
-
- // Attach or detach nav host fragment depending on whether it's the selected item.
- if (this.selectedItemId == graphId) {
- // Update livedata with the selected graph
- selectedNavController.value = navHostFragment.navController
- attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
- } else {
- detachNavHostFragment(fragmentManager, navHostFragment)
- }
- }
-
- // Now connect selecting an item with swapping Fragments
- var selectedItemTag = graphIdToTagMap[this.selectedItemId]
- val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
- var isOnFirstFragment = selectedItemTag == firstFragmentTag
-
- // When a navigation item is selected
- setOnNavigationItemSelectedListener { item ->
- // Don't do anything if the state is state has already been saved.
- if (fragmentManager.isStateSaved) {
- false
- } else {
- val newlySelectedItemTag = graphIdToTagMap[item.itemId]
- if (selectedItemTag != newlySelectedItemTag) {
- // Pop everything above the first fragment (the "fixed start destination")
- fragmentManager.popBackStack(firstFragmentTag,
- FragmentManager.POP_BACK_STACK_INCLUSIVE)
- val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
- as NavHostFragment
-
- // Exclude the first fragment tag because it's always in the back stack.
- if (firstFragmentTag != newlySelectedItemTag) {
- // Commit a transaction that cleans the back stack and adds the first fragment
- // to it, creating the fixed started destination.
- fragmentManager.beginTransaction()
- /* // YATTOZ' NOTE - disabling animations, it feels snappier and more in place.
- .setCustomAnimations(
- R.anim.nav_default_enter_anim,
- R.anim.nav_default_exit_anim,
- R.anim.nav_default_pop_enter_anim,
- R.anim.nav_default_pop_exit_anim)
- */
- .attach(selectedFragment)
- .setPrimaryNavigationFragment(selectedFragment)
- .apply {
- // Detach all other Fragments
- graphIdToTagMap.forEach { _, fragmentTagIter ->
- if (fragmentTagIter != newlySelectedItemTag) {
- detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
- }
- }
- }
- .addToBackStack(firstFragmentTag)
- .setReorderingAllowed(true)
- .commit()
- }
- selectedItemTag = newlySelectedItemTag
- isOnFirstFragment = selectedItemTag == firstFragmentTag
- selectedNavController.value = selectedFragment.navController
- true
- } else {
- false
- }
- }
- }
-
- // Optional: on item reselected, pop back stack to the destination of the graph
- setupItemReselected(graphIdToTagMap, fragmentManager)
-
- // Handle deep link
- setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
-
- // Finally, ensure that we update our BottomNavigationView when the back stack changes
- fragmentManager.addOnBackStackChangedListener {
- if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
- this.selectedItemId = firstFragmentGraphId
- }
-
- // Reset the graph if the currentDestination is not valid (happens when the back
- // stack is popped after using the back button).
- selectedNavController.value?.let { controller ->
- if (controller.currentDestination == null) {
- controller.navigate(controller.graph.id)
- }
- }
- }
- return selectedNavController
- }
-
- private fun BottomNavigationView.setupDeepLinks(
- navGraphIds: List<Int>,
- fragmentManager: FragmentManager,
- containerId: Int,
- intent: Intent
- ) {
- navGraphIds.forEachIndexed { index, navGraphId ->
- val fragmentTag = getFragmentTag(index)
-
- // Find or create the Navigation host fragment
- val navHostFragment = obtainNavHostFragment(
- fragmentManager,
- fragmentTag,
- navGraphId,
- containerId
- )
- // Handle Intent
- if (navHostFragment.navController.handleDeepLink(intent)
- && selectedItemId != navHostFragment.navController.graph.id) {
- this.selectedItemId = navHostFragment.navController.graph.id
- }
- }
- }
-
- private fun BottomNavigationView.setupItemReselected(
- graphIdToTagMap: SparseArray<String>,
- fragmentManager: FragmentManager
- ) {
- setOnNavigationItemReselectedListener { item ->
- val newlySelectedItemTag = graphIdToTagMap[item.itemId]
- val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
- as NavHostFragment
- val navController = selectedFragment.navController
- // Pop the back stack to the start destination of the current navController graph
- navController.popBackStack(
- navController.graph.startDestination, false
- )
- }
- }
-
- private fun detachNavHostFragment(
- fragmentManager: FragmentManager,
- navHostFragment: NavHostFragment
- ) {
- fragmentManager.beginTransaction()
- .detach(navHostFragment)
- .commitNow()
- }
-
- private fun attachNavHostFragment(
- fragmentManager: FragmentManager,
- navHostFragment: NavHostFragment,
- isPrimaryNavFragment: Boolean
- ) {
- fragmentManager.beginTransaction()
- .attach(navHostFragment)
- .apply {
- if (isPrimaryNavFragment) {
- setPrimaryNavigationFragment(navHostFragment)
- }
- }
- .commitNow()
-
- }
-
- private fun obtainNavHostFragment(
- fragmentManager: FragmentManager,
- fragmentTag: String,
- navGraphId: Int,
- containerId: Int
- ): NavHostFragment {
- // If the Nav Host fragment exists, return it
- val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
- existingFragment?.let { return it }
-
- // Otherwise, create it and return it.
- val navHostFragment = NavHostFragment.create(navGraphId)
- fragmentManager.beginTransaction()
- .add(containerId, navHostFragment, fragmentTag)
- .commitNow()
- return navHostFragment
- }
-
- private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
- val backStackCount = backStackEntryCount
- for (index in 0 until backStackCount) {
- if (getBackStackEntryAt(index).name == backStackName) {
- return true
- }
- }
- return false
- }
-
- private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
|