Created the KiwixNavGraph for Compose-based navigation.

This commit is contained in:
MohitMaliFtechiz 2025-07-19 00:54:14 +05:30
parent 095317e102
commit 4e56c2c781
10 changed files with 426 additions and 327 deletions

View File

@ -24,22 +24,19 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.compose.runtime.mutableStateOf
import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.navigation.NavigationView
import eu.mhutti1.utils.storage.StorageDevice import eu.mhutti1.utils.storage.StorageDevice
import eu.mhutti1.utils.storage.StorageDeviceUtils import eu.mhutti1.utils.storage.StorageDeviceUtils
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -53,9 +50,6 @@ import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.extensions.applyEdgeToEdgeInsets
import org.kiwix.kiwixmobile.core.extensions.getDialogHostComposeView
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
@ -63,9 +57,7 @@ import org.kiwix.kiwixmobile.core.main.NEW_TAB_SHORTCUT_ID
import org.kiwix.kiwixmobile.core.main.ZIM_FILE_URI_KEY import org.kiwix.kiwixmobile.core.main.ZIM_FILE_URI_KEY
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.databinding.ActivityKiwixMainBinding
import org.kiwix.kiwixmobile.kiwixActivityComponent import org.kiwix.kiwixmobile.kiwixActivityComponent
import org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragmentDirections
import javax.inject.Inject import javax.inject.Inject
const val ACTION_GET_CONTENT = "GET_CONTENT" const val ACTION_GET_CONTENT = "GET_CONTENT"
@ -77,27 +69,23 @@ class KiwixMainActivity : CoreMainActivity() {
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
override val cachedComponent by lazy { kiwixActivityComponent } override val cachedComponent by lazy { kiwixActivityComponent }
override val searchFragmentResId: Int = R.id.searchFragment override val searchFragmentResId: Int = R.id.searchFragment
override val navController by lazy {
(
supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
as NavHostFragment
).navController
}
override val drawerContainerLayout: DrawerLayout by lazy { // override val drawerContainerLayout: DrawerLayout by lazy {
activityKiwixMainBinding.navigationContainer // // activityKiwixMainBinding.navigationContainer
} // }
override val drawerNavView: NavigationView by lazy { // override val drawerNavView: NavigationView by lazy {
activityKiwixMainBinding.drawerNavView // activityKiwixMainBinding.drawerNavView
} // }
override val readerTableOfContentsDrawer: NavigationView by lazy { // override val readerTableOfContentsDrawer: NavigationView by lazy {
activityKiwixMainBinding.readerDrawerNavView // activityKiwixMainBinding.readerDrawerNavView
} // }
override val navHostContainer by lazy { override val navController: NavController by lazy {
activityKiwixMainBinding.navHostFragment val fragment = supportFragmentManager.findFragmentById(id.nav_host_fragment)
val navHostFragment = requireNotNull(fragment) as NavHostFragment
return@lazy navHostFragment.navController
} }
@Inject lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk @Inject lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
@ -113,8 +101,9 @@ class KiwixMainActivity : CoreMainActivity() {
override val helpFragmentResId: Int = R.id.helpFragment override val helpFragmentResId: Int = R.id.helpFragment
override val topLevelDestinations = override val topLevelDestinations =
setOf(R.id.downloadsFragment, R.id.libraryFragment, R.id.readerFragment) setOf(R.id.downloadsFragment, R.id.libraryFragment, R.id.readerFragment)
private val isBottomBarVisible = mutableStateOf(true)
private lateinit var activityKiwixMainBinding: ActivityKiwixMainBinding // private lateinit var activityKiwixMainBinding: ActivityKiwixMainBinding
private var isIntroScreenVisible: Boolean = false private var isIntroScreenVisible: Boolean = false
@ -127,25 +116,31 @@ class KiwixMainActivity : CoreMainActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
cachedComponent.inject(this) cachedComponent.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
activityKiwixMainBinding = ActivityKiwixMainBinding.inflate(layoutInflater) setContent {
setContentView(activityKiwixMainBinding.root) KiwixMainActivityScreen(
navController = navController,
navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange) topLevelDestinations = topLevelDestinations.toList(),
activityKiwixMainBinding.drawerNavView.apply { isBottomBarVisible = isBottomBarVisible.value,
setupWithNavController(navController) leftDrawerContent = { },
setNavigationItemSelectedListener { item -> rightDrawerContent = { }
closeNavigationDrawer() )
onNavigationItemSelected(item)
}
} }
activityKiwixMainBinding.bottomNavView.setupWithNavController(navController) navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange)
// activityKiwixMainBinding.drawerNavView.apply {
// setupWithNavController(navController)
// setNavigationItemSelectedListener { item ->
// closeNavigationDrawer()
// onNavigationItemSelected(item)
// }
// }
// activityKiwixMainBinding.bottomNavView.setupWithNavController(navController)
lifecycleScope.launch { lifecycleScope.launch {
migrateInternalToPublicAppDirectory() migrateInternalToPublicAppDirectory()
} }
handleZimFileIntent(intent) handleZimFileIntent(intent)
handleNotificationIntent(intent) handleNotificationIntent(intent)
handleGetContentIntent(intent) handleGetContentIntent(intent)
activityKiwixMainBinding.root.applyEdgeToEdgeInsets() // activityKiwixMainBinding.root.applyEdgeToEdgeInsets()
} }
private suspend fun migrateInternalToPublicAppDirectory() { private suspend fun migrateInternalToPublicAppDirectory() {
@ -180,46 +175,46 @@ class KiwixMainActivity : CoreMainActivity() {
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
if (::activityKiwixMainBinding.isInitialized) { // if (::activityKiwixMainBinding.isInitialized) {
activityKiwixMainBinding.bottomNavView.menu.apply { // activityKiwixMainBinding.bottomNavView.menu.apply {
findItem(R.id.readerFragment)?.title = resources.getString(string.reader) // findItem(R.id.readerFragment)?.title = resources.getString(string.reader)
findItem(R.id.libraryFragment)?.title = resources.getString(string.library) // findItem(R.id.libraryFragment)?.title = resources.getString(string.library)
findItem(R.id.downloadsFragment)?.title = resources.getString(string.download) // findItem(R.id.downloadsFragment)?.title = resources.getString(string.download)
} // }
activityKiwixMainBinding.drawerNavView.menu.apply { // activityKiwixMainBinding.drawerNavView.menu.apply {
findItem(org.kiwix.kiwixmobile.core.R.id.menu_bookmarks_list)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_bookmarks_list)?.title =
resources.getString(string.bookmarks) // resources.getString(string.bookmarks)
findItem(org.kiwix.kiwixmobile.core.R.id.menu_history)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_history)?.title =
resources.getString(string.history) // resources.getString(string.history)
findItem(org.kiwix.kiwixmobile.core.R.id.menu_notes)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_notes)?.title =
resources.getString(string.pref_notes) // resources.getString(string.pref_notes)
findItem(org.kiwix.kiwixmobile.core.R.id.menu_host_books)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_host_books)?.title =
resources.getString(string.menu_wifi_hotspot) // resources.getString(string.menu_wifi_hotspot)
findItem(org.kiwix.kiwixmobile.core.R.id.menu_settings)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_settings)?.title =
resources.getString(string.menu_settings) // resources.getString(string.menu_settings)
findItem(org.kiwix.kiwixmobile.core.R.id.menu_help)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_help)?.title =
resources.getString(string.menu_help) // resources.getString(string.menu_help)
findItem(org.kiwix.kiwixmobile.core.R.id.menu_support_kiwix)?.title = // findItem(org.kiwix.kiwixmobile.core.R.id.menu_support_kiwix)?.title =
resources.getString(string.menu_support_kiwix) // resources.getString(string.menu_support_kiwix)
} // }
} // }
} }
override fun configureActivityBasedOn(destination: NavDestination) { override fun configureActivityBasedOn(destination: NavDestination) {
super.configureActivityBasedOn(destination) super.configureActivityBasedOn(destination)
activityKiwixMainBinding.bottomNavView.isVisible = destination.id in topLevelDestinations isBottomBarVisible.value = destination.id in topLevelDestinations
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
navController.addOnDestinationChangedListener { _, destination, _ -> // navController.addOnDestinationChangedListener { _, destination, _ ->
activityKiwixMainBinding.bottomNavView.isVisible = destination.id in topLevelDestinations // isBottomBarVisible.value = destination.id in topLevelDestinations
if (destination.id !in topLevelDestinations) { // if (destination.id !in topLevelDestinations) {
handleDrawerOnNavigation() // handleDrawerOnNavigation()
} // }
} // }
if (sharedPreferenceUtil.showIntro() && !isIntroScreenNotVisible()) { if (sharedPreferenceUtil.showIntro() && !isIntroScreenNotVisible()) {
navigate(KiwixReaderFragmentDirections.actionReaderFragmentToIntroFragment()) // navigate(KiwixReaderFragmentDirections.actionReaderFragmentToIntroFragment())
} }
if (!sharedPreferenceUtil.prefIsTest) { if (!sharedPreferenceUtil.prefIsTest) {
sharedPreferenceUtil.setIsPlayStoreBuildType(BuildConfig.IS_PLAYSTORE) sharedPreferenceUtil.setIsPlayStoreBuildType(BuildConfig.IS_PLAYSTORE)
@ -234,16 +229,16 @@ class KiwixMainActivity : CoreMainActivity() {
* TODO Remove this once we migrate to compose. * TODO Remove this once we migrate to compose.
*/ */
override fun toggleBottomNavigation(isVisible: Boolean) { override fun toggleBottomNavigation(isVisible: Boolean) {
activityKiwixMainBinding.bottomNavView.animate() // activityKiwixMainBinding.bottomNavView.animate()
?.translationY( // ?.translationY(
if (isVisible) { // if (isVisible) {
ZERO.toFloat() // ZERO.toFloat()
} else { // } else {
activityKiwixMainBinding.bottomNavView.height.toFloat() // activityKiwixMainBinding.bottomNavView.height.toFloat()
} // }
) // )
?.setDuration(KIWIX_BOTTOM_BAR_ANIMATION_DURATION) // ?.setDuration(KIWIX_BOTTOM_BAR_ANIMATION_DURATION)
?.start() // ?.start()
} }
private fun setDefaultDeviceLanguage() { private fun setDefaultDeviceLanguage() {
@ -283,9 +278,9 @@ class KiwixMainActivity : CoreMainActivity() {
private fun handleGetContentIntent(intent: Intent?) { private fun handleGetContentIntent(intent: Intent?) {
if (intent?.action == ACTION_GET_CONTENT) { if (intent?.action == ACTION_GET_CONTENT) {
activityKiwixMainBinding.bottomNavView.menu.findItem(R.id.downloadsFragment)?.let { // activityKiwixMainBinding.bottomNavView.menu.findItem(R.id.downloadsFragment)?.let {
NavigationUI.onNavDestinationSelected(it, navController) // NavigationUI.onNavDestinationSelected(it, navController)
} // }
} }
} }
@ -356,7 +351,7 @@ class KiwixMainActivity : CoreMainActivity() {
} }
override fun setDialogHostToActivity(alertDialogShower: AlertDialogShower) { override fun setDialogHostToActivity(alertDialogShower: AlertDialogShower) {
activityKiwixMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0) // activityKiwixMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0)
} }
// Outdated shortcut ids(new_tab, get_content) // Outdated shortcut ids(new_tab, get_content)

View File

@ -18,34 +18,120 @@
package org.kiwix.kiwixmobile.main package org.kiwix.kiwixmobile.main
import androidx.compose.animation.AnimatedVisibility import android.view.ViewGroup
import androidx.compose.animation.slideInHorizontally import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.BottomAppBarScrollBehavior import androidx.compose.material3.BottomAppBarScrollBehavior
import androidx.compose.material3.DrawerValue import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager import androidx.compose.ui.viewinterop.AndroidView
import androidx.fragment.app.FragmentContainerView
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.fragment.NavHostFragment
import org.kiwix.kiwixmobile.R.drawable import org.kiwix.kiwixmobile.R.drawable
import org.kiwix.kiwixmobile.R.id import org.kiwix.kiwixmobile.R.id
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.R.navigation
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.NAVIGATION_DRAWER_WIDTH
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun KiwixMainActivityScreen(
navController: NavController,
topLevelDestinations: List<Int>,
leftDrawerContent: @Composable ColumnScope.() -> Unit,
rightDrawerContent: @Composable ColumnScope.() -> Unit,
isBottomBarVisible: Boolean = true
) {
val rightDrawerState = rememberDrawerState(DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val scrollingBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
val context = LocalContext.current
val fragmentManager = (context as AppCompatActivity).supportFragmentManager
KiwixTheme {
ModalNavigationDrawer(
drawerContent = {
Column(
Modifier
.fillMaxHeight()
.width(NAVIGATION_DRAWER_WIDTH)
) {
leftDrawerContent()
}
},
gesturesEnabled = true
) {
Box {
Scaffold(
bottomBar = {
if (isBottomBarVisible) {
BottomNavigationBar(
navController = navController,
scrollBehavior = scrollingBehavior,
topLevelDestinations = topLevelDestinations
)
}
}
) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues)) {
// AndroidView to host your FragmentContainerView with nav graph
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
FragmentContainerView(ctx).apply {
id = R.id.nav_host_fragment
if (fragmentManager.findFragmentById(id) == null) {
val navHostFragment = NavHostFragment.create(navigation.kiwix_nav_graph)
fragmentManager.beginTransaction()
.replace(id, navHostFragment)
.setPrimaryNavigationFragment(navHostFragment)
.commitNow()
}
}
}
)
}
}
// Right drawer overlay
ModalDrawerSheet(
drawerState = rightDrawerState,
modifier = Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd)
.width(NAVIGATION_DRAWER_WIDTH)
) {
rightDrawerContent()
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -93,64 +179,3 @@ fun BottomNavigationBar(
} }
} }
} }
@Composable
fun MainNavGraph(
fragmentManager: FragmentManager,
navGraphId: Int
) {
val navController = remember {
fragmentManager.findNavController(R.id.nav_host_fragment)
}
// Drawer states
val leftDrawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val rightDrawerVisible = remember { mutableStateOf(false) }
// Bottom nav destinations
val bottomNavDestinations = listOf(
id.readerFragment,
id.libraryFragment,
id.downloadsFragment
)
// Observe current destination
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestinationId = navBackStackEntry?.destination?.id
// Coroutine scope for drawer
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = leftDrawerState,
drawerContent = { DrawerContentLeft() }
) {
Box(modifier = Modifier.fillMaxSize()) {
// Fragment content
FragmentContainer(
fragmentManager = fragmentManager,
containerId = R.id.nav_host_fragment,
navGraphId = navGraphId
)
// Right drawer (slide in)
AnimatedVisibility(
visible = rightDrawerVisible.value,
enter = slideInHorizontally(initialOffsetX = { it }),
exit = slideOutHorizontally(targetOffsetX = { it }),
modifier = Modifier.align(Alignment.CenterEnd)
) {
DrawerContentRight(
onClose = { rightDrawerVisible.value = false }
)
}
// Bottom nav only on selected destinations
if (currentDestinationId in bottomNavDestinations) {
BottomNavigationBar(
navController = navController,
)
}
}
}
}

View File

@ -288,8 +288,8 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
zimManageViewModel.setAlertDialogShower(dialogShower as AlertDialogShower) zimManageViewModel.setAlertDialogShower(dialogShower as AlertDialogShower)
zimManageViewModel.fileSelectListStates.observe(viewLifecycleOwner, Observer(::render)) zimManageViewModel.fileSelectListStates.observe(viewLifecycleOwner, Observer(::render))
.also { .also {
coreMainActivity.navHostContainer // coreMainActivity.navHostContainer
.setBottomMarginToFragmentContainerView(0) // .setBottomMarginToFragmentContainerView(0)
} }
coroutineJobs.apply { coroutineJobs.apply {
add(sideEffects()) add(sideEffects())
@ -363,10 +363,11 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
} }
} }
private fun getBottomNavigationView() = // private fun getBottomNavigationView() =
requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view) // requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view)
private fun getBottomNavigationHeight() = getBottomNavigationView().measuredHeight private fun getBottomNavigationHeight() = ZERO
// getBottomNavigationView().measuredHeight
private fun filePickerButtonClick() { private fun filePickerButtonClick() {
if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) { if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) {
@ -560,10 +561,10 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
} }
private fun animateBottomViewToOrigin() { private fun animateBottomViewToOrigin() {
getBottomNavigationView().animate() // getBottomNavigationView().animate()
.translationY(0F) // .translationY(0F)
.setDuration(MATERIAL_BOTTOM_VIEW_ENTER_ANIMATION_DURATION) // .setDuration(MATERIAL_BOTTOM_VIEW_ENTER_ANIMATION_DURATION)
.start() // .start()
} }
private fun render(state: FileSelectListState) { private fun render(state: FileSelectListState) {

View File

@ -44,7 +44,6 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.tonyodev.fetch2.Status import com.tonyodev.fetch2.Status
import eu.mhutti1.utils.storage.StorageDevice import eu.mhutti1.utils.storage.StorageDevice
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -212,10 +211,11 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
composeView = it composeView = it
} }
private fun getBottomNavigationView() = // private fun getBottomNavigationView() =
requireActivity().findViewById<BottomNavigationView>(org.kiwix.kiwixmobile.R.id.bottom_nav_view) // requireActivity().findViewById<BottomNavigationView>(org.kiwix.kiwixmobile.R.id.bottom_nav_view)
private fun getBottomNavigationHeight() = getBottomNavigationView().measuredHeight private fun getBottomNavigationHeight() = ZERO
// getBottomNavigationView().measuredHeight
private fun onPauseResumeButtonClick(item: LibraryListItem.LibraryDownloadItem) { private fun onPauseResumeButtonClick(item: LibraryListItem.LibraryDownloadItem) {
context?.let { context -> context?.let { context ->
@ -298,8 +298,8 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
.onEach { onLibraryItemsChange(it) } .onEach { onLibraryItemsChange(it) }
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)
.also { .also {
coreMainActivity.navHostContainer // coreMainActivity.navHostContainer
.setBottomMarginToFragmentContainerView(0) // .setBottomMarginToFragmentContainerView(0)
} }
// Observe when online library downloading. // Observe when online library downloading.
onlineLibraryDownloading onlineLibraryDownloading

View File

@ -151,8 +151,8 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
override fun loadDrawerViews() { override fun loadDrawerViews() {
drawerLayout = requireActivity().findViewById(R.id.navigation_container) // drawerLayout = requireActivity().findViewById(R.id.navigation_container)
tableDrawerRightContainer = requireActivity().findViewById(R.id.reader_drawer_nav_view) // tableDrawerRightContainer = requireActivity().findViewById(R.id.reader_drawer_nav_view)
} }
override fun openHomeScreen() { override fun openHomeScreen() {
@ -199,13 +199,13 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
private fun setFragmentContainerBottomMarginToSizeOfNavBar() { private fun setFragmentContainerBottomMarginToSizeOfNavBar() {
val bottomNavigationView = // val bottomNavigationView =
requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view) // requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view)
bottomNavigationView?.let { // bottomNavigationView?.let {
setBottomMarginToNavHostContainer( // setBottomMarginToNavHostContainer(
bottomNavigationView.measuredHeight // bottomNavigationView.measuredHeight
) // )
} // }
} }
// override fun onPause() { // override fun onPause() {
@ -240,8 +240,8 @@ class KiwixReaderFragment : CoreReaderFragment() {
exitBook() exitBook()
} }
override fun getBottomNavigationView(): BottomNavigationView? = override fun getBottomNavigationView(): BottomNavigationView? = null
requireActivity().findViewById(R.id.bottom_nav_view) // requireActivity().findViewById(R.id.bottom_nav_view)
/** /**
* Restores the view state based on the provided webViewHistoryItemList data and restore origin. * Restores the view state based on the provided webViewHistoryItemList data and restore origin.
@ -299,11 +299,11 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun updateNavigationBarHeight(toolbarOffset: Float) { override fun updateNavigationBarHeight(toolbarOffset: Float) {
// if no activity exist simply return. // if no activity exist simply return.
if (activity == null) return if (activity == null) return
activity?.findViewById<BottomNavigationView>(R.id.bottom_nav_view)?.let { view -> // activity?.findViewById<BottomNavigationView>(R.id.bottom_nav_view)?.let { view ->
val toolbarHeightPx = activity?.getToolbarHeight() ?: 0f // val toolbarHeightPx = activity?.getToolbarHeight() ?: 0f
val offsetFactor = view.height / toolbarHeightPx.toFloat() // val offsetFactor = view.height / toolbarHeightPx.toFloat()
view.translationY = -1 * toolbarOffset * offsetFactor // view.translationY = -1 * toolbarOffset * offsetFactor
} // }
} }
override fun onFullscreenVideoToggled(isFullScreen: Boolean) { override fun onFullscreenVideoToggled(isFullScreen: Boolean) {
@ -328,15 +328,15 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
private fun hideNavBar() { private fun hideNavBar() {
requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility = GONE // requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility = GONE
setBottomMarginToNavHostContainer(0) setBottomMarginToNavHostContainer(0)
} }
private fun showNavBar() { private fun showNavBar() {
// show the navBar if fullScreenMode is not active. // show the navBar if fullScreenMode is not active.
if (!isInFullScreenMode()) { if (!isInFullScreenMode()) {
requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility = // requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility =
VISIBLE // VISIBLE
} }
} }
@ -345,7 +345,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
private fun setBottomMarginToNavHostContainer(margin: Int) { private fun setBottomMarginToNavHostContainer(margin: Int) {
coreMainActivity.navHostContainer // coreMainActivity.navHostContainer
.setBottomMarginToFragmentContainerView(margin) // .setBottomMarginToFragmentContainerView(margin)
} }
} }

View File

@ -0,0 +1,170 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.ui
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.doOnAttach
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.commit
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import org.kiwix.kiwixmobile.core.page.bookmark.BookmarksFragment
import org.kiwix.kiwixmobile.core.page.history.HistoryFragment
import org.kiwix.kiwixmobile.core.page.notes.NotesFragment
import org.kiwix.kiwixmobile.core.search.SearchFragment
import org.kiwix.kiwixmobile.help.KiwixHelpFragment
import org.kiwix.kiwixmobile.intro.IntroFragment
import org.kiwix.kiwixmobile.language.LanguageFragment
import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferFragment
import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragment
import org.kiwix.kiwixmobile.nav.destination.library.online.OnlineLibraryFragment
import org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragment
import org.kiwix.kiwixmobile.settings.KiwixSettingsFragment
import org.kiwix.kiwixmobile.webserver.ZimHostFragment
@Composable
fun KiwixNavGraph(
navController: NavHostController,
modifier: Modifier = Modifier
) {
NavHost(
navController = navController,
startDestination = "readerFragment",
modifier = modifier
) {
composable("readerFragment") {
FragmentContainer {
KiwixReaderFragment().apply {
arguments = Bundle().apply {
putString("zimFileUri", "")
putString("findInPageSearchString", "")
putString("pageUrl", "")
putBoolean("shouldOpenInNewTab", false)
putString("searchItemTitle", "")
}
}
}
}
composable("libraryFragment") {
FragmentContainer {
LocalLibraryFragment().apply {
arguments = Bundle().apply {
putString("zimFileUri", "")
}
}
}
}
composable("downloadsFragment") {
FragmentContainer {
OnlineLibraryFragment()
}
}
composable("bookmarksFragment") {
FragmentContainer {
BookmarksFragment()
}
}
composable("notesFragment") {
FragmentContainer {
NotesFragment()
}
}
composable("introFragment") {
FragmentContainer {
IntroFragment()
}
}
composable("historyFragment") {
FragmentContainer {
HistoryFragment()
}
}
composable("languageFragment") {
FragmentContainer {
LanguageFragment()
}
}
composable("zimHostFragment") {
FragmentContainer {
ZimHostFragment()
}
}
composable("helpFragment") {
FragmentContainer {
KiwixHelpFragment()
}
}
composable("kiwixSettingsFragment") {
FragmentContainer {
KiwixSettingsFragment()
}
}
composable("searchFragment") {
FragmentContainer {
SearchFragment()
}
}
composable("localFileTransferFragment") {
FragmentContainer {
LocalFileTransferFragment().apply {
arguments = Bundle().apply {
putParcelableArray("uris", null)
}
}
}
}
}
}
@Composable
fun FragmentContainer(
fragmentProvider: () -> Fragment
) {
val context = LocalContext.current
val fragmentManager = remember {
(context as AppCompatActivity).supportFragmentManager
}
val viewId = remember { View.generateViewId() }
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
FragmentContainerView(ctx).apply {
id = viewId
doOnAttach {
fragmentManager.commit {
if (fragmentManager.findFragmentById(viewId) == null) {
add(viewId, fragmentProvider())
}
}
}
}
}
)
}

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 Kiwix <android.kiwix.org>
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
~
-->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/open_drawer">
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="org.kiwix.kiwixmobile.core.main.NavigationHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="org.kiwix.kiwixmobile.nav.helper.ScrollingViewWithBottomNavigationBehavior"
app:navGraph="@navigation/kiwix_nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
app:layout_scrollFlags="scroll|enterAlways"
app:menu="@menu/menu_bottom_nav" />
</org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/drawer_nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_main"
app:menu="@menu/menu_drawer_main" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/reader_drawer_nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
app:headerLayout="@layout/drawer_right" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2022 Kiwix <android.kiwix.org>
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
~
-->
<resources>
<declare-styleable name="CustomPageIndicator">
<attr name="ipi_dotDiameter" format="dimension" />
<attr name="ipi_dotGap" format="dimension" />
<attr name="ipi_animationDuration" format="integer" />
<attr name="ipi_pageIndicatorColor" format="color" />
<attr name="ipi_currentPageIndicatorColor" format="color" />
</declare-styleable>
</resources>

View File

@ -91,9 +91,10 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
@Inject lateinit var zimReaderContainer: ZimReaderContainer @Inject lateinit var zimReaderContainer: ZimReaderContainer
abstract val navController: NavController abstract val navController: NavController
abstract val drawerContainerLayout: DrawerLayout
abstract val drawerNavView: NavigationView // abstract val drawerContainerLayout: DrawerLayout
abstract val readerTableOfContentsDrawer: NavigationView // abstract val drawerNavView: NavigationView
// abstract val readerTableOfContentsDrawer: NavigationView
abstract val bookmarksFragmentResId: Int abstract val bookmarksFragmentResId: Int
abstract val settingsFragmentResId: Int abstract val settingsFragmentResId: Int
abstract val historyFragmentResId: Int abstract val historyFragmentResId: Int
@ -101,7 +102,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
abstract val helpFragmentResId: Int abstract val helpFragmentResId: Int
abstract val cachedComponent: CoreActivityComponent abstract val cachedComponent: CoreActivityComponent
abstract val topLevelDestinations: Set<Int> abstract val topLevelDestinations: Set<Int>
abstract val navHostContainer: FragmentContainerView // abstract val navHostContainer: FragmentContainerView
abstract val mainActivity: AppCompatActivity abstract val mainActivity: AppCompatActivity
abstract val appName: String abstract val appName: String
@ -114,7 +115,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
@Suppress("InjectDispatcher") @Suppress("InjectDispatcher")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.KiwixTheme) // setTheme(R.style.KiwixTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
val appContext = applicationContext val appContext = applicationContext
@ -160,9 +161,9 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
downloadMonitor.startMonitoringDownload() downloadMonitor.startMonitoringDownload()
stopDownloadServiceIfRunning() stopDownloadServiceIfRunning()
rateDialogHandler.checkForRateDialog(getIconResId()) rateDialogHandler.checkForRateDialog(getIconResId())
navController.addOnDestinationChangedListener { _, destination, _ -> // navController.addOnDestinationChangedListener { _, destination, _ ->
configureActivityBasedOn(destination) // configureActivityBasedOn(destination)
} // }
} }
/** /**
@ -206,17 +207,17 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
if (destination.id !in topLevelDestinations) { if (destination.id !in topLevelDestinations) {
handleDrawerOnNavigation() handleDrawerOnNavigation()
} }
readerTableOfContentsDrawer.setLockMode( // readerTableOfContentsDrawer.setLockMode(
if (destination.id == readerFragmentResId) { // if (destination.id == readerFragmentResId) {
LOCK_MODE_UNLOCKED // LOCK_MODE_UNLOCKED
} else { // } else {
LOCK_MODE_LOCKED_CLOSED // LOCK_MODE_LOCKED_CLOSED
} // }
) // )
} }
private fun NavigationView.setLockMode(lockMode: Int) { private fun NavigationView.setLockMode(lockMode: Int) {
drawerContainerLayout.setDrawerLockMode(lockMode, this) // drawerContainerLayout.setDrawerLockMode(lockMode, this)
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -278,34 +279,34 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
// toolbar.getToolbarNavigationIcon()?.setToolTipWithContentDescription( // toolbar.getToolbarNavigationIcon()?.setToolTipWithContentDescription(
// getString(R.string.open_drawer) // getString(R.string.open_drawer)
// ) // )
drawerToggle = // drawerToggle =
ActionBarDrawerToggle( // ActionBarDrawerToggle(
this, // this,
drawerContainerLayout, // drawerContainerLayout,
R.string.open_drawer, // R.string.open_drawer,
R.string.close_drawer // R.string.close_drawer
) // )
drawerToggle?.let { // drawerToggle?.let {
drawerContainerLayout.addDrawerListener(it) // drawerContainerLayout.addDrawerListener(it)
it.syncState() // it.syncState()
} // }
drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) // drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
if (shouldEnableRightDrawer) { // if (shouldEnableRightDrawer) {
// Enable the right drawer // // Enable the right drawer
drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) // drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
} // }
} }
open fun disableDrawer(disableRightDrawer: Boolean = true) { open fun disableDrawer(disableRightDrawer: Boolean = true) {
drawerToggle?.isDrawerIndicatorEnabled = false // drawerToggle?.isDrawerIndicatorEnabled = false
drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) // drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
if (disableRightDrawer) { // if (disableRightDrawer) {
// Disable the right drawer // // Disable the right drawer
drawerContainerLayout.setDrawerLockMode( // drawerContainerLayout.setDrawerLockMode(
DrawerLayout.LOCK_MODE_LOCKED_CLOSED, // DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
GravityCompat.END // GravityCompat.END
) // )
} // }
} }
open fun onNavigationItemSelected(item: MenuItem): Boolean { open fun onNavigationItemSelected(item: MenuItem): Boolean {
@ -326,15 +327,15 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
handleDrawerOnNavigation() handleDrawerOnNavigation()
} }
fun navigationDrawerIsOpen(): Boolean = fun navigationDrawerIsOpen(): Boolean = false
drawerContainerLayout.isDrawerOpen(drawerNavView) // drawerContainerLayout.isDrawerOpen(drawerNavView)
fun closeNavigationDrawer() { fun closeNavigationDrawer() {
drawerContainerLayout.closeDrawer(drawerNavView) // drawerContainerLayout.closeDrawer(drawerNavView)
} }
fun openNavigationDrawer() { fun openNavigationDrawer() {
drawerContainerLayout.openDrawer(drawerNavView) // drawerContainerLayout.openDrawer(drawerNavView)
} }
fun openSupportKiwixExternalLink() { fun openSupportKiwixExternalLink() {

View File

@ -4,4 +4,5 @@
<item name="tabsAdapterCloseImageView" type="id" /> <item name="tabsAdapterCloseImageView" type="id" />
<item name="tabsAdapterCardView" type="id" /> <item name="tabsAdapterCardView" type="id" />
<item name="tabsAdapterTextView" type="id" /> <item name="tabsAdapterTextView" type="id" />
<item name="nav_host_fragment" type="id" />
</resources> </resources>