Added a Table of Contents drawer to the reader screen and refactored all related code. * Fixed: BottomAppBar was not showing when hiding the tab switcher (tabs view).

* Removed some unused code from the project.
* Fixed: Some lint, and detekt issues in kiwix app.
This commit is contained in:
MohitMaliFtechiz 2025-07-26 21:18:38 +05:30
parent 078ecb48cf
commit f041ee9cf0
14 changed files with 217 additions and 181 deletions

View File

@ -23,7 +23,6 @@ import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -354,7 +353,6 @@ class KiwixMainActivity : CoreMainActivity() {
} }
override fun showBottomAppBar() { override fun showBottomAppBar() {
Log.e("SHOW_BOTTOM_APP", "showBottomAppBar: ")
shouldShowBottomAppBar.update { true } shouldShowBottomAppBar.update { true }
} }

View File

@ -18,7 +18,6 @@
package org.kiwix.kiwixmobile.main package org.kiwix.kiwixmobile.main
import android.util.Log
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -67,7 +66,6 @@ fun KiwixMainActivityScreen(
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
val shouldShowBottomBar = currentRoute in topLevelDestinationsRoute && shouldShowBottomAppBar val shouldShowBottomBar = currentRoute in topLevelDestinationsRoute && shouldShowBottomAppBar
Log.e("CURRENT_DESTINATION", "KiwixMainActivityScreen: $currentRoute and $shouldShowBottomAppBar")
KiwixTheme { KiwixTheme {
ModalNavigationDrawer( ModalNavigationDrawer(
drawerState = leftDrawerState, drawerState = leftDrawerState,

View File

@ -175,6 +175,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
*/ */
override fun hideTabSwitcher(shouldCloseZimBook: Boolean) { override fun hideTabSwitcher(shouldCloseZimBook: Boolean) {
activity?.setupDrawerToggle(true) activity?.setupDrawerToggle(true)
(requireActivity() as CoreMainActivity).showBottomAppBar()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
if (webViewList.isEmpty()) { if (webViewList.isEmpty()) {
readerMenuState?.hideTabSwitcher() readerMenuState?.hideTabSwitcher()
@ -195,23 +196,6 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
} }
private fun setFragmentContainerBottomMarginToSizeOfNavBar() {
// val bottomNavigationView =
// requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view)
// bottomNavigationView?.let {
// setBottomMarginToNavHostContainer(
// bottomNavigationView.measuredHeight
// )
// }
}
// override fun onPause() {
// super.onPause()
// // ScrollingViewWithBottomNavigationBehavior changes the margin to the size of the nav bar,
// // this resets the margin to zero, before fragment navigation.
// setBottomMarginToNavHostContainer(ZERO)
// }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
super.onCreateOptionsMenu(menu, menuInflater) super.onCreateOptionsMenu(menu, menuInflater)
@ -311,28 +295,20 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun closeFullScreen() { override fun closeFullScreen() {
super.closeFullScreen() super.closeFullScreen()
showNavBar() showNavBar()
setFragmentContainerBottomMarginToSizeOfNavBar()
} }
private fun hideNavBar() { private fun hideNavBar() {
// requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility = GONE (requireActivity() as CoreMainActivity).hideBottomAppBar()
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() as CoreMainActivity).showBottomAppBar()
// VISIBLE
} }
} }
override fun createNewTab() { override fun createNewTab() {
newMainPageTab() newMainPageTab()
} }
private fun setBottomMarginToNavHostContainer(margin: Int) {
// coreMainActivity.navHostContainer
// .setBottomMarginToFragmentContainerView(margin)
}
} }

View File

@ -87,11 +87,26 @@ fun KiwixNavGraph(
composable( composable(
route = KiwixDestination.Reader.route, route = KiwixDestination.Reader.route,
arguments = listOf( arguments = listOf(
navArgument(ZIM_FILE_URI_KEY) { type = NavType.StringType; defaultValue = "" }, navArgument(ZIM_FILE_URI_KEY) {
navArgument(FIND_IN_PAGE_SEARCH_STRING) { type = NavType.StringType; defaultValue = "" }, type = NavType.StringType
navArgument(PAGE_URL_KEY) { type = NavType.StringType; defaultValue = "" }, defaultValue = ""
navArgument(SHOULD_OPEN_IN_NEW_TAB) { type = NavType.BoolType; defaultValue = false }, },
navArgument(SEARCH_ITEM_TITLE_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 -> ) { backStackEntry ->
val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty() val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty()
@ -116,7 +131,10 @@ fun KiwixNavGraph(
composable( composable(
route = KiwixDestination.Library.route, route = KiwixDestination.Library.route,
arguments = listOf( arguments = listOf(
navArgument(ZIM_FILE_URI_KEY) { type = NavType.StringType; defaultValue = "" } navArgument(ZIM_FILE_URI_KEY) {
type = NavType.StringType
defaultValue = ""
}
) )
) { backStackEntry -> ) { backStackEntry ->
val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty() val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty()
@ -177,9 +195,18 @@ fun KiwixNavGraph(
composable( composable(
route = KiwixDestination.Search.route, route = KiwixDestination.Search.route,
arguments = listOf( arguments = listOf(
navArgument(NAV_ARG_SEARCH_STRING) { type = NavType.StringType; defaultValue = "" }, navArgument(NAV_ARG_SEARCH_STRING) {
navArgument(TAG_FROM_TAB_SWITCHER) { type = NavType.BoolType; defaultValue = false }, type = NavType.StringType
navArgument(EXTRA_IS_WIDGET_VOICE) { type = NavType.BoolType; defaultValue = false } defaultValue = ""
},
navArgument(TAG_FROM_TAB_SWITCHER) {
type = NavType.BoolType
defaultValue = false
},
navArgument(EXTRA_IS_WIDGET_VOICE) {
type = NavType.BoolType
defaultValue = false
}
) )
) { backStackEntry -> ) { backStackEntry ->
val searchString = backStackEntry.arguments?.getString(NAV_ARG_SEARCH_STRING).orEmpty() val searchString = backStackEntry.arguments?.getString(NAV_ARG_SEARCH_STRING).orEmpty()
@ -314,10 +341,11 @@ sealed class KiwixDestination(val route: String) {
object LocalFileTransfer : KiwixDestination("$LOCAL_FILE_TRANSFER_FRAGMENT?uris={uris}") { object LocalFileTransfer : KiwixDestination("$LOCAL_FILE_TRANSFER_FRAGMENT?uris={uris}") {
fun createRoute(uris: String? = null): String { fun createRoute(uris: String? = null): String {
return if (uris != null) return if (uris != null) {
"$LOCAL_FILE_TRANSFER_FRAGMENT?uris=${Uri.encode(uris)}" "$LOCAL_FILE_TRANSFER_FRAGMENT?uris=${Uri.encode(uris)}"
else } else {
"$LOCAL_FILE_TRANSFER_FRAGMENT?uris=null" "$LOCAL_FILE_TRANSFER_FRAGMENT?uris=null"
}
} }
} }
} }

View File

@ -13,7 +13,6 @@
<ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList&lt;KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID> <ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList&lt;KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID>
<ID>LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )</ID> <ID>LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )</ID>
<ID>LongParameterList:Repository.kt$Repository$( private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID> <ID>LongParameterList:Repository.kt$Repository$( private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID>
<ID>LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, videoView: ViewGroup?, webViewClient: CoreWebViewClient, sharedPreferenceUtil: SharedPreferenceUtil )</ID>
<ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID> <ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID>
<ID>MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</ID> <ID>MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</ID>
<ID>MagicNumber:DownloadItem.kt$DownloadItem$1000L</ID> <ID>MagicNumber:DownloadItem.kt$DownloadItem$1000L</ID>
@ -57,7 +56,6 @@
<ID>ReturnCount:FileUtils.kt$FileUtils$@Synchronized private fun deleteZimFileParts(path: String): Boolean</ID> <ID>ReturnCount:FileUtils.kt$FileUtils$@Synchronized private fun deleteZimFileParts(path: String): Boolean</ID>
<ID>ReturnCount:ImageUtils.kt$ImageUtils$private fun getBitmapFromView(width: Int, height: Int, viewToDrawFrom: View): Bitmap?</ID> <ID>ReturnCount:ImageUtils.kt$ImageUtils$private fun getBitmapFromView(width: Int, height: Int, viewToDrawFrom: View): Bitmap?</ID>
<ID>ReturnCount:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$override fun onFling( e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean</ID> <ID>ReturnCount:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$override fun onFling( e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean</ID>
<ID>ReturnCount:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$@SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean</ID>
<ID>TooGenericExceptionCaught:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$exception: Exception</ID> <ID>TooGenericExceptionCaught:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$exception: Exception</ID>
<ID>TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: Exception</ID> <ID>TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: Exception</ID>
<ID>TooGenericExceptionCaught:LibkiwixBookmarks.kt$LibkiwixBookmarks$exception: Exception</ID> <ID>TooGenericExceptionCaught:LibkiwixBookmarks.kt$LibkiwixBookmarks$exception: Exception</ID>

View File

@ -35,7 +35,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
@ -249,19 +248,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
} }
} }
open fun configureActivityBasedOn(destination: NavDestination) { @Suppress("UnusedParameter")
// if (destination.id !in topLevelDestinations) {
// handleDrawerOnNavigation()
// }
// readerTableOfContentsDrawer.setLockMode(
// if (destination.id == readerFragmentResId) {
// LOCK_MODE_UNLOCKED
// } else {
// LOCK_MODE_LOCKED_CLOSED
// }
// )
}
private fun NavigationView.setLockMode(lockMode: Int) { private fun NavigationView.setLockMode(lockMode: Int) {
// drawerContainerLayout.setDrawerLockMode(lockMode, this) // drawerContainerLayout.setDrawerLockMode(lockMode, this)
} }
@ -455,14 +442,6 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
navController.navigate(route, navOptions) navController.navigate(route, navOptions)
} }
fun navigate(fragmentId: Int, bundle: Bundle) {
navController.navigate(fragmentId, bundle)
}
fun navigate(fragmentId: Int, bundle: Bundle, navOptions: NavOptions) {
navController.navigate(fragmentId, bundle, navOptions)
}
private fun openSettings() { private fun openSettings() {
handleDrawerOnNavigation() handleDrawerOnNavigation()
navigate(settingsFragmentRoute) navigate(settingsFragmentRoute)
@ -492,15 +471,13 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
.setLaunchSingleTop(true) .setLaunchSingleTop(true)
.setPopUpTo(readerFragmentRoute, inclusive = true) .setPopUpTo(readerFragmentRoute, inclusive = true)
.build() .build()
// navigate( val readerRouteWithArguments =
// readerFragmentRoute, "$readerFragmentRoute?$PAGE_URL_KEY=$pageUrl&$ZIM_FILE_URI_KEY=$zimFileUri" +
// bundleOf( "&$SHOULD_OPEN_IN_NEW_TAB=$shouldOpenInNewTab"
// PAGE_URL_KEY to pageUrl, navigate(
// ZIM_FILE_URI_KEY to zimFileUri, readerRouteWithArguments,
// SHOULD_OPEN_IN_NEW_TAB to shouldOpenInNewTab navOptions
// ), )
// navOptions
// )
} }
private fun openBookmarks() { private fun openBookmarks() {
@ -523,7 +500,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
} }
fun findInPage(searchString: String) { fun findInPage(searchString: String) {
// navigate(readerFragmentRoute, bundleOf(FIND_IN_PAGE_SEARCH_STRING to searchString)) navigate("$readerFragmentRoute?$FIND_IN_PAGE_SEARCH_STRING=$searchString")
} }
private val bookRelatedDrawerGroup by lazy { private val bookRelatedDrawerGroup by lazy {

View File

@ -22,15 +22,14 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import android.webkit.WebView import android.webkit.WebView
import org.kiwix.kiwixmobile.core.main.reader.DocumentSection
import kotlin.collections.List import kotlin.collections.List
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection
class DocumentParser(private var listener: DocumentParser.SectionsListener) { class DocumentParser(private var listener: DocumentParser.SectionsListener) {
private var title: String = "" private var title: String = ""
@Suppress("DoubleMutabilityForCollection") @Suppress("DoubleMutabilityForCollection")
private var sections = ArrayList<TableDrawerAdapter.DocumentSection>() private var sections = ArrayList<DocumentSection>()
fun initInterface(webView: WebView) { fun initInterface(webView: WebView) {
webView.addJavascriptInterface(ParserCallback(), "DocumentParser") webView.addJavascriptInterface(ParserCallback(), "DocumentParser")

View File

@ -54,6 +54,7 @@ import javax.inject.Inject
private const val INITIAL_SCALE = 100 private const val INITIAL_SCALE = 100
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
@Suppress("LongParameterList")
open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor( open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
context: Context, context: Context,
private val callback: WebViewCallback, private val callback: WebViewCallback,

View File

@ -62,6 +62,7 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -71,10 +72,8 @@ import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.navigation.NavigationView
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -124,9 +123,6 @@ import org.kiwix.kiwixmobile.core.main.KiwixTextToSpeech.OnSpeakingListener
import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.main.MainRepositoryActions import org.kiwix.kiwixmobile.core.main.MainRepositoryActions
import org.kiwix.kiwixmobile.core.main.ServiceWorkerUninitialiser import org.kiwix.kiwixmobile.core.main.ServiceWorkerUninitialiser
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener
import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS
import org.kiwix.kiwixmobile.core.main.WebViewCallback import org.kiwix.kiwixmobile.core.main.WebViewCallback
import org.kiwix.kiwixmobile.core.main.WebViewProvider import org.kiwix.kiwixmobile.core.main.WebViewProvider
@ -199,7 +195,6 @@ abstract class CoreReaderFragment :
private val webUrlsFlow = MutableStateFlow("") private val webUrlsFlow = MutableStateFlow("")
var drawerLayout: DrawerLayout? = null var drawerLayout: DrawerLayout? = null
protected var tableDrawerRightContainer: NavigationView? = null
@JvmField @JvmField
@Inject @Inject
@ -256,7 +251,7 @@ abstract class CoreReaderFragment :
@Inject @Inject
var unsupportedMimeTypeHandler: UnsupportedMimeTypeHandler? = null var unsupportedMimeTypeHandler: UnsupportedMimeTypeHandler? = null
private var hideBackToTopTimer: CountDownTimer? = null private var hideBackToTopTimer: CountDownTimer? = null
private val documentSections: MutableList<DocumentSection>? = ArrayList() private val documentSections: SnapshotStateList<DocumentSection>? = mutableStateListOf()
private var isBackToTopEnabled = false private var isBackToTopEnabled = false
private var isOpenNewTabInBackground = false private var isOpenNewTabInBackground = false
private var documentParserJs: String? = null private var documentParserJs: String? = null
@ -269,8 +264,6 @@ abstract class CoreReaderFragment :
private val tempWebViewListForUndo: MutableList<KiwixWebView> = ArrayList() private val tempWebViewListForUndo: MutableList<KiwixWebView> = ArrayList()
private var tempZimSourceForUndo: ZimReaderSource? = null private var tempZimSourceForUndo: ZimReaderSource? = null
private var isFirstRun = false private var isFirstRun = false
private var tableDrawerAdapter: TableDrawerAdapter? = null
private var tableDrawerRight: RecyclerView? = null
private var bookmarkingJob: Job? = null private var bookmarkingJob: Job? = null
private var isBookmarked = false private var isBookmarked = false
private lateinit var serviceConnection: ServiceConnection private lateinit var serviceConnection: ServiceConnection
@ -279,6 +272,7 @@ abstract class CoreReaderFragment :
private var isReadSelection = false private var isReadSelection = false
private var isReadAloudServiceRunning = false private var isReadAloudServiceRunning = false
private var libkiwixBook: Book? = null private var libkiwixBook: Book? = null
private var shouldTableOfContentDrawer = mutableStateOf(false)
protected var readerMenuState: ReaderMenuState? = null protected var readerMenuState: ReaderMenuState? = null
private var composeView: ComposeView? = null private var composeView: ComposeView? = null
@ -296,7 +290,7 @@ abstract class CoreReaderFragment :
onExitFullscreenClick = { closeFullScreen() }, onExitFullscreenClick = { closeFullScreen() },
showTtsControls = false, showTtsControls = false,
onPauseTtsClick = { pauseTts() }, onPauseTtsClick = { pauseTts() },
pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty(), pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty(),
onStopTtsClick = { stopTts() }, onStopTtsClick = { stopTts() },
kiwixWebViewList = webViewList, kiwixWebViewList = webViewList,
bookmarkButtonItem = Triple( bookmarkButtonItem = Triple(
@ -335,7 +329,10 @@ abstract class CoreReaderFragment :
}, },
appName = "", appName = "",
donateButtonClick = {}, donateButtonClick = {},
laterButtonClick = {} laterButtonClick = {},
tableOfContentTitle = "",
tableContentHeaderClick = { tableOfContentHeaderClick() },
tableOfContentSectionClick = { tableOfContentSectionClick(it) },
) )
) )
private var readerLifeCycleScope: CoroutineScope? = null private var readerLifeCycleScope: CoroutineScope? = null
@ -362,12 +359,12 @@ abstract class CoreReaderFragment :
* 2) Permission has been disabled on device * 2) Permission has been disabled on device
*/ */
requireActivity().toast( requireActivity().toast(
R.string.ext_storage_permission_rationale_add_note, string.ext_storage_permission_rationale_add_note,
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
} else { } else {
requireActivity().toast( requireActivity().toast(
R.string.ext_storage_write_permission_denied_add_note, string.ext_storage_write_permission_denied_add_note,
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
alertDialogShower?.show( alertDialogShower?.show(
@ -447,7 +444,7 @@ abstract class CoreReaderFragment :
readerScreenState.update { readerScreenState.update {
copy( copy(
bottomNavigationHeight = getBottomNavigationHeight(), bottomNavigationHeight = getBottomNavigationHeight(),
readerScreenTitle = context.getString(R.string.reader), readerScreenTitle = context.getString(string.reader),
darkModeViewPainter = darkModeViewPainter, darkModeViewPainter = darkModeViewPainter,
fullScreenItem = fullScreenItem.first to getVideoView(), fullScreenItem = fullScreenItem.first to getVideoView(),
tocButtonItem = getTocButtonStateAndAction(), tocButtonItem = getTocButtonStateAndAction(),
@ -483,7 +480,9 @@ abstract class CoreReaderFragment :
iconTint = navigationIconTint() iconTint = navigationIconTint()
) )
}, },
mainActivityBottomAppBarScrollBehaviour = (requireActivity() as CoreMainActivity).bottomAppBarScrollBehaviour mainActivityBottomAppBarScrollBehaviour = (requireActivity() as CoreMainActivity).bottomAppBarScrollBehaviour,
documentSections = documentSections,
showTableOfContentDrawer = shouldTableOfContentDrawer,
) )
DialogHost(alertDialogShower as AlertDialogShower) DialogHost(alertDialogShower as AlertDialogShower)
} }
@ -497,10 +496,7 @@ abstract class CoreReaderFragment :
handleLocaleCheck() handleLocaleCheck()
initHideBackToTopTimer() initHideBackToTopTimer()
loadDrawerViews() loadDrawerViews()
tableDrawerRight =
tableDrawerRightContainer?.getHeaderView(0)?.findViewById(R.id.right_drawer_list)
addFileReader() addFileReader()
setTableDrawerInfo()
activity?.let { activity?.let {
compatCallback = CompatFindActionModeCallback(it) compatCallback = CompatFindActionModeCallback(it)
} }
@ -567,7 +563,7 @@ abstract class CoreReaderFragment :
private fun navigationIconContentDescription() = private fun navigationIconContentDescription() =
if (readerMenuState?.isInTabSwitcher == true) { if (readerMenuState?.isInTabSwitcher == true) {
R.string.search_open_in_new_tab string.search_open_in_new_tab
} else { } else {
string.open_drawer string.open_drawer
} }
@ -665,54 +661,36 @@ abstract class CoreReaderFragment :
sections: List<DocumentSection> sections: List<DocumentSection>
) { ) {
if (isAdded) { if (isAdded) {
documentSections?.let { documentSections?.addAll(sections)
it.addAll(sections) readerScreenState.update { copy(tableOfContentTitle = title) }
tableDrawerAdapter?.setTitle(title)
tableDrawerAdapter?.setSections(it)
tableDrawerAdapter?.notifyDataSetChanged()
}
} }
} }
override fun clearSections() { override fun clearSections() {
documentSections?.clear() documentSections?.clear()
tableDrawerAdapter?.notifyDataSetChanged()
} }
}) })
} }
private fun setTableDrawerInfo() {
tableDrawerRight?.apply {
layoutManager = LinearLayoutManager(requireActivity())
tableDrawerAdapter = setupTableDrawerAdapter()
adapter = tableDrawerAdapter
tableDrawerAdapter?.notifyDataSetChanged()
}
}
private fun addFileReader() { private fun addFileReader() {
documentParserJs = requireActivity().readFile("js/documentParser.js") documentParserJs = requireActivity().readFile("js/documentParser.js")
documentSections?.clear() documentSections?.clear()
} }
private fun setupTableDrawerAdapter(): TableDrawerAdapter { private fun tableOfContentHeaderClick() {
return TableDrawerAdapter(object : TableClickListener { getCurrentWebView()?.scrollY = 0
override fun onHeaderClick(view: View?) { shouldTableOfContentDrawer.update { false }
getCurrentWebView()?.scrollY = 0 }
drawerLayout?.closeDrawer(GravityCompat.END)
}
override fun onSectionClick(view: View?, position: Int) { private fun tableOfContentSectionClick(position: Int) {
if (hasItemForPositionInDocumentSectionsList(position)) { // Bug Fix #3796 if (hasItemForPositionInDocumentSectionsList(position)) { // Bug Fix #3796
loadUrlWithCurrentWebview( loadUrlWithCurrentWebview(
"javascript:document.getElementById('" + "javascript:document.getElementById('" +
documentSections?.get(position)?.id?.replace("'", "\\'") + documentSections?.get(position)?.id?.replace("'", "\\'") +
"').scrollIntoView();" "').scrollIntoView();"
) )
} }
drawerLayout?.closeDrawers() shouldTableOfContentDrawer.update { false }
}
})
} }
private fun hasItemForPositionInDocumentSectionsList(position: Int): Boolean { private fun hasItemForPositionInDocumentSectionsList(position: Int): Boolean {
@ -772,7 +750,6 @@ abstract class CoreReaderFragment :
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) { protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
setUpDrawerToggle() setUpDrawerToggle()
(requireActivity() as CoreMainActivity).showBottomAppBar() (requireActivity() as CoreMainActivity).showBottomAppBar()
Log.e("SHOW_BOTTOM_APP", "hideTabSwitcher: ")
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
readerScreenState.update { readerScreenState.update {
copy( copy(
@ -862,9 +839,9 @@ abstract class CoreReaderFragment :
**/ **/
val dialogFragment = NavigationHistoryDialog( val dialogFragment = NavigationHistoryDialog(
if (isForwardHistory) { if (isForwardHistory) {
R.string.forward_history string.forward_history
} else { } else {
R.string.backward_history string.backward_history
}, },
navigationHistoryList, navigationHistoryList,
this this
@ -885,7 +862,7 @@ abstract class CoreReaderFragment :
repositoryActions?.clearWebViewPageHistory() repositoryActions?.clearWebViewPageHistory()
} }
updateBottomToolbarArrowsAlpha() updateBottomToolbarArrowsAlpha()
toast(R.string.navigation_history_cleared) toast(string.navigation_history_cleared)
} }
@Suppress("MagicNumber") @Suppress("MagicNumber")
@ -900,7 +877,7 @@ abstract class CoreReaderFragment :
} }
protected fun openToc() { protected fun openToc() {
drawerLayout?.openDrawer(GravityCompat.END) shouldTableOfContentDrawer.update { true }
} }
@Suppress("ReturnCount", "NestedBlockDepth") @Suppress("ReturnCount", "NestedBlockDepth")
@ -1029,7 +1006,7 @@ abstract class CoreReaderFragment :
readerScreenState.update { readerScreenState.update {
copy( copy(
showTtsControls = false, showTtsControls = false,
pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty() pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty()
) )
} }
setActionAndStartTTSService(ACTION_STOP_TTS) setActionAndStartTTSService(ACTION_STOP_TTS)
@ -1048,14 +1025,14 @@ abstract class CoreReaderFragment :
AudioManager.AUDIOFOCUS_LOSS -> { AudioManager.AUDIOFOCUS_LOSS -> {
if (tts?.currentTTSTask?.paused == false) tts?.pauseOrResume() if (tts?.currentTTSTask?.paused == false) tts?.pauseOrResume()
readerScreenState.update { readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_resume).orEmpty()) copy(pauseTtsButtonText = context?.getString(string.tts_resume).orEmpty())
} }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true) setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true)
} }
AudioManager.AUDIOFOCUS_GAIN -> { AudioManager.AUDIOFOCUS_GAIN -> {
readerScreenState.update { readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty()) copy(pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty())
} }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false) setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false)
} }
@ -1090,13 +1067,13 @@ abstract class CoreReaderFragment :
if (it.paused) { if (it.paused) {
tts?.pauseOrResume() tts?.pauseOrResume()
readerScreenState.update { readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty()) copy(pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty())
} }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false) setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false)
} else { } else {
tts?.pauseOrResume() tts?.pauseOrResume()
readerScreenState.update { readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_resume).orEmpty()) copy(pauseTtsButtonText = context?.getString(string.tts_resume).orEmpty())
} }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true) setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true)
} }
@ -1135,8 +1112,6 @@ abstract class CoreReaderFragment :
hideBackToTopTimer?.cancel() hideBackToTopTimer?.cancel()
hideBackToTopTimer = null hideBackToTopTimer = null
stopOngoingLoadingAndClearWebViewList() stopOngoingLoadingAndClearWebViewList()
tableDrawerRight?.adapter = null
tableDrawerAdapter = null
tempWebViewListForUndo.clear() tempWebViewListForUndo.clear()
// create a base Activity class that class this. // create a base Activity class that class this.
deleteCachedFiles(requireActivity()) deleteCachedFiles(requireActivity())
@ -1166,7 +1141,6 @@ abstract class CoreReaderFragment :
compatCallback?.finish() compatCallback?.finish()
compatCallback = null compatCallback = null
drawerLayout = null drawerLayout = null
tableDrawerRightContainer = null
} }
private fun updateTableOfContents() { private fun updateTableOfContents() {
@ -1281,8 +1255,8 @@ abstract class CoreReaderFragment :
currentWebViewIndex-- currentWebViewIndex--
} }
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
requireActivity().getString(R.string.tab_closed), requireActivity().getString(string.tab_closed),
actionLabel = requireActivity().getString(R.string.undo), actionLabel = requireActivity().getString(string.undo),
actionClick = { restoreDeletedTab(index) }, actionClick = { restoreDeletedTab(index) },
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
snackBarResult = { result -> snackBarResult = { result ->
@ -1307,7 +1281,7 @@ abstract class CoreReaderFragment :
readerScreenState.update { readerScreenState.update {
copy( copy(
shouldShowBottomAppBar = false, shouldShowBottomAppBar = false,
readerScreenTitle = context?.getString(R.string.reader).orEmpty() readerScreenTitle = context?.getString(string.reader).orEmpty()
) )
} }
hideProgressBar() hideProgressBar()
@ -1342,7 +1316,7 @@ abstract class CoreReaderFragment :
tempWebViewForUndo?.let { tempWebViewForUndo?.let {
webViewList.add(index, it) webViewList.add(index, it)
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tab_restored).orEmpty(), context?.getString(string.tab_restored).orEmpty(),
lifecycleScope = lifecycleScope lifecycleScope = lifecycleScope
) )
setUpWithTextToSpeech(it) setUpWithTextToSpeech(it)
@ -1421,7 +1395,7 @@ abstract class CoreReaderFragment :
hideBackToTopButton() hideBackToTopButton()
} }
readerScreenState.update { readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty()) copy(pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty())
} }
if (tts?.isInitialized == false) { if (tts?.isInitialized == false) {
isReadSelection = false isReadSelection = false
@ -1621,7 +1595,7 @@ abstract class CoreReaderFragment :
exitBook() exitBook()
Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + zimReaderSource.toDatabase()) Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + zimReaderSource.toDatabase())
requireActivity().toast( requireActivity().toast(
getString(R.string.error_file_not_found, zimReaderSource.toDatabase()), getString(string.error_file_not_found, zimReaderSource.toDatabase()),
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
} }
@ -1664,13 +1638,13 @@ abstract class CoreReaderFragment :
} }
readerMenuState?.onFileOpened(urlIsValid()) readerMenuState?.onFileOpened(urlIsValid())
setUpBookmarks(zimFileReader) setUpBookmarks(zimFileReader)
} ?: kotlin.run { } ?: run {
// If the ZIM file is not opened properly (especially for ZIM chunks), exit the book to // If the ZIM file is not opened properly (especially for ZIM chunks), exit the book to
// disable all controls for this ZIM file. This prevents potential crashes. // disable all controls for this ZIM file. This prevents potential crashes.
// See issue #4161 for more details. // See issue #4161 for more details.
exitBook() exitBook()
requireActivity().toast( requireActivity().toast(
getString(R.string.error_file_invalid, zimReaderSource.toDatabase()), getString(string.error_file_invalid, zimReaderSource.toDatabase()),
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
} }
@ -1758,8 +1732,8 @@ abstract class CoreReaderFragment :
} }
} else { } else {
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.request_storage).orEmpty(), context?.getString(string.request_storage).orEmpty(),
context?.getString(R.string.menu_settings), context?.getString(string.menu_settings),
snackbarDuration = SnackbarDuration.Long, snackbarDuration = SnackbarDuration.Long,
actionClick = { actionClick = {
val intent = Intent() val intent = Intent()
@ -1791,8 +1765,8 @@ abstract class CoreReaderFragment :
webViewList.clear() webViewList.clear()
openHomeScreen() openHomeScreen()
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tabs_closed).orEmpty(), context?.getString(string.tabs_closed).orEmpty(),
context?.getString(R.string.undo), context?.getString(string.undo),
actionClick = { restoreDeletedTabs() }, actionClick = { restoreDeletedTabs() },
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
snackBarResult = { result -> snackBarResult = { result ->
@ -1810,7 +1784,7 @@ abstract class CoreReaderFragment :
if (tempWebViewListForUndo.isNotEmpty()) { if (tempWebViewListForUndo.isNotEmpty()) {
webViewList.addAll(tempWebViewListForUndo) webViewList.addAll(tempWebViewListForUndo)
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tabs_restored).orEmpty(), context?.getString(string.tabs_restored).orEmpty(),
lifecycleScope = lifecycleScope lifecycleScope = lifecycleScope
) )
reopenBook() reopenBook()
@ -1850,7 +1824,7 @@ abstract class CoreReaderFragment :
if (isBookmarked) { if (isBookmarked) {
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl) repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.bookmark_removed).orEmpty(), context?.getString(string.bookmark_removed).orEmpty(),
lifecycleScope = lifecycleScope lifecycleScope = lifecycleScope
) )
} else { } else {
@ -1859,22 +1833,22 @@ abstract class CoreReaderFragment :
LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook) LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook)
) )
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.bookmark_added).orEmpty(), context?.getString(string.bookmark_added).orEmpty(),
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
actionLabel = context?.getString(R.string.open), actionLabel = context?.getString(string.open),
actionClick = { goToBookmarks() } actionClick = { goToBookmarks() }
) )
} }
} }
} }
} ?: kotlin.run { } ?: run {
requireActivity().toast(R.string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT) requireActivity().toast(string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT)
} }
} }
} catch (_: Exception) { } catch (_: Exception) {
// Catch the exception while saving the bookmarks for splitted zim files. // Catch the exception while saving the bookmarks for splitted zim files.
// we have an issue with split zim files, see #3827 // we have an issue with split zim files, see #3827
requireActivity().toast(R.string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT) requireActivity().toast(string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT)
} }
} }
@ -1952,7 +1926,7 @@ abstract class CoreReaderFragment :
if (item.shouldOpenInNewTab) { if (item.shouldOpenInNewTab) {
createNewTab() createNewTab()
} }
item.pageUrl?.let(::loadUrlWithCurrentWebview) ?: kotlin.run { item.pageUrl?.let(::loadUrlWithCurrentWebview) ?: run {
zimReaderContainer?.titleToUrl(item.pageTitle)?.apply { zimReaderContainer?.titleToUrl(item.pageTitle)?.apply {
loadUrlWithCurrentWebview(zimReaderContainer?.urlSuffixToParsableUrl(this)) loadUrlWithCurrentWebview(zimReaderContainer?.urlSuffixToParsableUrl(this))
} }
@ -2069,7 +2043,7 @@ abstract class CoreReaderFragment :
} else { } else {
contentUrl contentUrl
} }
} ?: kotlin.run { } ?: run {
return@redirectOrOriginal contentUrl return@redirectOrOriginal contentUrl
} }
} }
@ -2088,7 +2062,7 @@ abstract class CoreReaderFragment :
private fun openRandomArticle(retryCount: Int = 2) { private fun openRandomArticle(retryCount: Int = 2) {
// Check if the ZIM file reader is available, if not show an error and exit. // Check if the ZIM file reader is available, if not show an error and exit.
if (zimReaderContainer?.zimFileReader == null) { if (zimReaderContainer?.zimFileReader == null) {
toast(R.string.error_loading_random_article_zim_not_loaded) toast(string.error_loading_random_article_zim_not_loaded)
return return
} }
val articleUrl = zimReaderContainer?.getRandomArticleUrl() val articleUrl = zimReaderContainer?.getRandomArticleUrl()
@ -2105,7 +2079,7 @@ abstract class CoreReaderFragment :
} else { } else {
// if it is failed to find the random article two times then show a error to user. // if it is failed to find the random article two times then show a error to user.
Log.e(TAG_KIWIX, "Failed to load random article after multiple attempts") Log.e(TAG_KIWIX, "Failed to load random article after multiple attempts")
toast(R.string.could_not_find_random_article) toast(string.could_not_find_random_article)
} }
return return
} }
@ -2420,7 +2394,7 @@ abstract class CoreReaderFragment :
Log.d( Log.d(
TAG_KIWIX, TAG_KIWIX,
String.format( String.format(
getString(R.string.error_article_url_not_found), getString(string.error_article_url_not_found),
failingUrl failingUrl
) )
) )
@ -2500,9 +2474,9 @@ abstract class CoreReaderFragment :
if (isOpenNewTabInBackground) { if (isOpenNewTabInBackground) {
newTabInBackground(url) newTabInBackground(url)
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
message = context?.getString(R.string.new_tab_snack_bar).orEmpty(), message = context?.getString(string.new_tab_snack_bar).orEmpty(),
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
actionLabel = context?.getString(R.string.open), actionLabel = context?.getString(string.open),
actionClick = { actionClick = {
if (webViewList.size > 1) { if (webViewList.size > 1) {
selectTab(webViewList.size - 1) selectTab(webViewList.size - 1)
@ -2603,7 +2577,7 @@ abstract class CoreReaderFragment :
readerMenuState?.showWebViewOptions(urlIsValid()) readerMenuState?.showWebViewOptions(urlIsValid())
} catch (ignore: Exception) { } catch (ignore: Exception) {
Log.w(TAG_KIWIX, "Kiwix shared preferences corrupted", ignore) Log.w(TAG_KIWIX, "Kiwix shared preferences corrupted", ignore)
activity.toast(R.string.could_not_restore_tabs, Toast.LENGTH_LONG) activity.toast(string.could_not_restore_tabs, Toast.LENGTH_LONG)
} }
} }
@ -2627,7 +2601,7 @@ abstract class CoreReaderFragment :
webViewHistoryItem?.webViewBackForwardListBundle?.let { bundle -> webViewHistoryItem?.webViewBackForwardListBundle?.let { bundle ->
webView.restoreState(bundle) webView.restoreState(bundle)
webView.scrollY = webViewHistoryItem.webViewCurrentPosition webView.scrollY = webViewHistoryItem.webViewCurrentPosition
} ?: kotlin.run { } ?: run {
zimReaderContainer?.zimFileReader?.let { zimReaderContainer?.zimFileReader?.let {
webView.loadUrl(redirectOrOriginal(contentUrl("${it.mainPage}"))) webView.loadUrl(redirectOrOriginal(contentUrl("${it.mainPage}")))
} }

View File

@ -27,7 +27,9 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
@ -42,6 +44,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -49,6 +52,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@ -68,6 +72,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallFloatingActionButton import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -76,6 +81,7 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -107,13 +113,16 @@ import androidx.compose.ui.zIndex
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED
import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.main.KiwixWebView
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
import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost
import org.kiwix.kiwixmobile.core.ui.components.ONE
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
import org.kiwix.kiwixmobile.core.ui.components.TWELVE
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.ui.models.IconItem.Drawable import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
@ -130,6 +139,7 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.KIWIX_TOOLBAR_HEIGHT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.KIWIX_TOOLBAR_HEIGHT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.NAVIGATION_DRAWER_WIDTH
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA
@ -140,6 +150,7 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TEN_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.THREE_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.THREE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWELVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml
@ -155,6 +166,8 @@ const val TAB_TITLE_TESTING_TAG = "tabTitleTestingTag"
fun ReaderScreen( fun ReaderScreen(
state: ReaderScreenState, state: ReaderScreenState,
actionMenuItems: List<ActionMenuItem>, actionMenuItems: List<ActionMenuItem>,
showTableOfContentDrawer: MutableState<Boolean>,
documentSections: MutableList<DocumentSection>?,
mainActivityBottomAppBarScrollBehaviour: BottomAppBarScrollBehavior?, mainActivityBottomAppBarScrollBehaviour: BottomAppBarScrollBehavior?,
navigationIcon: @Composable () -> Unit navigationIcon: @Composable () -> Unit
) { ) {
@ -185,11 +198,34 @@ fun ReaderScreen(
} }
.semantics { testTag = READER_SCREEN_TESTING_TAG } .semantics { testTag = READER_SCREEN_TESTING_TAG }
) { paddingValues -> ) { paddingValues ->
ReaderContentLayout( Box(Modifier.fillMaxSize()) {
state, ReaderContentLayout(
Modifier.padding(paddingValues), state,
bottomAppBarScrollBehavior Modifier.padding(paddingValues),
) bottomAppBarScrollBehavior
)
AnimatedVisibility(
visible = showTableOfContentDrawer.value,
enter = slideInHorizontally(initialOffsetX = { it }) + fadeIn(),
exit = slideOutHorizontally(targetOffsetX = { it }) + fadeOut(),
modifier = Modifier.align(Alignment.CenterEnd)
) {
TableDrawerSheet(
title = state.tableOfContentTitle,
sections = documentSections.orEmpty(),
onHeaderClick = state.tableContentHeaderClick,
onSectionClick = state.tableOfContentSectionClick
)
}
if (showTableOfContentDrawer.value) {
Box(
Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.3f))
.clickable { showTableOfContentDrawer.update { false } }
)
}
}
} }
} }
} }
@ -255,6 +291,45 @@ private fun ReaderContentLayout(
} }
} }
@Composable
fun TableDrawerSheet(
title: String,
sections: List<DocumentSection>,
onHeaderClick: () -> Unit,
onSectionClick: (Int) -> Unit
) {
ModalDrawerSheet(
modifier = Modifier.width(NAVIGATION_DRAWER_WIDTH)
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
) {
item {
Text(
text = title.ifEmpty { stringResource(id = R.string.no_section_info) },
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
modifier = Modifier
.fillMaxWidth()
.clickable { onHeaderClick() }
.padding(horizontal = SIXTEEN_DP, vertical = TWELVE_DP)
)
}
itemsIndexed(sections) { index, section ->
val paddingStart = (section.level - ONE) * TWELVE
Text(
text = section.title,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.clickable { onSectionClick(index) }
.padding(start = paddingStart.dp, top = EIGHT_DP, bottom = EIGHT_DP, end = SIXTEEN_DP)
)
}
}
}
}
@Composable @Composable
private fun TabSwitcherAnimated(state: ReaderScreenState) { private fun TabSwitcherAnimated(state: ReaderScreenState) {
val transitionSpec = remember { val transitionSpec = remember {
@ -827,3 +902,5 @@ interface TabClickListener {
fun onSelectTab(position: Int) fun onSelectTab(position: Int)
fun onCloseTab(position: Int) fun onCloseTab(position: Int)
} }
data class DocumentSection(var title: String, var id: String, var level: Int)

View File

@ -168,5 +168,17 @@ data class ReaderScreenState(
/** /**
* Handles the click when user clicks on "Later" button in donation layout. * Handles the click when user clicks on "Later" button in donation layout.
*/ */
val laterButtonClick: () -> Unit val laterButtonClick: () -> Unit,
/**
* Manages the showing of header title of "table of content".
*/
val tableOfContentTitle: String,
/**
* Handles the click when user clicks on the "Header" of "table of content".
*/
val tableContentHeaderClick: () -> Unit,
/**
* Handles the click when user clicks on the "section" of "table of content".
*/
val tableOfContentSectionClick: (Int) -> Unit
) )

View File

@ -29,7 +29,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job

View File

@ -86,6 +86,7 @@ const val SHOWCASE_VIEW_ROUND_ANIMATION_DURATION = 2000
const val ONE = 1 const val ONE = 1
const val TWO = 2 const val TWO = 2
const val SIXTEEN = 16 const val SIXTEEN = 16
const val TWELVE = 12
const val SHOWCASE_VIEW_NEXT_BUTTON_TESTING_TAG = "showcaseViewNextButtonTestingTag" const val SHOWCASE_VIEW_NEXT_BUTTON_TESTING_TAG = "showcaseViewNextButtonTestingTag"
const val SHOWCASE_VIEW_MESSAGE_TESTING_TAG = "showCaseViewMessageTestingTag" const val SHOWCASE_VIEW_MESSAGE_TESTING_TAG = "showCaseViewMessageTestingTag"

View File

@ -29,14 +29,12 @@ import androidx.compose.material.icons.filled.Menu
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.fragment.findNavController
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.core.base.BaseActivity 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