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.Handler
import android.os.Looper
import android.util.Log
import android.view.MenuItem
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
@ -354,7 +353,6 @@ class KiwixMainActivity : CoreMainActivity() {
}
override fun showBottomAppBar() {
Log.e("SHOW_BOTTOM_APP", "showBottomAppBar: ")
shouldShowBottomAppBar.update { true }
}

View File

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

View File

@ -175,6 +175,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
*/
override fun hideTabSwitcher(shouldCloseZimBook: Boolean) {
activity?.setupDrawerToggle(true)
(requireActivity() as CoreMainActivity).showBottomAppBar()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
if (webViewList.isEmpty()) {
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")
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
super.onCreateOptionsMenu(menu, menuInflater)
@ -311,28 +295,20 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun closeFullScreen() {
super.closeFullScreen()
showNavBar()
setFragmentContainerBottomMarginToSizeOfNavBar()
}
private fun hideNavBar() {
// requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility = GONE
setBottomMarginToNavHostContainer(0)
(requireActivity() as CoreMainActivity).hideBottomAppBar()
}
private fun showNavBar() {
// show the navBar if fullScreenMode is not active.
if (!isInFullScreenMode()) {
// requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view).visibility =
// VISIBLE
(requireActivity() as CoreMainActivity).showBottomAppBar()
}
}
override fun createNewTab() {
newMainPageTab()
}
private fun setBottomMarginToNavHostContainer(margin: Int) {
// coreMainActivity.navHostContainer
// .setBottomMarginToFragmentContainerView(margin)
}
}

View File

@ -87,11 +87,26 @@ fun KiwixNavGraph(
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 = "" }
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()
@ -116,7 +131,10 @@ fun KiwixNavGraph(
composable(
route = KiwixDestination.Library.route,
arguments = listOf(
navArgument(ZIM_FILE_URI_KEY) { type = NavType.StringType; defaultValue = "" }
navArgument(ZIM_FILE_URI_KEY) {
type = NavType.StringType
defaultValue = ""
}
)
) { backStackEntry ->
val zimFileUri = backStackEntry.arguments?.getString(ZIM_FILE_URI_KEY).orEmpty()
@ -177,9 +195,18 @@ fun KiwixNavGraph(
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 }
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()
@ -314,10 +341,11 @@ sealed class KiwixDestination(val route: String) {
object LocalFileTransfer : KiwixDestination("$LOCAL_FILE_TRANSFER_FRAGMENT?uris={uris}") {
fun createRoute(uris: String? = null): String {
return if (uris != null)
return if (uris != null) {
"$LOCAL_FILE_TRANSFER_FRAGMENT?uris=${Uri.encode(uris)}"
else
} else {
"$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: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: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:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</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: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:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$@SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean</ID>
<ID>TooGenericExceptionCaught:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$exception: Exception</ID>
<ID>TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: 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.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination
import androidx.navigation.NavDirections
import androidx.navigation.NavHostController
import androidx.navigation.NavOptions
@ -249,19 +248,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
}
}
open fun configureActivityBasedOn(destination: NavDestination) {
// if (destination.id !in topLevelDestinations) {
// handleDrawerOnNavigation()
// }
// readerTableOfContentsDrawer.setLockMode(
// if (destination.id == readerFragmentResId) {
// LOCK_MODE_UNLOCKED
// } else {
// LOCK_MODE_LOCKED_CLOSED
// }
// )
}
@Suppress("UnusedParameter")
private fun NavigationView.setLockMode(lockMode: Int) {
// drawerContainerLayout.setDrawerLockMode(lockMode, this)
}
@ -455,14 +442,6 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
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() {
handleDrawerOnNavigation()
navigate(settingsFragmentRoute)
@ -492,15 +471,13 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
.setLaunchSingleTop(true)
.setPopUpTo(readerFragmentRoute, inclusive = true)
.build()
// navigate(
// readerFragmentRoute,
// bundleOf(
// PAGE_URL_KEY to pageUrl,
// ZIM_FILE_URI_KEY to zimFileUri,
// SHOULD_OPEN_IN_NEW_TAB to shouldOpenInNewTab
// ),
// navOptions
// )
val readerRouteWithArguments =
"$readerFragmentRoute?$PAGE_URL_KEY=$pageUrl&$ZIM_FILE_URI_KEY=$zimFileUri" +
"&$SHOULD_OPEN_IN_NEW_TAB=$shouldOpenInNewTab"
navigate(
readerRouteWithArguments,
navOptions
)
}
private fun openBookmarks() {
@ -523,7 +500,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
}
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 {

View File

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

View File

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

View File

@ -62,6 +62,7 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.platform.ComposeView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@ -71,10 +72,8 @@ import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.navigation.NavigationView
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
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.MainRepositoryActions
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.WebViewCallback
import org.kiwix.kiwixmobile.core.main.WebViewProvider
@ -199,7 +195,6 @@ abstract class CoreReaderFragment :
private val webUrlsFlow = MutableStateFlow("")
var drawerLayout: DrawerLayout? = null
protected var tableDrawerRightContainer: NavigationView? = null
@JvmField
@Inject
@ -256,7 +251,7 @@ abstract class CoreReaderFragment :
@Inject
var unsupportedMimeTypeHandler: UnsupportedMimeTypeHandler? = null
private var hideBackToTopTimer: CountDownTimer? = null
private val documentSections: MutableList<DocumentSection>? = ArrayList()
private val documentSections: SnapshotStateList<DocumentSection>? = mutableStateListOf()
private var isBackToTopEnabled = false
private var isOpenNewTabInBackground = false
private var documentParserJs: String? = null
@ -269,8 +264,6 @@ abstract class CoreReaderFragment :
private val tempWebViewListForUndo: MutableList<KiwixWebView> = ArrayList()
private var tempZimSourceForUndo: ZimReaderSource? = null
private var isFirstRun = false
private var tableDrawerAdapter: TableDrawerAdapter? = null
private var tableDrawerRight: RecyclerView? = null
private var bookmarkingJob: Job? = null
private var isBookmarked = false
private lateinit var serviceConnection: ServiceConnection
@ -279,6 +272,7 @@ abstract class CoreReaderFragment :
private var isReadSelection = false
private var isReadAloudServiceRunning = false
private var libkiwixBook: Book? = null
private var shouldTableOfContentDrawer = mutableStateOf(false)
protected var readerMenuState: ReaderMenuState? = null
private var composeView: ComposeView? = null
@ -296,7 +290,7 @@ abstract class CoreReaderFragment :
onExitFullscreenClick = { closeFullScreen() },
showTtsControls = false,
onPauseTtsClick = { pauseTts() },
pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty(),
pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty(),
onStopTtsClick = { stopTts() },
kiwixWebViewList = webViewList,
bookmarkButtonItem = Triple(
@ -335,7 +329,10 @@ abstract class CoreReaderFragment :
},
appName = "",
donateButtonClick = {},
laterButtonClick = {}
laterButtonClick = {},
tableOfContentTitle = "",
tableContentHeaderClick = { tableOfContentHeaderClick() },
tableOfContentSectionClick = { tableOfContentSectionClick(it) },
)
)
private var readerLifeCycleScope: CoroutineScope? = null
@ -362,12 +359,12 @@ abstract class CoreReaderFragment :
* 2) Permission has been disabled on device
*/
requireActivity().toast(
R.string.ext_storage_permission_rationale_add_note,
string.ext_storage_permission_rationale_add_note,
Toast.LENGTH_LONG
)
} else {
requireActivity().toast(
R.string.ext_storage_write_permission_denied_add_note,
string.ext_storage_write_permission_denied_add_note,
Toast.LENGTH_LONG
)
alertDialogShower?.show(
@ -447,7 +444,7 @@ abstract class CoreReaderFragment :
readerScreenState.update {
copy(
bottomNavigationHeight = getBottomNavigationHeight(),
readerScreenTitle = context.getString(R.string.reader),
readerScreenTitle = context.getString(string.reader),
darkModeViewPainter = darkModeViewPainter,
fullScreenItem = fullScreenItem.first to getVideoView(),
tocButtonItem = getTocButtonStateAndAction(),
@ -483,7 +480,9 @@ abstract class CoreReaderFragment :
iconTint = navigationIconTint()
)
},
mainActivityBottomAppBarScrollBehaviour = (requireActivity() as CoreMainActivity).bottomAppBarScrollBehaviour
mainActivityBottomAppBarScrollBehaviour = (requireActivity() as CoreMainActivity).bottomAppBarScrollBehaviour,
documentSections = documentSections,
showTableOfContentDrawer = shouldTableOfContentDrawer,
)
DialogHost(alertDialogShower as AlertDialogShower)
}
@ -497,10 +496,7 @@ abstract class CoreReaderFragment :
handleLocaleCheck()
initHideBackToTopTimer()
loadDrawerViews()
tableDrawerRight =
tableDrawerRightContainer?.getHeaderView(0)?.findViewById(R.id.right_drawer_list)
addFileReader()
setTableDrawerInfo()
activity?.let {
compatCallback = CompatFindActionModeCallback(it)
}
@ -567,7 +563,7 @@ abstract class CoreReaderFragment :
private fun navigationIconContentDescription() =
if (readerMenuState?.isInTabSwitcher == true) {
R.string.search_open_in_new_tab
string.search_open_in_new_tab
} else {
string.open_drawer
}
@ -665,54 +661,36 @@ abstract class CoreReaderFragment :
sections: List<DocumentSection>
) {
if (isAdded) {
documentSections?.let {
it.addAll(sections)
tableDrawerAdapter?.setTitle(title)
tableDrawerAdapter?.setSections(it)
tableDrawerAdapter?.notifyDataSetChanged()
}
documentSections?.addAll(sections)
readerScreenState.update { copy(tableOfContentTitle = title) }
}
}
override fun clearSections() {
documentSections?.clear()
tableDrawerAdapter?.notifyDataSetChanged()
}
})
}
private fun setTableDrawerInfo() {
tableDrawerRight?.apply {
layoutManager = LinearLayoutManager(requireActivity())
tableDrawerAdapter = setupTableDrawerAdapter()
adapter = tableDrawerAdapter
tableDrawerAdapter?.notifyDataSetChanged()
}
}
private fun addFileReader() {
documentParserJs = requireActivity().readFile("js/documentParser.js")
documentSections?.clear()
}
private fun setupTableDrawerAdapter(): TableDrawerAdapter {
return TableDrawerAdapter(object : TableClickListener {
override fun onHeaderClick(view: View?) {
getCurrentWebView()?.scrollY = 0
drawerLayout?.closeDrawer(GravityCompat.END)
}
private fun tableOfContentHeaderClick() {
getCurrentWebView()?.scrollY = 0
shouldTableOfContentDrawer.update { false }
}
override fun onSectionClick(view: View?, position: Int) {
if (hasItemForPositionInDocumentSectionsList(position)) { // Bug Fix #3796
loadUrlWithCurrentWebview(
"javascript:document.getElementById('" +
documentSections?.get(position)?.id?.replace("'", "\\'") +
"').scrollIntoView();"
)
}
drawerLayout?.closeDrawers()
}
})
private fun tableOfContentSectionClick(position: Int) {
if (hasItemForPositionInDocumentSectionsList(position)) { // Bug Fix #3796
loadUrlWithCurrentWebview(
"javascript:document.getElementById('" +
documentSections?.get(position)?.id?.replace("'", "\\'") +
"').scrollIntoView();"
)
}
shouldTableOfContentDrawer.update { false }
}
private fun hasItemForPositionInDocumentSectionsList(position: Int): Boolean {
@ -772,7 +750,6 @@ abstract class CoreReaderFragment :
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
setUpDrawerToggle()
(requireActivity() as CoreMainActivity).showBottomAppBar()
Log.e("SHOW_BOTTOM_APP", "hideTabSwitcher: ")
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
readerScreenState.update {
copy(
@ -862,9 +839,9 @@ abstract class CoreReaderFragment :
**/
val dialogFragment = NavigationHistoryDialog(
if (isForwardHistory) {
R.string.forward_history
string.forward_history
} else {
R.string.backward_history
string.backward_history
},
navigationHistoryList,
this
@ -885,7 +862,7 @@ abstract class CoreReaderFragment :
repositoryActions?.clearWebViewPageHistory()
}
updateBottomToolbarArrowsAlpha()
toast(R.string.navigation_history_cleared)
toast(string.navigation_history_cleared)
}
@Suppress("MagicNumber")
@ -900,7 +877,7 @@ abstract class CoreReaderFragment :
}
protected fun openToc() {
drawerLayout?.openDrawer(GravityCompat.END)
shouldTableOfContentDrawer.update { true }
}
@Suppress("ReturnCount", "NestedBlockDepth")
@ -1029,7 +1006,7 @@ abstract class CoreReaderFragment :
readerScreenState.update {
copy(
showTtsControls = false,
pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty()
pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty()
)
}
setActionAndStartTTSService(ACTION_STOP_TTS)
@ -1048,14 +1025,14 @@ abstract class CoreReaderFragment :
AudioManager.AUDIOFOCUS_LOSS -> {
if (tts?.currentTTSTask?.paused == false) tts?.pauseOrResume()
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)
}
AudioManager.AUDIOFOCUS_GAIN -> {
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)
}
@ -1090,13 +1067,13 @@ abstract class CoreReaderFragment :
if (it.paused) {
tts?.pauseOrResume()
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)
} else {
tts?.pauseOrResume()
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)
}
@ -1135,8 +1112,6 @@ abstract class CoreReaderFragment :
hideBackToTopTimer?.cancel()
hideBackToTopTimer = null
stopOngoingLoadingAndClearWebViewList()
tableDrawerRight?.adapter = null
tableDrawerAdapter = null
tempWebViewListForUndo.clear()
// create a base Activity class that class this.
deleteCachedFiles(requireActivity())
@ -1166,7 +1141,6 @@ abstract class CoreReaderFragment :
compatCallback?.finish()
compatCallback = null
drawerLayout = null
tableDrawerRightContainer = null
}
private fun updateTableOfContents() {
@ -1281,8 +1255,8 @@ abstract class CoreReaderFragment :
currentWebViewIndex--
}
readerScreenState.value.snackBarHostState.snack(
requireActivity().getString(R.string.tab_closed),
actionLabel = requireActivity().getString(R.string.undo),
requireActivity().getString(string.tab_closed),
actionLabel = requireActivity().getString(string.undo),
actionClick = { restoreDeletedTab(index) },
lifecycleScope = lifecycleScope,
snackBarResult = { result ->
@ -1307,7 +1281,7 @@ abstract class CoreReaderFragment :
readerScreenState.update {
copy(
shouldShowBottomAppBar = false,
readerScreenTitle = context?.getString(R.string.reader).orEmpty()
readerScreenTitle = context?.getString(string.reader).orEmpty()
)
}
hideProgressBar()
@ -1342,7 +1316,7 @@ abstract class CoreReaderFragment :
tempWebViewForUndo?.let {
webViewList.add(index, it)
readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tab_restored).orEmpty(),
context?.getString(string.tab_restored).orEmpty(),
lifecycleScope = lifecycleScope
)
setUpWithTextToSpeech(it)
@ -1421,7 +1395,7 @@ abstract class CoreReaderFragment :
hideBackToTopButton()
}
readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty())
copy(pauseTtsButtonText = context?.getString(string.tts_pause).orEmpty())
}
if (tts?.isInitialized == false) {
isReadSelection = false
@ -1621,7 +1595,7 @@ abstract class CoreReaderFragment :
exitBook()
Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + zimReaderSource.toDatabase())
requireActivity().toast(
getString(R.string.error_file_not_found, zimReaderSource.toDatabase()),
getString(string.error_file_not_found, zimReaderSource.toDatabase()),
Toast.LENGTH_LONG
)
}
@ -1664,13 +1638,13 @@ abstract class CoreReaderFragment :
}
readerMenuState?.onFileOpened(urlIsValid())
setUpBookmarks(zimFileReader)
} ?: kotlin.run {
} ?: run {
// 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.
// See issue #4161 for more details.
exitBook()
requireActivity().toast(
getString(R.string.error_file_invalid, zimReaderSource.toDatabase()),
getString(string.error_file_invalid, zimReaderSource.toDatabase()),
Toast.LENGTH_LONG
)
}
@ -1758,8 +1732,8 @@ abstract class CoreReaderFragment :
}
} else {
readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.request_storage).orEmpty(),
context?.getString(R.string.menu_settings),
context?.getString(string.request_storage).orEmpty(),
context?.getString(string.menu_settings),
snackbarDuration = SnackbarDuration.Long,
actionClick = {
val intent = Intent()
@ -1791,8 +1765,8 @@ abstract class CoreReaderFragment :
webViewList.clear()
openHomeScreen()
readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tabs_closed).orEmpty(),
context?.getString(R.string.undo),
context?.getString(string.tabs_closed).orEmpty(),
context?.getString(string.undo),
actionClick = { restoreDeletedTabs() },
lifecycleScope = lifecycleScope,
snackBarResult = { result ->
@ -1810,7 +1784,7 @@ abstract class CoreReaderFragment :
if (tempWebViewListForUndo.isNotEmpty()) {
webViewList.addAll(tempWebViewListForUndo)
readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tabs_restored).orEmpty(),
context?.getString(string.tabs_restored).orEmpty(),
lifecycleScope = lifecycleScope
)
reopenBook()
@ -1850,7 +1824,7 @@ abstract class CoreReaderFragment :
if (isBookmarked) {
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.bookmark_removed).orEmpty(),
context?.getString(string.bookmark_removed).orEmpty(),
lifecycleScope = lifecycleScope
)
} else {
@ -1859,22 +1833,22 @@ abstract class CoreReaderFragment :
LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook)
)
readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.bookmark_added).orEmpty(),
context?.getString(string.bookmark_added).orEmpty(),
lifecycleScope = lifecycleScope,
actionLabel = context?.getString(R.string.open),
actionLabel = context?.getString(string.open),
actionClick = { goToBookmarks() }
)
}
}
}
} ?: kotlin.run {
requireActivity().toast(R.string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT)
} ?: run {
requireActivity().toast(string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT)
}
}
} catch (_: Exception) {
// Catch the exception while saving the bookmarks for splitted zim files.
// 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) {
createNewTab()
}
item.pageUrl?.let(::loadUrlWithCurrentWebview) ?: kotlin.run {
item.pageUrl?.let(::loadUrlWithCurrentWebview) ?: run {
zimReaderContainer?.titleToUrl(item.pageTitle)?.apply {
loadUrlWithCurrentWebview(zimReaderContainer?.urlSuffixToParsableUrl(this))
}
@ -2069,7 +2043,7 @@ abstract class CoreReaderFragment :
} else {
contentUrl
}
} ?: kotlin.run {
} ?: run {
return@redirectOrOriginal contentUrl
}
}
@ -2088,7 +2062,7 @@ abstract class CoreReaderFragment :
private fun openRandomArticle(retryCount: Int = 2) {
// Check if the ZIM file reader is available, if not show an error and exit.
if (zimReaderContainer?.zimFileReader == null) {
toast(R.string.error_loading_random_article_zim_not_loaded)
toast(string.error_loading_random_article_zim_not_loaded)
return
}
val articleUrl = zimReaderContainer?.getRandomArticleUrl()
@ -2105,7 +2079,7 @@ abstract class CoreReaderFragment :
} else {
// 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")
toast(R.string.could_not_find_random_article)
toast(string.could_not_find_random_article)
}
return
}
@ -2420,7 +2394,7 @@ abstract class CoreReaderFragment :
Log.d(
TAG_KIWIX,
String.format(
getString(R.string.error_article_url_not_found),
getString(string.error_article_url_not_found),
failingUrl
)
)
@ -2500,9 +2474,9 @@ abstract class CoreReaderFragment :
if (isOpenNewTabInBackground) {
newTabInBackground(url)
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,
actionLabel = context?.getString(R.string.open),
actionLabel = context?.getString(string.open),
actionClick = {
if (webViewList.size > 1) {
selectTab(webViewList.size - 1)
@ -2603,7 +2577,7 @@ abstract class CoreReaderFragment :
readerMenuState?.showWebViewOptions(urlIsValid())
} catch (ignore: Exception) {
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 ->
webView.restoreState(bundle)
webView.scrollY = webViewHistoryItem.webViewCurrentPosition
} ?: kotlin.run {
} ?: run {
zimReaderContainer?.zimFileReader?.let {
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.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@ -68,6 +72,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
@ -76,6 +81,7 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
@ -107,13 +113,16 @@ import androidx.compose.ui.zIndex
import kotlinx.coroutines.delay
import org.kiwix.kiwixmobile.core.R
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.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
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.TWELVE
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem
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.FOUR_DP
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.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE
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.THREE_DP
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.StyleUtils.fromHtml
@ -155,6 +166,8 @@ const val TAB_TITLE_TESTING_TAG = "tabTitleTestingTag"
fun ReaderScreen(
state: ReaderScreenState,
actionMenuItems: List<ActionMenuItem>,
showTableOfContentDrawer: MutableState<Boolean>,
documentSections: MutableList<DocumentSection>?,
mainActivityBottomAppBarScrollBehaviour: BottomAppBarScrollBehavior?,
navigationIcon: @Composable () -> Unit
) {
@ -185,11 +198,34 @@ fun ReaderScreen(
}
.semantics { testTag = READER_SCREEN_TESTING_TAG }
) { paddingValues ->
ReaderContentLayout(
state,
Modifier.padding(paddingValues),
bottomAppBarScrollBehavior
)
Box(Modifier.fillMaxSize()) {
ReaderContentLayout(
state,
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
private fun TabSwitcherAnimated(state: ReaderScreenState) {
val transitionSpec = remember {
@ -827,3 +902,5 @@ interface TabClickListener {
fun onSelectTab(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.
*/
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.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job

View File

@ -86,6 +86,7 @@ const val SHOWCASE_VIEW_ROUND_ANIMATION_DURATION = 2000
const val ONE = 1
const val TWO = 2
const val SIXTEEN = 16
const val TWELVE = 12
const val SHOWCASE_VIEW_NEXT_BUTTON_TESTING_TAG = "showcaseViewNextButtonTestingTag"
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.core.net.toUri
import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.extensions.browserIntent
import org.kiwix.kiwixmobile.core.extensions.isFileExist
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.ReaderMenuState
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin