Introduced route-based navigation to pass arguments to fragments. * Removed some unused files. * Fixed: all library controls for navigation (e.g., sharing files, navigating to downloads, etc.).

* Fixed: Open library button not working.
* Fixed: opening ZIM file from storage (using the plus button) had too much bottom margin after migrating MainActivity to Compose.
* Added support for automatically hiding and showing the BottomAppBar while scrolling, using the Compose approach.
* Many other improvements in UI and logics.
* Show or hide the BottomAppBar when full screen mode is enabled/disabled or when tabs are visible.
* Disable the left drawer when opening full screen mode, tabs, etc.
This commit is contained in:
MohitMaliFtechiz 2025-07-23 00:28:10 +05:30
parent edae60fa52
commit 877c4a7166
20 changed files with 330 additions and 254 deletions

View File

@ -22,11 +22,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.navigation.fragment.findNavController import androidx.navigation.NavOptions
import org.kiwix.kiwixmobile.cachedComponent import org.kiwix.kiwixmobile.cachedComponent
import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.ui.KiwixDestination
import javax.inject.Inject import javax.inject.Inject
class IntroFragment : BaseFragment(), IntroContract.View, FragmentActivityExtensions { class IntroFragment : BaseFragment(), IntroContract.View, FragmentActivityExtensions {
@ -58,7 +60,10 @@ class IntroFragment : BaseFragment(), IntroContract.View, FragmentActivityExtens
private fun navigateToLibrary() { private fun navigateToLibrary() {
presenter.setIntroShown() presenter.setIntroShown()
findNavController().navigate(IntroFragmentDirections.actionIntrofragmentToLibraryFragment()) val navOptions = NavOptions.Builder()
.setPopUpTo(KiwixDestination.Intro.route, inclusive = true)
.build()
(requireActivity() as CoreMainActivity).navigate(KiwixDestination.Library.route, navOptions)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -28,18 +28,22 @@ import android.view.MenuItem
import androidx.activity.compose.setContent 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.material3.BottomAppBarDefaults
import androidx.compose.material3.DrawerValue import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
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.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavOptions
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
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
@ -59,9 +63,9 @@ import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.DrawerMenuItem import org.kiwix.kiwixmobile.core.main.DrawerMenuItem
import org.kiwix.kiwixmobile.core.main.NEW_TAB_SHORTCUT_ID 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.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.core.utils.dialog.DialogHost
import org.kiwix.kiwixmobile.kiwixActivityComponent import org.kiwix.kiwixmobile.kiwixActivityComponent
import org.kiwix.kiwixmobile.ui.KiwixDestination import org.kiwix.kiwixmobile.ui.KiwixDestination
import javax.inject.Inject import javax.inject.Inject
@ -106,6 +110,7 @@ class KiwixMainActivity : CoreMainActivity() {
KiwixDestination.Reader.route KiwixDestination.Reader.route
) )
private val shouldShowBottomAppBar = mutableStateOf(true)
// private lateinit var activityKiwixMainBinding: ActivityKiwixMainBinding // private lateinit var activityKiwixMainBinding: ActivityKiwixMainBinding
private var isIntroScreenVisible: Boolean = false private var isIntroScreenVisible: Boolean = false
@ -116,6 +121,7 @@ class KiwixMainActivity : CoreMainActivity() {
} }
private val storageDeviceList = arrayListOf<StorageDevice>() private val storageDeviceList = arrayListOf<StorageDevice>()
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
cachedComponent.inject(this) cachedComponent.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -123,17 +129,27 @@ class KiwixMainActivity : CoreMainActivity() {
navController = rememberNavController() navController = rememberNavController()
leftDrawerState = rememberDrawerState(DrawerValue.Closed) leftDrawerState = rememberDrawerState(DrawerValue.Closed)
uiCoroutineScope = rememberCoroutineScope() uiCoroutineScope = rememberCoroutineScope()
bottomAppBarScrollBehaviour = BottomAppBarDefaults.exitAlwaysScrollBehavior()
KiwixMainActivityScreen( KiwixMainActivityScreen(
navController = navController, navController = navController,
leftDrawerContent = leftDrawerMenu, leftDrawerContent = leftDrawerMenu,
topLevelDestinationsRoute = topLevelDestinationsRoute, topLevelDestinationsRoute = topLevelDestinationsRoute,
leftDrawerState = leftDrawerState, leftDrawerState = leftDrawerState,
uiCoroutineScope = uiCoroutineScope, uiCoroutineScope = uiCoroutineScope,
enableLeftDrawer = enableLeftDrawer.value enableLeftDrawer = enableLeftDrawer.value,
shouldShowBottomAppBar = shouldShowBottomAppBar.value,
bottomAppBarScrollBehaviour = bottomAppBarScrollBehaviour
) )
LaunchedEffect(navController) { LaunchedEffect(navController) {
navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange) navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange)
if (sharedPreferenceUtil.showIntro() && !isIntroScreenNotVisible()) {
val navOptions = NavOptions.Builder()
.setPopUpTo(KiwixDestination.Reader.route, inclusive = true)
.build()
navigate(KiwixDestination.Intro.route, navOptions)
}
} }
DialogHost(alertDialogShower)
} }
// activityKiwixMainBinding.drawerNavView.apply { // activityKiwixMainBinding.drawerNavView.apply {
// setupWithNavController(navController) // setupWithNavController(navController)
@ -191,34 +207,12 @@ class KiwixMainActivity : CoreMainActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
if (sharedPreferenceUtil.showIntro() && !isIntroScreenNotVisible()) {
// navigate(KiwixReaderFragmentDirections.actionReaderFragmentToIntroFragment())
}
if (!sharedPreferenceUtil.prefIsTest) { if (!sharedPreferenceUtil.prefIsTest) {
sharedPreferenceUtil.setIsPlayStoreBuildType(BuildConfig.IS_PLAYSTORE) sharedPreferenceUtil.setIsPlayStoreBuildType(BuildConfig.IS_PLAYSTORE)
} }
setDefaultDeviceLanguage() setDefaultDeviceLanguage()
} }
/**
* This is for manually showing/hiding the BottomNavigationView with animation from compose
* screens until we migrate the BottomNavigationView to compose. Once we migrate we will remove it.
*
* TODO Remove this once we migrate to compose.
*/
override fun toggleBottomNavigation(isVisible: Boolean) {
// activityKiwixMainBinding.bottomNavView.animate()
// ?.translationY(
// if (isVisible) {
// ZERO.toFloat()
// } else {
// activityKiwixMainBinding.bottomNavView.height.toFloat()
// }
// )
// ?.setDuration(KIWIX_BOTTOM_BAR_ANIMATION_DURATION)
// ?.start()
}
private fun setDefaultDeviceLanguage() { private fun setDefaultDeviceLanguage() {
if (sharedPreferenceUtil.prefDeviceDefaultLanguage.isEmpty()) { if (sharedPreferenceUtil.prefDeviceDefaultLanguage.isEmpty()) {
ConfigurationCompat.getLocales( ConfigurationCompat.getLocales(
@ -256,9 +250,7 @@ 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 { navigate(KiwixDestination.Downloads.route)
// NavigationUI.onNavDestinationSelected(it, navController)
// }
} }
} }
@ -286,12 +278,7 @@ class KiwixMainActivity : CoreMainActivity() {
} }
private fun openLocalLibraryWithZimFilePath(path: String) { private fun openLocalLibraryWithZimFilePath(path: String) {
navigate( navigate(KiwixDestination.Library.createRoute(zimFileUri = path))
R.id.libraryFragment,
bundleOf(
ZIM_FILE_URI_KEY to path,
)
)
} }
private fun handleNotificationIntent(intent: Intent) { private fun handleNotificationIntent(intent: Intent) {
@ -307,6 +294,10 @@ class KiwixMainActivity : CoreMainActivity() {
} }
} }
private fun openZimFromFilePath(path: String) {
navigate(KiwixDestination.Reader.createRoute(zimFileUri = path))
}
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
id.menu_host_books -> openZimHostFragment() id.menu_host_books -> openZimHostFragment()
@ -359,6 +350,24 @@ class KiwixMainActivity : CoreMainActivity() {
// activityKiwixMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0) // activityKiwixMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0)
} }
override fun openSearch(searchString: String, isOpenedFromTabView: Boolean, isVoice: Boolean) {
navigate(
KiwixDestination.Search.createRoute(
searchString = searchString,
isOpenedFromTabView = isOpenedFromTabView,
isVoice = isVoice
)
)
}
override fun hideBottomAppBar() {
shouldShowBottomAppBar.value = false
}
override fun showBottomAppBar() {
shouldShowBottomAppBar.value = true
}
// Outdated shortcut ids(new_tab, get_content) // Outdated shortcut ids(new_tab, get_content)
// Remove if the application has the outdated shortcuts. // Remove if the application has the outdated shortcuts.
private fun removeOutdatedIdShortcuts() { private fun removeOutdatedIdShortcuts() {

View File

@ -60,10 +60,11 @@ fun KiwixMainActivityScreen(
topLevelDestinationsRoute: Set<String>, topLevelDestinationsRoute: Set<String>,
leftDrawerState: DrawerState, leftDrawerState: DrawerState,
uiCoroutineScope: CoroutineScope, uiCoroutineScope: CoroutineScope,
enableLeftDrawer: Boolean enableLeftDrawer: Boolean,
shouldShowBottomAppBar: Boolean,
bottomAppBarScrollBehaviour: BottomAppBarScrollBehavior
) { ) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val scrollingBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
KiwixTheme { KiwixTheme {
ModalNavigationDrawer( ModalNavigationDrawer(
drawerState = leftDrawerState, drawerState = leftDrawerState,
@ -77,16 +78,17 @@ fun KiwixMainActivityScreen(
Box { Box {
Scaffold( Scaffold(
bottomBar = { bottomBar = {
if (navBackStackEntry?.destination?.route in topLevelDestinationsRoute) { if (navBackStackEntry?.destination?.route in topLevelDestinationsRoute && shouldShowBottomAppBar) {
BottomNavigationBar( BottomNavigationBar(
navController = navController, navController = navController,
scrollBehavior = scrollingBehavior, bottomAppBarScrollBehaviour = bottomAppBarScrollBehaviour,
navBackStackEntry = navBackStackEntry, navBackStackEntry = navBackStackEntry,
leftDrawerState = leftDrawerState, leftDrawerState = leftDrawerState,
uiCoroutineScope = uiCoroutineScope uiCoroutineScope = uiCoroutineScope
) )
} }
} },
modifier = Modifier.fillMaxSize()
) { paddingValues -> ) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues)) { Box(modifier = Modifier.padding(paddingValues)) {
KiwixNavGraph( KiwixNavGraph(
@ -104,7 +106,7 @@ fun KiwixMainActivityScreen(
@Composable @Composable
fun BottomNavigationBar( fun BottomNavigationBar(
navController: NavHostController, navController: NavHostController,
scrollBehavior: BottomAppBarScrollBehavior, bottomAppBarScrollBehaviour: BottomAppBarScrollBehavior,
navBackStackEntry: NavBackStackEntry?, navBackStackEntry: NavBackStackEntry?,
leftDrawerState: DrawerState, leftDrawerState: DrawerState,
uiCoroutineScope: CoroutineScope uiCoroutineScope: CoroutineScope
@ -130,12 +132,14 @@ fun BottomNavigationBar(
BottomAppBar( BottomAppBar(
containerColor = Black, containerColor = Black,
contentColor = White, contentColor = White,
scrollBehavior = scrollBehavior scrollBehavior = bottomAppBarScrollBehaviour
) { ) {
bottomNavItems.forEach { item -> bottomNavItems.forEach { item ->
NavigationBarItem( NavigationBarItem(
selected = currentDestinationRoute == item.route, selected = currentDestinationRoute == item.route,
onClick = { onClick = {
// Do not load again fragment that is already loaded.
if (item.route == currentDestinationRoute) return@NavigationBarItem
uiCoroutineScope.launch { uiCoroutineScope.launch {
leftDrawerState.close() leftDrawerState.close()
navController.navigate(item.route) navController.navigate(item.route)

View File

@ -39,6 +39,7 @@ import androidx.appcompat.view.ActionMode
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -51,6 +52,7 @@ 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 androidx.navigation.NavOptions
import eu.mhutti1.utils.storage.Bytes import eu.mhutti1.utils.storage.Bytes
import eu.mhutti1.utils.storage.StorageDevice import eu.mhutti1.utils.storage.StorageDevice
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@ -78,7 +80,6 @@ import org.kiwix.kiwixmobile.core.navigateToSettings
import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION
@ -173,6 +174,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
baseActivity.cachedComponent.inject(this) baseActivity.cachedComponent.inject(this)
} }
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -183,10 +185,6 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
return ComposeView(requireContext()).apply { return ComposeView(requireContext()).apply {
setContent { setContent {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState)
LaunchedEffect(isBottomNavVisible) {
(requireActivity() as KiwixMainActivity).toggleBottomNavigation(isBottomNavVisible)
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
updateLibraryScreenState( updateLibraryScreenState(
bottomNavigationHeight = getBottomNavigationHeight(), bottomNavigationHeight = getBottomNavigationHeight(),
@ -202,6 +200,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
onMultiSelect = { offerAction(RequestSelect(it)) }, onMultiSelect = { offerAction(RequestSelect(it)) },
onRefresh = { onSwipeRefresh() }, onRefresh = { onSwipeRefresh() },
onDownloadButtonClick = { downloadBookButtonClick() }, onDownloadButtonClick = { downloadBookButtonClick() },
bottomAppBarScrollBehaviour = (requireActivity() as CoreMainActivity).bottomAppBarScrollBehaviour
) { ) {
NavigationIcon( NavigationIcon(
iconItem = IconItem.Vector(Icons.Filled.Menu), iconItem = IconItem.Vector(Icons.Filled.Menu),
@ -493,9 +492,12 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
zimFileReader.dispose() zimFileReader.dispose()
} }
} }
val navOptions = NavOptions.Builder()
.setPopUpTo(KiwixDestination.Reader.route, false)
.build()
activity?.navigate( activity?.navigate(
LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader() KiwixDestination.Reader.createRoute(zimFileUri = file.toUri().toString()),
.apply { zimFileUri = file.toUri().toString() } navOptions
) )
} }
} }

View File

@ -30,6 +30,7 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.BottomAppBarScrollBehavior
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -49,7 +50,6 @@ import androidx.compose.ui.text.style.TextAlign
import org.kiwix.kiwixmobile.R.string import org.kiwix.kiwixmobile.R.string
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.main.reader.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG import org.kiwix.kiwixmobile.core.main.reader.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG
import org.kiwix.kiwixmobile.core.main.reader.rememberScrollBehavior
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
@ -60,7 +60,6 @@ import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.ui.theme.White import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FAB_ICON_BOTTOM_MARGIN
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
@ -85,10 +84,9 @@ fun LocalLibraryScreen(
onClick: ((BookOnDisk) -> Unit)? = null, onClick: ((BookOnDisk) -> Unit)? = null,
onLongClick: ((BookOnDisk) -> Unit)? = null, onLongClick: ((BookOnDisk) -> Unit)? = null,
onMultiSelect: ((BookOnDisk) -> Unit)? = null, onMultiSelect: ((BookOnDisk) -> Unit)? = null,
bottomAppBarScrollBehaviour: BottomAppBarScrollBehavior,
navigationIcon: @Composable () -> Unit navigationIcon: @Composable () -> Unit
) { ) {
val (bottomNavHeight, lazyListState) =
rememberScrollBehavior(state.bottomNavigationHeight, listState)
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
KiwixTheme { KiwixTheme {
Scaffold( Scaffold(
@ -105,7 +103,7 @@ fun LocalLibraryScreen(
modifier = Modifier modifier = Modifier
.systemBarsPadding() .systemBarsPadding()
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.padding(bottom = bottomNavHeight.value) .nestedScroll(bottomAppBarScrollBehaviour.nestedScrollConnection)
) { contentPadding -> ) { contentPadding ->
SwipeRefreshLayout( SwipeRefreshLayout(
isRefreshing = state.swipeRefreshItem.first, isRefreshing = state.swipeRefreshItem.first,
@ -130,7 +128,7 @@ fun LocalLibraryScreen(
onClick, onClick,
onLongClick, onLongClick,
onMultiSelect, onMultiSelect,
lazyListState listState
) )
} }
} }
@ -178,7 +176,6 @@ private fun SelectFileButton(fabButtonClick: () -> Unit) {
FloatingActionButton( FloatingActionButton(
onClick = fabButtonClick, onClick = fabButtonClick,
modifier = Modifier modifier = Modifier
.padding(bottom = FAB_ICON_BOTTOM_MARGIN)
.testTag(SELECT_FILE_BUTTON_TESTING_TAG), .testTag(SELECT_FILE_BUTTON_TESTING_TAG),
containerColor = Black, containerColor = Black,
shape = MaterialTheme.shapes.extraLarge shape = MaterialTheme.shapes.extraLarge

View File

@ -34,6 +34,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -75,7 +76,6 @@ import org.kiwix.kiwixmobile.core.navigateToSettings
import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
import org.kiwix.kiwixmobile.core.ui.components.ONE import org.kiwix.kiwixmobile.core.ui.components.ONE
import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.utils.BookUtils import org.kiwix.kiwixmobile.core.utils.BookUtils
@ -244,14 +244,11 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
composeView?.setContent { composeView?.setContent {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState)
LaunchedEffect(isBottomNavVisible) {
(requireActivity() as KiwixMainActivity).toggleBottomNavigation(isBottomNavVisible)
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
onlineLibraryScreenState.value.update { onlineLibraryScreenState.value.update {
copy( copy(
@ -273,7 +270,8 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
contentDescription = string.open_drawer, contentDescription = string.open_drawer,
onClick = { navigationIconClick(onlineLibraryScreenState.value.value.isSearchActive) } onClick = { navigationIconClick(onlineLibraryScreenState.value.value.isSearchActive) }
) )
} },
bottomAppBarScrollBehaviour = (requireActivity() as CoreMainActivity).bottomAppBarScrollBehaviour
) )
DialogHost(alertDialogShower) DialogHost(alertDialogShower)
} }

View File

@ -35,6 +35,7 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.BottomAppBarScrollBehavior
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -61,7 +62,6 @@ import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.downloader.downloadManager.FIVE import org.kiwix.kiwixmobile.core.downloader.downloadManager.FIVE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.extensions.hideKeyboardOnLazyColumnScroll import org.kiwix.kiwixmobile.core.extensions.hideKeyboardOnLazyColumnScroll
import org.kiwix.kiwixmobile.core.main.reader.rememberScrollBehavior
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView
@ -98,10 +98,9 @@ fun OnlineLibraryScreen(
state: OnlineLibraryScreenState, state: OnlineLibraryScreenState,
actionMenuItems: List<ActionMenuItem>, actionMenuItems: List<ActionMenuItem>,
listState: LazyListState, listState: LazyListState,
bottomAppBarScrollBehaviour: BottomAppBarScrollBehavior,
navigationIcon: @Composable () -> Unit, navigationIcon: @Composable () -> Unit,
) { ) {
val (bottomNavHeight, lazyListState) =
rememberScrollBehavior(state.bottomNavigationHeight, listState)
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
KiwixTheme { KiwixTheme {
@ -118,7 +117,7 @@ fun OnlineLibraryScreen(
}, },
modifier = Modifier modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.padding(bottom = bottomNavHeight.value) .nestedScroll(bottomAppBarScrollBehaviour.nestedScrollConnection)
) { paddingValues -> ) { paddingValues ->
SwipeRefreshLayout( SwipeRefreshLayout(
isRefreshing = state.isRefreshing && !state.scanningProgressItem.first, isRefreshing = state.isRefreshing && !state.scanningProgressItem.first,
@ -132,7 +131,7 @@ fun OnlineLibraryScreen(
end = paddingValues.calculateEndPadding(LocalLayoutDirection.current), end = paddingValues.calculateEndPadding(LocalLayoutDirection.current),
) )
) { ) {
OnlineLibraryScreenContent(state, lazyListState) OnlineLibraryScreenContent(state, listState)
} }
} }
} }

View File

@ -28,6 +28,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavOptions
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.cachedComponent import org.kiwix.kiwixmobile.cachedComponent
@ -55,6 +56,7 @@ import org.kiwix.kiwixmobile.core.utils.TAG_CURRENT_FILE
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
import org.kiwix.kiwixmobile.core.utils.files.FileUtils import org.kiwix.kiwixmobile.core.utils.files.FileUtils
import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.ui.KiwixDestination
import java.io.File import java.io.File
class KiwixReaderFragment : CoreReaderFragment() { class KiwixReaderFragment : CoreReaderFragment() {
@ -70,9 +72,10 @@ class KiwixReaderFragment : CoreReaderFragment() {
val activity = activity as CoreMainActivity val activity = activity as CoreMainActivity
readerScreenState.update { readerScreenState.update {
copy(onOpenLibraryButtonClicked = { copy(onOpenLibraryButtonClicked = {
activity.navigate( val navOptions = NavOptions.Builder()
KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary() .setPopUpTo(KiwixDestination.Reader.route, inclusive = true)
) .build()
activity.navigate(KiwixDestination.Library.route, navOptions)
}) })
} }
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)

View File

@ -18,26 +18,52 @@
package org.kiwix.kiwixmobile.ui package org.kiwix.kiwixmobile.ui
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.net.toUri
import androidx.core.view.doOnAttach import androidx.core.view.doOnAttach
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import org.kiwix.kiwixmobile.core.main.BOOKMARK_FRAGMENT
import org.kiwix.kiwixmobile.core.main.DOWNLOAD_FRAGMENT
import org.kiwix.kiwixmobile.core.main.FIND_IN_PAGE_SEARCH_STRING
import org.kiwix.kiwixmobile.core.main.HELP_FRAGMENT
import org.kiwix.kiwixmobile.core.main.HISTORY_FRAGMENT
import org.kiwix.kiwixmobile.core.main.INTRO_FRAGMENT
import org.kiwix.kiwixmobile.core.main.LANGUAGE_FRAGMENT
import org.kiwix.kiwixmobile.core.main.LOCAL_FILE_TRANSFER_FRAGMENT
import org.kiwix.kiwixmobile.core.main.LOCAL_LIBRARY_FRAGMENT
import org.kiwix.kiwixmobile.core.main.NOTES_FRAGMENT
import org.kiwix.kiwixmobile.core.main.PAGE_URL_KEY
import org.kiwix.kiwixmobile.core.main.READER_FRAGMENT
import org.kiwix.kiwixmobile.core.main.SEARCH_FRAGMENT
import org.kiwix.kiwixmobile.core.main.SETTINGS_FRAGMENT
import org.kiwix.kiwixmobile.core.main.SHOULD_OPEN_IN_NEW_TAB
import org.kiwix.kiwixmobile.core.main.ZIM_FILE_URI_KEY
import org.kiwix.kiwixmobile.core.main.ZIM_HOST_FRAGMENT
import org.kiwix.kiwixmobile.core.main.reader.SEARCH_ITEM_TITLE_KEY
import org.kiwix.kiwixmobile.core.page.bookmark.BookmarksFragment import org.kiwix.kiwixmobile.core.page.bookmark.BookmarksFragment
import org.kiwix.kiwixmobile.core.page.history.HistoryFragment import org.kiwix.kiwixmobile.core.page.history.HistoryFragment
import org.kiwix.kiwixmobile.core.page.notes.NotesFragment import org.kiwix.kiwixmobile.core.page.notes.NotesFragment
import org.kiwix.kiwixmobile.core.search.NAV_ARG_SEARCH_STRING
import org.kiwix.kiwixmobile.core.search.SearchFragment import org.kiwix.kiwixmobile.core.search.SearchFragment
import org.kiwix.kiwixmobile.core.utils.EXTRA_IS_WIDGET_VOICE
import org.kiwix.kiwixmobile.core.utils.TAG_FROM_TAB_SWITCHER
import org.kiwix.kiwixmobile.help.KiwixHelpFragment import org.kiwix.kiwixmobile.help.KiwixHelpFragment
import org.kiwix.kiwixmobile.intro.IntroFragment import org.kiwix.kiwixmobile.intro.IntroFragment
import org.kiwix.kiwixmobile.language.LanguageFragment import org.kiwix.kiwixmobile.language.LanguageFragment
@ -58,24 +84,47 @@ fun KiwixNavGraph(
startDestination = KiwixDestination.Reader.route, startDestination = KiwixDestination.Reader.route,
modifier = modifier modifier = modifier
) { ) {
composable(KiwixDestination.Reader.route) { composable(
route = KiwixDestination.Reader.route,
arguments = listOf(
navArgument(ZIM_FILE_URI_KEY) { type = NavType.StringType; defaultValue = "" },
navArgument(FIND_IN_PAGE_SEARCH_STRING) { type = NavType.StringType; defaultValue = "" },
navArgument(PAGE_URL_KEY) { type = NavType.StringType; defaultValue = "" },
navArgument(SHOULD_OPEN_IN_NEW_TAB) { type = NavType.BoolType; defaultValue = false },
navArgument(SEARCH_ITEM_TITLE_KEY) { type = NavType.StringType; defaultValue = "" }
)
) { backStackEntry ->
val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty()
val findInPageSearchString =
backStackEntry.arguments?.getString(FIND_IN_PAGE_SEARCH_STRING).orEmpty()
val pageUrl = backStackEntry.arguments?.getString(PAGE_URL_KEY).orEmpty()
val shouldOpenInNewTab = backStackEntry.arguments?.getBoolean(SHOULD_OPEN_IN_NEW_TAB) ?: false
val searchItemTitle = backStackEntry.arguments?.getString(SEARCH_ITEM_TITLE_KEY).orEmpty()
FragmentContainer { FragmentContainer {
KiwixReaderFragment().apply { KiwixReaderFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putString("zimFileUri", "") putString(ZIM_FILE_URI_KEY, zimFileUri)
putString("findInPageSearchString", "") putString(FIND_IN_PAGE_SEARCH_STRING, findInPageSearchString)
putString("pageUrl", "") putString(PAGE_URL_KEY, pageUrl)
putBoolean("shouldOpenInNewTab", false) putBoolean(SHOULD_OPEN_IN_NEW_TAB, shouldOpenInNewTab)
putString("searchItemTitle", "") putString(SEARCH_ITEM_TITLE_KEY, searchItemTitle)
} }
} }
} }
} }
composable(KiwixDestination.Library.route) { composable(
route = KiwixDestination.Library.route,
arguments = listOf(
navArgument(ZIM_FILE_URI_KEY) { type = NavType.StringType; defaultValue = "" }
)
) { backStackEntry ->
val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty()
FragmentContainer { FragmentContainer {
LocalLibraryFragment().apply { LocalLibraryFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putString("zimFileUri", "") putString(ZIM_FILE_URI_KEY, zimFileUri)
} }
} }
} }
@ -125,16 +174,51 @@ fun KiwixNavGraph(
KiwixSettingsFragment() KiwixSettingsFragment()
} }
} }
composable(KiwixDestination.Search.route) { composable(
route = KiwixDestination.Search.route,
arguments = listOf(
navArgument(NAV_ARG_SEARCH_STRING) { type = NavType.StringType; defaultValue = "" },
navArgument(TAG_FROM_TAB_SWITCHER) { type = NavType.BoolType; defaultValue = false },
navArgument(EXTRA_IS_WIDGET_VOICE) { type = NavType.BoolType; defaultValue = false }
)
) { backStackEntry ->
val searchString = backStackEntry.arguments?.getString(NAV_ARG_SEARCH_STRING).orEmpty()
val isOpenedFromTabSwitcher =
backStackEntry.arguments?.getBoolean(TAG_FROM_TAB_SWITCHER) ?: false
val isVoice = backStackEntry.arguments?.getBoolean(EXTRA_IS_WIDGET_VOICE) ?: false
FragmentContainer { FragmentContainer {
SearchFragment() SearchFragment().apply {
arguments = Bundle().apply {
putString(NAV_ARG_SEARCH_STRING, searchString)
putBoolean(TAG_FROM_TAB_SWITCHER, isOpenedFromTabSwitcher)
putBoolean(EXTRA_IS_WIDGET_VOICE, isVoice)
}
}
} }
} }
composable(KiwixDestination.LocalFileTransfer.route) { composable(
route = KiwixDestination.LocalFileTransfer.route,
arguments = listOf(
navArgument("uris") {
type = NavType.StringType
nullable = true
defaultValue = null
}
)
) { backStackEntry ->
val urisParam = backStackEntry.arguments?.getString("uris")
val uris: List<Uri>? =
urisParam?.takeIf { it != "null" }?.split(",")?.map {
Uri.decode(it).toUri()
}
FragmentContainer { FragmentContainer {
LocalFileTransferFragment().apply { LocalFileTransferFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putParcelableArray("uris", null) putParcelableArray(
"uris",
uris?.toTypedArray()
)
} }
} }
} }
@ -142,6 +226,7 @@ fun KiwixNavGraph(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun FragmentContainer( fun FragmentContainer(
fragmentProvider: () -> Fragment fragmentProvider: () -> Fragment
@ -170,17 +255,72 @@ fun FragmentContainer(
} }
sealed class KiwixDestination(val route: String) { sealed class KiwixDestination(val route: String) {
object Reader : KiwixDestination("readerFragment") object Reader : KiwixDestination(
object Library : KiwixDestination("libraryFragment") READER_FRAGMENT +
object Downloads : KiwixDestination("downloadsFragment") "?$ZIM_FILE_URI_KEY={$ZIM_FILE_URI_KEY}" +
object Bookmarks : KiwixDestination("bookmarksFragment") "&$FIND_IN_PAGE_SEARCH_STRING={$FIND_IN_PAGE_SEARCH_STRING}" +
object Notes : KiwixDestination("notesFragment") "&$PAGE_URL_KEY={$PAGE_URL_KEY}" +
object Intro : KiwixDestination("introFragment") "&$SHOULD_OPEN_IN_NEW_TAB={$SHOULD_OPEN_IN_NEW_TAB}" +
object History : KiwixDestination("historyFragment") "&$SEARCH_ITEM_TITLE_KEY={$SEARCH_ITEM_TITLE_KEY}"
object Language : KiwixDestination("languageFragment") ) {
object ZimHost : KiwixDestination("zimHostFragment") fun createRoute(
object Help : KiwixDestination("helpFragment") zimFileUri: String = "",
object Settings : KiwixDestination("kiwixSettingsFragment") findInPageSearchString: String = "",
object Search : KiwixDestination("searchFragment") pageUrl: String = "",
object LocalFileTransfer : KiwixDestination("localFileTransferFragment") shouldOpenInNewTab: Boolean = false,
searchItemTitle: String = ""
): String {
return READER_FRAGMENT +
"?$ZIM_FILE_URI_KEY=${Uri.encode(zimFileUri)}" +
"&$FIND_IN_PAGE_SEARCH_STRING=${Uri.encode(findInPageSearchString)}" +
"&$PAGE_URL_KEY=${Uri.encode(pageUrl)}" +
"&$SHOULD_OPEN_IN_NEW_TAB=$shouldOpenInNewTab" +
"&$SEARCH_ITEM_TITLE_KEY=${Uri.encode(searchItemTitle)}"
}
}
object Library :
KiwixDestination("$LOCAL_LIBRARY_FRAGMENT?$ZIM_FILE_URI_KEY={$ZIM_FILE_URI_KEY}") {
fun createRoute(zimFileUri: String = "") =
"$LOCAL_LIBRARY_FRAGMENT?$ZIM_FILE_URI_KEY=${Uri.encode(zimFileUri)}"
}
object Downloads : KiwixDestination(DOWNLOAD_FRAGMENT)
object Bookmarks : KiwixDestination(BOOKMARK_FRAGMENT)
object Notes : KiwixDestination(NOTES_FRAGMENT)
object Intro : KiwixDestination(INTRO_FRAGMENT)
object History : KiwixDestination(HISTORY_FRAGMENT)
object Language : KiwixDestination(LANGUAGE_FRAGMENT)
object ZimHost : KiwixDestination(ZIM_HOST_FRAGMENT)
object Help : KiwixDestination(HELP_FRAGMENT)
object Settings : KiwixDestination(SETTINGS_FRAGMENT)
object Search : KiwixDestination(
SEARCH_FRAGMENT +
"?$NAV_ARG_SEARCH_STRING={$NAV_ARG_SEARCH_STRING}" +
"&$TAG_FROM_TAB_SWITCHER={$TAG_FROM_TAB_SWITCHER}" +
"&$EXTRA_IS_WIDGET_VOICE={$EXTRA_IS_WIDGET_VOICE}"
) {
fun createRoute(
searchString: String = "",
isOpenedFromTabView: Boolean = false,
isVoice: Boolean = false
): String {
return SEARCH_FRAGMENT +
"?$NAV_ARG_SEARCH_STRING=$searchString" +
"&$TAG_FROM_TAB_SWITCHER=$isOpenedFromTabView" +
"&$EXTRA_IS_WIDGET_VOICE=$isVoice"
}
}
object LocalFileTransfer : KiwixDestination("$LOCAL_FILE_TRANSFER_FRAGMENT?uris={uris}") {
fun createRoute(uris: String? = null): String {
return if (uris != null)
"$LOCAL_FILE_TRANSFER_FRAGMENT?uris=${Uri.encode(uris)}"
else
"$LOCAL_FILE_TRANSFER_FRAGMENT?uris=null"
}
}
} }
fun List<Uri>.toUriParam(): String =
joinToString(",") { Uri.encode(it.toString()) }

View File

@ -19,12 +19,17 @@
package org.kiwix.kiwixmobile.zimManager.fileselectView.effects package org.kiwix.kiwixmobile.zimManager.fileselectView.effects
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavOptions
import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate
import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections import org.kiwix.kiwixmobile.ui.KiwixDestination
object NavigateToDownloads : SideEffect<Unit> { object NavigateToDownloads : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) { override fun invokeWith(activity: AppCompatActivity) {
activity.navigate(LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationDownloads()) val navOptions = NavOptions.Builder()
.setPopUpTo(KiwixDestination.Library.route, inclusive = true)
.setRestoreState(true)
.build()
activity.navigate(KiwixDestination.Downloads.route, navOptions)
} }
} }

View File

@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.zimManager.fileselectView.effects
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavOptions
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -28,7 +29,7 @@ import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.kiwixmobile.main.KiwixMainActivity import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader import org.kiwix.kiwixmobile.ui.KiwixDestination
@Suppress("InjectDispatcher") @Suppress("InjectDispatcher")
data class OpenFileWithNavigation(private val bookOnDisk: BooksOnDiskListItem.BookOnDisk) : data class OpenFileWithNavigation(private val bookOnDisk: BooksOnDiskListItem.BookOnDisk) :
@ -45,10 +46,12 @@ data class OpenFileWithNavigation(private val bookOnDisk: BooksOnDiskListItem.Bo
activity.getString(R.string.error_file_not_found, zimReaderSource.toDatabase()) activity.getString(R.string.error_file_not_found, zimReaderSource.toDatabase())
) )
} else { } else {
val navOptions = NavOptions.Builder()
.setPopUpTo(KiwixDestination.Library.route, inclusive = false)
.build()
activity.navigate( activity.navigate(
actionNavigationLibraryToNavigationReader().apply { KiwixDestination.Reader.createRoute(zimFileUri = zimReaderSource.toDatabase()),
zimFileUri = zimReaderSource.toDatabase() navOptions
}
) )
} }
} }

View File

@ -19,12 +19,11 @@
package org.kiwix.kiwixmobile.zimManager.fileselectView.effects package org.kiwix.kiwixmobile.zimManager.fileselectView.effects
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.localFileTransfer.URIS_KEY import org.kiwix.kiwixmobile.ui.KiwixDestination
import org.kiwix.kiwixmobile.ui.toUriParam
data class ShareFiles(private val selectedBooks: List<BookOnDisk>) : data class ShareFiles(private val selectedBooks: List<BookOnDisk>) :
SideEffect<Unit> { SideEffect<Unit> {
@ -34,8 +33,7 @@ data class ShareFiles(private val selectedBooks: List<BookOnDisk>) :
it.zimReaderSource.getUri(activity) it.zimReaderSource.getUri(activity)
} }
activity.navigate( activity.navigate(
R.id.localFileTransferFragment, KiwixDestination.LocalFileTransfer.createRoute(uris = selectedFileContentURIs.toUriParam())
bundleOf(URIS_KEY to selectedFileContentURIs.toTypedArray())
) )
} }
} }

View File

@ -26,7 +26,6 @@ import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -41,7 +40,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.navigation.NavDirections import androidx.navigation.NavOptions
import org.kiwix.kiwixmobile.core.di.components.CoreActivityComponent import org.kiwix.kiwixmobile.core.di.components.CoreActivityComponent
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.utils.REQUEST_POST_NOTIFICATION_PERMISSION import org.kiwix.kiwixmobile.core.utils.REQUEST_POST_NOTIFICATION_PERMISSION
@ -93,22 +92,14 @@ object ActivityExtensions {
ViewModelProviders.of(this, viewModelFactory) ViewModelProviders.of(this, viewModelFactory)
.get(T::class.java) .get(T::class.java)
fun Activity.navigate(action: NavDirections) {
coreMainActivity.navigate(action)
}
val Activity.cachedComponent: CoreActivityComponent val Activity.cachedComponent: CoreActivityComponent
get() = coreMainActivity.cachedComponent get() = coreMainActivity.cachedComponent
fun Activity.setupDrawerToggle(shouldEnableRightDrawer: Boolean = false) = fun Activity.setupDrawerToggle(shouldEnableRightDrawer: Boolean = false) =
coreMainActivity.setupDrawerToggle(shouldEnableRightDrawer) coreMainActivity.setupDrawerToggle(shouldEnableRightDrawer)
fun Activity.navigate(route: String) { fun Activity.navigate(route: String, navOptions: NavOptions? = null) {
coreMainActivity.navigate(route) coreMainActivity.navigate(route, navOptions)
}
fun Activity.navigate(fragmentId: Int, bundle: Bundle) {
coreMainActivity.navigate(fragmentId, bundle)
} }
fun Activity.popNavigationBackstack() { fun Activity.popNavigationBackstack() {

View File

@ -27,7 +27,9 @@ import android.view.MenuItem
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material3.BottomAppBarScrollBehavior
import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.core.net.toUri import androidx.core.net.toUri
@ -73,6 +75,21 @@ const val KIWIX_INTERNAL_ERROR = 10
const val ACTION_NEW_TAB = "NEW_TAB" const val ACTION_NEW_TAB = "NEW_TAB"
const val NEW_TAB_SHORTCUT_ID = "new_tab_shortcut" const val NEW_TAB_SHORTCUT_ID = "new_tab_shortcut"
// Fragments names for compose based navigation.
const val READER_FRAGMENT = "readerFragment"
const val LOCAL_LIBRARY_FRAGMENT = "localLibraryFragment"
const val DOWNLOAD_FRAGMENT = "downloadsFragment"
const val BOOKMARK_FRAGMENT = "bookmarkFragment"
const val NOTES_FRAGMENT = "notesFragment"
const val INTRO_FRAGMENT = "introFragment"
const val HISTORY_FRAGMENT = "historyFragment"
const val LANGUAGE_FRAGMENT = "languageFragment"
const val ZIM_HOST_FRAGMENT = "zimHostFragment"
const val HELP_FRAGMENT = "helpFragment"
const val SETTINGS_FRAGMENT = "settingsFragment"
const val SEARCH_FRAGMENT = "searchFragment"
const val LOCAL_FILE_TRANSFER_FRAGMENT = "localFileTransferFragment"
abstract class CoreMainActivity : BaseActivity(), WebViewProvider { abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
abstract val searchFragmentRoute: String abstract val searchFragmentRoute: String
@ -108,8 +125,17 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
*/ */
protected val leftDrawerMenu = mutableStateListOf<DrawerMenuGroup>() protected val leftDrawerMenu = mutableStateListOf<DrawerMenuGroup>()
/**
* Manages the enabling/disabling the left drawer
*/
protected val enableLeftDrawer = mutableStateOf(true) protected val enableLeftDrawer = mutableStateOf(true)
/**
* For managing the the showing/hiding the bottomAppBar when scrolling.
*/
@OptIn(ExperimentalMaterial3Api::class)
lateinit var bottomAppBarScrollBehaviour: BottomAppBarScrollBehavior
// abstract val drawerContainerLayout: DrawerLayout // abstract val drawerContainerLayout: DrawerLayout
// abstract val drawerNavView: NavigationView // abstract val drawerNavView: NavigationView
// abstract val readerTableOfContentsDrawer: NavigationView // abstract val readerTableOfContentsDrawer: NavigationView
@ -319,6 +345,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
} }
open fun disableDrawer(disableRightDrawer: Boolean = true) { open fun disableDrawer(disableRightDrawer: Boolean = true) {
enableLeftDrawer.value = false
// drawerToggle?.isDrawerIndicatorEnabled = false // drawerToggle?.isDrawerIndicatorEnabled = false
// drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) // drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
// if (disableRightDrawer) { // if (disableRightDrawer) {
@ -424,8 +451,8 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
} }
} }
fun navigate(route: String) { fun navigate(route: String, navOptions: NavOptions? = null) {
navController.navigate(route) navController.navigate(route, navOptions)
} }
fun navigate(fragmentId: Int, bundle: Bundle) { fun navigate(fragmentId: Int, bundle: Bundle) {
@ -446,29 +473,11 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
navigate(historyFragmentRoute) navigate(historyFragmentRoute)
} }
fun openSearch( abstract fun openSearch(
searchString: String = "", searchString: String = "",
isOpenedFromTabView: Boolean = false, isOpenedFromTabView: Boolean = false,
isVoice: Boolean = false isVoice: Boolean = false
) { )
// navigate(
// searchFragmentRoute,
// bundleOf(
// NAV_ARG_SEARCH_STRING to searchString,
// TAG_FROM_TAB_SWITCHER to isOpenedFromTabView,
// EXTRA_IS_WIDGET_VOICE to isVoice
// )
// )
}
fun openZimFromFilePath(path: String) {
// navigate(
// readerFragmentRoute,
// bundleOf(
// ZIM_FILE_URI_KEY to path,
// )
// )
}
fun openPage( fun openPage(
pageUrl: String, pageUrl: String,
@ -607,14 +616,6 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
abstract val readerFragmentRoute: String abstract val readerFragmentRoute: String
abstract fun createApplicationShortcuts() abstract fun createApplicationShortcuts()
abstract fun setDialogHostToActivity(alertDialogShower: AlertDialogShower) abstract fun setDialogHostToActivity(alertDialogShower: AlertDialogShower)
abstract fun hideBottomAppBar()
/** abstract fun showBottomAppBar()
* This is for showing and hiding the bottomNavigationView when user scroll the screen.
* We are making this abstract so that it can be easily used from the reader screen.
* Since we do not have the bottomNavigationView in custom apps. So doing this way both apps will
* provide there own implementation.
*
* TODO we will remove this once we will migrate mainActivity to the compose.
*/
abstract fun toggleBottomNavigation(isVisible: Boolean)
} }

View File

@ -1,91 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 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.core.main
import android.annotation.SuppressLint
import android.content.Context
import org.kiwix.kiwixmobile.core.utils.files.Log
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import kotlin.math.abs
open class OnSwipeTouchListener constructor(context: Context) : OnTouchListener {
private val gestureDetector: GestureDetector
init {
gestureDetector = GestureDetector(context, GestureListener())
}
@SuppressLint("ClickableViewAccessibility") override fun onTouch(
view: View,
event: MotionEvent
): Boolean = gestureDetector.onTouchEvent(event)
private inner class GestureListener : SimpleOnGestureListener() {
private val tag = "GestureListener"
private val swipeThreshold = 100
private val swipeVelocityThreshold = 100
override fun onDown(event: MotionEvent): Boolean = true
override fun onSingleTapUp(event: MotionEvent): Boolean {
onTap(event)
return super.onSingleTapUp(event)
}
// See:- https://stackoverflow.com/questions/73463685/gesturedetector-ongesturelistener-overridden-methods-are-not-working-in-android
@Suppress("NOTHING_TO_OVERRIDE", "ACCIDENTAL_OVERRIDE", "NestedBlockDepth", "ReturnCount")
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
try {
val diffY = e2.y - e1.y
val diffX = e2.x - e1.x
if (abs(diffX) > abs(diffY)) {
if (abs(diffX) > swipeThreshold && abs(velocityX) > swipeVelocityThreshold) {
if (diffX > 0) {
onSwipeRight()
} else {
onSwipeLeft()
}
return true
}
} else if (abs(diffY) > swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
if (diffY > 0) {
onSwipeBottom()
}
return true
}
} catch (exception: Exception) {
Log.e(tag, "Exception in onFling", exception)
}
return false
}
}
open fun onSwipeRight() {}
open fun onSwipeLeft() {}
open fun onSwipeBottom() {}
open fun onTap(e: MotionEvent?) {}
}

View File

@ -741,10 +741,10 @@ abstract class CoreReaderFragment :
} }
private fun showTabSwitcher() { private fun showTabSwitcher() {
(requireActivity() as CoreMainActivity).disableDrawer() (requireActivity() as CoreMainActivity).apply {
// Set a negative top margin to the web views to remove disableDrawer()
// the unwanted blank space caused by the toolbar. hideBottomAppBar()
// setTopMarginToWebViews(-requireActivity().getToolbarHeight()) }
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
readerScreenState.update { readerScreenState.update {
copy( copy(
@ -787,6 +787,7 @@ abstract class CoreReaderFragment :
*/ */
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) { protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
setUpDrawerToggle() setUpDrawerToggle()
(requireActivity() as CoreMainActivity).showBottomAppBar()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
readerScreenState.update { readerScreenState.update {
copy( copy(
@ -1569,7 +1570,10 @@ abstract class CoreReaderFragment :
@Suppress("MagicNumber") @Suppress("MagicNumber")
protected open fun openFullScreen() { protected open fun openFullScreen() {
(requireActivity() as CoreMainActivity).disableDrawer(false) (requireActivity() as CoreMainActivity).apply {
disableDrawer(false)
hideBottomAppBar()
}
readerScreenState.update { readerScreenState.update {
copy( copy(
shouldShowBottomAppBar = false, shouldShowBottomAppBar = false,
@ -1588,6 +1592,7 @@ abstract class CoreReaderFragment :
@Suppress("MagicNumber") @Suppress("MagicNumber")
open fun closeFullScreen() { open fun closeFullScreen() {
setUpDrawerToggle() setUpDrawerToggle()
(requireActivity() as CoreMainActivity).showBottomAppBar()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
sharedPreferenceUtil?.putPrefFullScreen(false) sharedPreferenceUtil?.putPrefFullScreen(false)
updateBottomToolbarVisibility() updateBottomToolbarVisibility()

View File

@ -226,7 +226,7 @@ class SearchFragment : BaseFragment() {
private fun goBack() { private fun goBack() {
val readerFragmentRoute = (activity as CoreMainActivity).readerFragmentRoute val readerFragmentRoute = (activity as CoreMainActivity).readerFragmentRoute
findNavController().popBackStack(readerFragmentRoute, false) (requireActivity() as CoreMainActivity).navController.popBackStack(readerFragmentRoute, false)
} }
private fun getSearchListItemForQuery(query: String): SearchListItem? = private fun getSearchListItemForQuery(query: String): SearchListItem? =

View File

@ -109,9 +109,6 @@ object ComposeDimens {
// BookItem dimens // BookItem dimens
val BOOK_ICON_SIZE = 48.dp val BOOK_ICON_SIZE = 48.dp
// LocalLibraryFragment dimens
val FAB_ICON_BOTTOM_MARGIN = 50.dp
// HelpFragment dimens // HelpFragment dimens
val HELP_SCREEN_DIVIDER_HEIGHT = 0.7.dp val HELP_SCREEN_DIVIDER_HEIGHT = 0.7.dp
val HELP_SCREEN_ITEM_TITLE_TEXT_SIZE = 20.sp val HELP_SCREEN_ITEM_TITLE_TEXT_SIZE = 20.sp

View File

@ -285,8 +285,16 @@ class CustomMainActivity : CoreMainActivity() {
activityCustomMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0) activityCustomMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0)
} }
override fun toggleBottomNavigation(isVisible: Boolean) { override fun openSearch(searchString: String, isOpenedFromTabView: Boolean, isVoice: Boolean) {
// Do nothing as we do not have the bottomNavigationView in custom apps. // TODO implement when refactoring the custom app UI.
}
override fun hideBottomAppBar() {
// Do nothing since custom apps does not have the bottomAppBar.
}
override fun showBottomAppBar() {
// Do nothing since custom apps does not have the bottomAppBar.
} }
// Outdated shortcut id(new_tab) // Outdated shortcut id(new_tab)

View File

@ -36,6 +36,7 @@ import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.extensions.browserIntent import org.kiwix.kiwixmobile.core.extensions.browserIntent
import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.update import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
import org.kiwix.kiwixmobile.core.main.reader.ReaderMenuState import org.kiwix.kiwixmobile.core.main.reader.ReaderMenuState
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin
@ -272,7 +273,8 @@ class CustomReaderFragment : CoreReaderFragment() {
}, },
onNoFilesFound = { onNoFilesFound = {
if (sharedPreferenceUtil?.prefIsTest == false) { if (sharedPreferenceUtil?.prefIsTest == false) {
findNavController().navigate(R.id.customDownloadFragment) // TODO refactor this when migrating the custom app in compose
// (requireActivity() as CoreMainActivity).navigate(R.id.customDownloadFragment)
} }
} }
) )