Fixed: Restore web view history in Kiwix app.

* Improved restoration of web view history after opening a searched article.
* Refactored `restoreViewStateOnValidJSON` and `restoreViewStateOnInvalidJSON` methods to save and retrieve web view history from the Room database.
* Added detailed comments to methods for better understanding, including guidance for developers to check subclass implementations before making modifications.
* Fixed the `static analysis` errors.
This commit is contained in:
MohitMaliFtechiz 2024-10-25 16:23:23 +05:30
parent ee50417154
commit 2fb1896cea
6 changed files with 254 additions and 61 deletions

View File

@ -58,6 +58,7 @@ import org.kiwix.kiwixmobile.core.main.ToolbarScrollingKiwixWebView
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource.Companion.fromDatabaseValue import org.kiwix.kiwixmobile.core.reader.ZimReaderSource.Companion.fromDatabaseValue
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.TAG_CURRENT_FILE import org.kiwix.kiwixmobile.core.utils.TAG_CURRENT_FILE
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
@ -69,6 +70,7 @@ private const val HIDE_TAB_SWITCHER_DELAY: Long = 300
class KiwixReaderFragment : CoreReaderFragment() { class KiwixReaderFragment : CoreReaderFragment() {
private var isFullScreenVideo: Boolean = false private var isFullScreenVideo: Boolean = false
private var searchItemToOpen: SearchItemToOpen? = null
override fun inject(baseActivity: BaseActivity) { override fun inject(baseActivity: BaseActivity) {
baseActivity.cachedComponent.inject(this) baseActivity.cachedComponent.inject(this)
@ -111,7 +113,16 @@ class KiwixReaderFragment : CoreReaderFragment() {
} else { } else {
val restoreOrigin = val restoreOrigin =
if (args.searchItemTitle.isNotEmpty()) FromSearchScreen else FromExternalLaunch if (args.searchItemTitle.isNotEmpty()) FromSearchScreen else FromExternalLaunch
manageExternalLaunchAndRestoringViewState(restoreOrigin) manageExternalLaunchAndRestoringViewState(restoreOrigin) {
// This lambda function is invoked after restoring the tabs. It checks if there is a
// search item to open. If `searchItemToOpen` is not null, it will call the superclass
// method to open the specified search item. After opening, it sets `searchItemToOpen`
// to null to prevent any unexpected behavior on subsequent calls.
searchItemToOpen?.let {
super.openSearchItem(it)
}
searchItemToOpen = null
}
} }
} }
requireArguments().clear() requireArguments().clear()
@ -151,6 +162,18 @@ class KiwixReaderFragment : CoreReaderFragment() {
openZimFile(zimReaderSource) openZimFile(zimReaderSource)
} }
/**
* Stores the specified search item to be opened later.
*
* This method saves the provided `SearchItemToOpen` object, which will be used to
* open the searched item after the tabs have been restored.
*
* @param item The search item to be opened after restoring the tabs.
*/
override fun openSearchItem(item: SearchItemToOpen) {
searchItemToOpen = item
}
override fun loadDrawerViews() { override fun loadDrawerViews() {
drawerLayout = requireActivity().findViewById(R.id.navigation_container) drawerLayout = requireActivity().findViewById(R.id.navigation_container)
tableDrawerRightContainer = requireActivity().findViewById(R.id.reader_drawer_nav_view) tableDrawerRightContainer = requireActivity().findViewById(R.id.reader_drawer_nav_view)
@ -243,7 +266,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
} }
override fun restoreViewStateOnInvalidJSON() { override fun restoreViewStateOnInvalidWebViewHistory() {
Log.d(TAG_KIWIX, "Kiwix normal start, no zimFile loaded last time -> display home page") Log.d(TAG_KIWIX, "Kiwix normal start, no zimFile loaded last time -> display home page")
exitBook() exitBook()
} }
@ -256,15 +279,16 @@ class KiwixReaderFragment : CoreReaderFragment() {
* as the ZIM file is already set in the reader. The method handles setting up the ZIM file and bookmarks, * as the ZIM file is already set in the reader. The method handles setting up the ZIM file and bookmarks,
* and restores the tabs and positions from the provided data. * and restores the tabs and positions from the provided data.
* *
* @param webViewHistoryItemList JSON string representing the list of articles to be restored. * @param webViewHistoryItemList WebViewHistoryItem list representing the list of articles to be restored.
* @param currentTab Index of the tab to be restored as the currently active one. * @param currentTab Index of the tab to be restored as the currently active one.
* @param restoreOrigin Indicates whether the restoration is triggered from an external launch or the search screen. * @param restoreOrigin Indicates whether the restoration is triggered from an external launch or the search screen.
* @param onComplete Callback to be invoked upon completion of the restoration process.
*/ */
override fun restoreViewStateOnValidWebViewHistory(
override fun restoreViewStateOnValidJSON(
webViewHistoryItemList: List<WebViewHistoryItem>, webViewHistoryItemList: List<WebViewHistoryItem>,
currentTab: Int, currentTab: Int,
restoreOrigin: RestoreOrigin restoreOrigin: RestoreOrigin,
onComplete: () -> Unit
) { ) {
when (restoreOrigin) { when (restoreOrigin) {
FromExternalLaunch -> { FromExternalLaunch -> {
@ -274,7 +298,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
val zimReaderSource = fromDatabaseValue(settings.getString(TAG_CURRENT_FILE, null)) val zimReaderSource = fromDatabaseValue(settings.getString(TAG_CURRENT_FILE, null))
if (zimReaderSource?.canOpenInLibkiwix() == true) { if (zimReaderSource?.canOpenInLibkiwix() == true) {
if (zimReaderContainer?.zimReaderSource == null) { if (zimReaderContainer?.zimReaderSource == null) {
openZimFile(zimReaderSource) openZimFile(zimReaderSource, isFromManageExternalLaunch = true)
Log.d( Log.d(
TAG_KIWIX, TAG_KIWIX,
"Kiwix normal start, Opened last used zimFile: -> ${zimReaderSource.toDatabase()}" "Kiwix normal start, Opened last used zimFile: -> ${zimReaderSource.toDatabase()}"
@ -282,7 +306,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
} else { } else {
zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) zimReaderContainer?.zimFileReader?.let(::setUpBookmarks)
} }
restoreTabs(webViewHistoryItemList, currentTab) restoreTabs(webViewHistoryItemList, currentTab, onComplete)
} else { } else {
getCurrentWebView()?.snack(string.zim_not_opened) getCurrentWebView()?.snack(string.zim_not_opened)
exitBook() // hide the options for zim file to avoid unexpected UI behavior exitBook() // hide the options for zim file to avoid unexpected UI behavior
@ -291,7 +315,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
} }
FromSearchScreen -> { FromSearchScreen -> {
restoreTabs(webViewHistoryItemList, currentTab) restoreTabs(webViewHistoryItemList, currentTab, onComplete)
} }
} }
} }

View File

@ -24,7 +24,6 @@ import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem

View File

@ -28,8 +28,8 @@ import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.dao.NotesRoomDao import org.kiwix.kiwixmobile.core.dao.NotesRoomDao
import org.kiwix.kiwixmobile.core.dao.WebViewHistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.dao.WebViewHistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity
import org.kiwix.kiwixmobile.core.di.qualifiers.IO import org.kiwix.kiwixmobile.core.di.qualifiers.IO
import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread
@ -37,7 +37,6 @@ import org.kiwix.kiwixmobile.core.extensions.HeaderizableList
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
@ -148,7 +147,9 @@ class Repository @Inject internal constructor(
Completable.fromAction { notesRoomDao.deleteNotes(noteList) } Completable.fromAction { notesRoomDao.deleteNotes(noteList) }
.subscribeOn(ioThread) .subscribeOn(ioThread)
override suspend fun insertWebViewPageHistoryItems(webViewHistoryEntityList: List<WebViewHistoryEntity>) { override suspend fun insertWebViewPageHistoryItems(
webViewHistoryEntityList: List<WebViewHistoryEntity>
) {
webViewHistoryRoomDao.insertWebViewPageHistoryItems(webViewHistoryEntityList) webViewHistoryRoomDao.insertWebViewPageHistoryItems(webViewHistoryEntityList)
} }
@ -159,7 +160,7 @@ class Repository @Inject internal constructor(
.observeOn(mainThread) .observeOn(mainThread)
override suspend fun clearWebViewPagesHistory() { override suspend fun clearWebViewPagesHistory() {
webViewHistoryRoomDao.clearPageHistoryWithPrimaryKey() webViewHistoryRoomDao.clearWebViewPagesHistory()
} }
override fun deleteNote(noteTitle: String): Completable = override fun deleteNote(noteTitle: String): Completable =

View File

@ -100,8 +100,8 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONException
import org.kiwix.kiwixmobile.core.BuildConfig import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.DarkModeConfig import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.StorageObserver
@ -286,6 +286,7 @@ abstract class CoreReaderFragment :
private var isFirstTimeMainPageLoaded = true private var isFirstTimeMainPageLoaded = true
private var isFromManageExternalLaunch = false private var isFromManageExternalLaunch = false
private var shouldSaveTabsOnPause = true
@JvmField @JvmField
@Inject @Inject
@ -421,6 +422,9 @@ abstract class CoreReaderFragment :
savedInstanceState: Bundle? savedInstanceState: Bundle?
) { ) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Set this to true to enable saving the tab history
// when the fragment goes into the paused state.
shouldSaveTabsOnPause = true
setupMenu() setupMenu()
donationDialogHandler?.setDonationDialogCallBack(this) donationDialogHandler?.setDonationDialogCallBack(this)
val activity = requireActivity() as AppCompatActivity? val activity = requireActivity() as AppCompatActivity?
@ -1306,6 +1310,15 @@ abstract class CoreReaderFragment :
} }
} }
/**
* Initializes a new instance of `KiwixWebView` with the specified URL.
*
* @param url The URL to load in the web view. This is ignored if `shouldLoadUrl` is false.
* @param shouldLoadUrl A flag indicating whether to load the specified URL in the web view.
* When restoring tabs, this should be set to false to avoid loading
* an extra page, as the previous web view history will be restored directly.
* @return The initialized `KiwixWebView` instance, or null if initialization fails.
*/
private fun initalizeWebView(url: String, shouldLoadUrl: Boolean = true): KiwixWebView? { private fun initalizeWebView(url: String, shouldLoadUrl: Boolean = true): KiwixWebView? {
if (isAdded) { if (isAdded) {
val attrs = requireActivity().getAttributes(R.xml.webview) val attrs = requireActivity().getAttributes(R.xml.webview)
@ -1354,6 +1367,17 @@ abstract class CoreReaderFragment :
newTab(url, false) newTab(url, false)
} }
/**
* Creates a new instance of `KiwixWebView` and adds it to the list of web views.
*
* @param url The URL to load in the newly created web view.
* @param selectTab A flag indicating whether to select the newly created tab immediately.
* Defaults to true, which means the new tab will be selected.
* @param shouldLoadUrl A flag indicating whether to load the specified URL in the web view.
* If set to false, the web view will be created without loading the URL,
* which is useful when restoring tabs.
* @return The newly created `KiwixWebView` instance, or null if the initialization fails.
*/
private fun newTab( private fun newTab(
url: String, url: String,
selectTab: Boolean = true, selectTab: Boolean = true,
@ -1529,6 +1553,17 @@ abstract class CoreReaderFragment :
} }
} }
override fun onSearchMenuClickedMenuClicked() {
// Set this to false to prevent saving the tab history in onPause
// when opening the search fragment.
shouldSaveTabsOnPause = false
saveTabStates {
// Pass this function to saveTabStates so that after saving
// the tab state in the database, it will open the search fragment.
openSearch("", isOpenedFromTabView = isInTabSwitcher, false)
}
}
@Suppress("NestedBlockDepth") @Suppress("NestedBlockDepth")
override fun onReadAloudMenuClicked() { override fun onReadAloudMenuClicked() {
if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) { if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) {
@ -2103,7 +2138,21 @@ abstract class CoreReaderFragment :
openSearch("", isOpenedFromTabView = false, isVoice) openSearch("", isOpenedFromTabView = false, isVoice)
} }
private fun openSearchItem(item: SearchItemToOpen) { /**
* Opens a search item based on its properties.
*
* If the item should open in a new tab, a new tab is created.
*
* The method attempts to load the page URL directly. If the page URL is not available,
* it attempts to convert the page title to a URL using the ZIM reader container. The
* resulting URL is then loaded in the current web view.
*
* Note: This method is overridden in the `KiwixReaderFragment` class to store the
* `SearchItemToOpen` object for later use. If modifications are made to this method,
* please check the overridden version to understand how it interacts with the fragment's
* navigation logic.
*/
open fun openSearchItem(item: SearchItemToOpen) {
if (item.shouldOpenInNewTab) { if (item.shouldOpenInNewTab) {
createNewTab() createNewTab()
} }
@ -2360,13 +2409,42 @@ abstract class CoreReaderFragment :
updateNightMode() updateNightMode()
} }
private fun saveTabStates() { /**
* Saves the current state of tabs and web view history to persistent storage.
*
* This method is designed to be called when the fragment is about to pause,
* ensuring that the current tab states are preserved. It performs the following steps:
*
* 1. Clears any previous web view page history stored in the database.
* 2. Retrieves the current activity's shared preferences to store the tab states.
* 3. Iterates over the currently opened web views, creating a list of
* `WebViewHistoryEntity` objects based on their URLs.
* 4. Saves the collected web view history entities to the database.
* 5. Updates the shared preferences with the current ZIM file and tab index.
* 6. Logs the current ZIM file being saved for debugging purposes.
* 7. Calls the provided `onComplete` callback function once all operations are finished.
*
* Note: This method runs on the main thread and performs database operations
* in a background thread to avoid blocking the UI.
*
* @param onComplete A lambda function to be executed after the tab states have
* been successfully saved. This is optional and defaults to
* an empty function.
*
* Example usage:
* ```
* saveTabStates {
* openSearch("", isOpenedFromTabView = isInTabSwitcher, false)
* }
*/
private fun saveTabStates(onComplete: () -> Unit = {}) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
// clear the previous history saved in database // clear the previous history saved in database
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
repositoryActions?.clearWebViewPageHistory() repositoryActions?.clearWebViewPageHistory()
} }
val settings = requireActivity().getSharedPreferences( val coreApp = sharedPreferenceUtil?.context as CoreApp
val settings = coreApp.getMainActivity().getSharedPreferences(
SharedPreferenceUtil.PREF_KIWIX_MOBILE, SharedPreferenceUtil.PREF_KIWIX_MOBILE,
0 0
) )
@ -2382,10 +2460,43 @@ abstract class CoreReaderFragment :
editor.putString(TAG_CURRENT_FILE, zimReaderContainer?.zimReaderSource?.toDatabase()) editor.putString(TAG_CURRENT_FILE, zimReaderContainer?.zimReaderSource?.toDatabase())
editor.putInt(TAG_CURRENT_TAB, currentWebViewIndex) editor.putInt(TAG_CURRENT_TAB, currentWebViewIndex)
editor.apply() editor.apply()
Log.d(
TAG_KIWIX,
"Save current zim file to preferences: " +
"${zimReaderContainer?.zimReaderSource?.toDatabase()}"
)
onComplete.invoke()
} }
} }
private fun getWebViewHistoryEntity( /**
* Retrieves a `WebViewHistoryEntity` from the given `KiwixWebView` instance.
*
* This method captures the current state of the specified web view, including its
* scroll position and back-forward list, and creates a `WebViewHistoryEntity`
* if the necessary conditions are met. The steps involved are as follows:
*
* 1. Initializes a `Bundle` to store the state of the web view.
* 2. Calls `saveState` on the provided `webView`, which populates the bundle
* with the current state of the web view's back-forward list.
* 3. Retrieves the ID of the currently loaded ZIM file from the `zimReaderContainer`.
* 4. Checks if the ZIM ID is not null and if the web back-forward list contains any entries:
* - If both conditions are satisfied, it creates and returns a `WebViewHistoryEntity`
* containing a `WebViewHistoryItem` with the following data:
* - `zimId`: The ID of the current ZIM file.
* - `webViewIndex`: The index of the web view in the list of opened views.
* - `webViewPosition`: The current vertical scroll position of the web view.
* - `webViewBackForwardList`: The bundle containing the saved state of the
* web view's back-forward list.
* 5. If the ZIM ID is null or the web back-forward list is empty, the method returns null.
*
* @param webView The `KiwixWebView` instance from which to retrieve the history entity.
* @param webViewIndex The index of the web view in the list of opened web views,
* used to identify the position of this web view in the history.
* @return A `WebViewHistoryEntity` containing the state information of the web view,
* or null if the necessary conditions for creating the entity are not met.
*/
private suspend fun getWebViewHistoryEntity(
webView: KiwixWebView, webView: KiwixWebView,
webViewIndex: Int webViewIndex: Int
): WebViewHistoryEntity? { ): WebViewHistoryEntity? {
@ -2406,14 +2517,14 @@ abstract class CoreReaderFragment :
return null return null
} }
/**
* @see shouldSaveTabsOnPause
*/
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
saveTabStates() if (shouldSaveTabsOnPause) {
Log.d( saveTabStates()
TAG_KIWIX, }
"onPause Save current zim file to preferences: " +
"${zimReaderContainer?.zimReaderSource?.toDatabase()}"
)
} }
override fun webViewUrlLoading() { override fun webViewUrlLoading() {
@ -2597,7 +2708,8 @@ abstract class CoreReaderFragment :
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
protected fun manageExternalLaunchAndRestoringViewState( protected fun manageExternalLaunchAndRestoringViewState(
restoreOrigin: RestoreOrigin = FromExternalLaunch restoreOrigin: RestoreOrigin = FromExternalLaunch,
onComplete: () -> Unit = {}
) { ) {
val settings = requireActivity().getSharedPreferences( val settings = requireActivity().getSharedPreferences(
SharedPreferenceUtil.PREF_KIWIX_MOBILE, SharedPreferenceUtil.PREF_KIWIX_MOBILE,
@ -2606,29 +2718,49 @@ abstract class CoreReaderFragment :
val currentTab = safelyGetCurrentTab(settings) val currentTab = safelyGetCurrentTab(settings)
repositoryActions?.loadWebViewPagesHistory() repositoryActions?.loadWebViewPagesHistory()
?.subscribe({ webViewHistoryItemList -> ?.subscribe({ webViewHistoryItemList ->
Log.e(
"VALID_DATA",
"manageExternalLaunchAndRestoringViewState: ${webViewHistoryItemList.size}"
)
if (webViewHistoryItemList.isEmpty()) { if (webViewHistoryItemList.isEmpty()) {
restoreViewStateOnInvalidJSON() restoreViewStateOnInvalidWebViewHistory()
return@subscribe return@subscribe
} }
restoreViewStateOnValidJSON(webViewHistoryItemList, currentTab, restoreOrigin) restoreViewStateOnValidWebViewHistory(
webViewHistoryItemList,
currentTab,
restoreOrigin,
onComplete
)
}, { }, {
Log.e("INVALID_DATA", "manageExternalLaunchAndRestoringViewState: $it") restoreViewStateOnInvalidWebViewHistory()
restoreViewStateOnInvalidJSON()
}) })
} }
private fun safelyGetCurrentTab(settings: SharedPreferences): Int = private fun safelyGetCurrentTab(settings: SharedPreferences): Int =
max(settings.getInt(TAG_CURRENT_TAB, 0), 0) max(settings.getInt(TAG_CURRENT_TAB, 0), 0)
/* This method restores tabs state in new launches, do not modify it /**
unless it is explicitly mentioned in the issue you're fixing */ * Restores the tabs based on the provided webViewHistoryItemList.
*
* This method performs the following actions:
* - Resets the current web view index to zero.
* - Removes the first tab from the webViewList and updates the tabs adapter.
* - Iterates over the provided webViewHistoryItemList, creating new tabs and restoring
* their states based on the historical data.
* - Selects the specified tab to make it the currently active one.
* - Invokes the onComplete callback once the restoration is finished.
*
* If any error occurs during the restoration process, it logs a warning and displays
* a toast message to inform the user that the tabs could not be restored.
*
* @param webViewHistoryItemList List of WebViewHistoryItem representing the historical data for restoring tabs.
* @param currentTab Index of the tab to be set as the currently active tab after restoration.
* @param onComplete Callback to be invoked upon successful restoration of the tabs.
*
* @Warning: This method restores tabs state in new launches, do not modify it
* unless it is explicitly mentioned in the issue you're fixing.
*/
protected fun restoreTabs( protected fun restoreTabs(
webViewHistoryItemList: List<WebViewHistoryItem>, webViewHistoryItemList: List<WebViewHistoryItem>,
currentTab: Int currentTab: Int,
onComplete: () -> Unit
) { ) {
try { try {
currentWebViewIndex = 0 currentWebViewIndex = 0
@ -2643,8 +2775,9 @@ abstract class CoreReaderFragment :
} }
} }
selectTab(currentTab) selectTab(currentTab)
} catch (e: JSONException) { onComplete.invoke()
Log.w(TAG_KIWIX, "Kiwix shared preferences corrupted", e) } 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(R.string.could_not_restore_tabs, Toast.LENGTH_LONG)
} }
// After restoring the tabs, observe any search actions that the user might have triggered. // After restoring the tabs, observe any search actions that the user might have triggered.
@ -2671,6 +2804,18 @@ abstract class CoreReaderFragment :
) )
} }
/**
* Restores the state of a given KiwixWebView based on the provided WebViewHistoryItem.
*
* This method retrieves the back-forward list from the WebViewHistoryItem and
* uses it to restore the web view's state. It also sets the vertical scroll position
* of the web view to the position stored in the WebViewHistoryItem.
*
* If the provided WebViewHistoryItem is null, the method does nothing.
*
* @param webView The KiwixWebView instance whose state is to be restored.
* @param webViewHistoryItem The WebViewHistoryItem containing the saved state and scroll position.
*/
private fun restoreTabState(webView: KiwixWebView, webViewHistoryItem: WebViewHistoryItem?) { private fun restoreTabState(webView: KiwixWebView, webViewHistoryItem: WebViewHistoryItem?) {
webViewHistoryItem?.webViewBackForwardListBundle?.let { bundle -> webViewHistoryItem?.webViewBackForwardListBundle?.let { bundle ->
webView.restoreState(bundle) webView.restoreState(bundle)
@ -2744,28 +2889,29 @@ abstract class CoreReaderFragment :
} }
/** /**
* Restores the view state after successfully reading valid JSON from shared preferences. * Restores the view state after successfully reading valid webViewHistory from room database.
* Developers modifying this method in subclasses, such as CustomReaderFragment and * Developers modifying this method in subclasses, such as CustomReaderFragment and
* KiwixReaderFragment, should review and consider the implementations in those subclasses * KiwixReaderFragment, should review and consider the implementations in those subclasses
* (e.g., CustomReaderFragment.restoreViewStateOnValidJSON, * (e.g., CustomReaderFragment.restoreViewStateOnValidWebViewHistory,
* KiwixReaderFragment.restoreViewStateOnValidJSON) to ensure consistent behavior * KiwixReaderFragment.restoreViewStateOnValidWebViewHistory) to ensure consistent behavior
* when handling valid JSON scenarios. * when handling valid webViewHistory scenarios.
*/ */
protected abstract fun restoreViewStateOnValidJSON( protected abstract fun restoreViewStateOnValidWebViewHistory(
webViewHistoryItemList: List<WebViewHistoryItem>, webViewHistoryItemList: List<WebViewHistoryItem>,
currentTab: Int, currentTab: Int,
restoreOrigin: RestoreOrigin restoreOrigin: RestoreOrigin,
onComplete: () -> Unit
) )
/** /**
* Restores the view state when the attempt to read JSON from shared preferences fails * Restores the view state when the attempt to read webViewHistory from room database fails
* due to invalid or corrupted data. Developers modifying this method in subclasses, such as * due to the absence of any history records. Developers modifying this method in subclasses, such as
* CustomReaderFragment and KiwixReaderFragment, should review and consider the implementations * CustomReaderFragment and KiwixReaderFragment, should review and consider the implementations
* in those subclasses (e.g., CustomReaderFragment.restoreViewStateOnInvalidJSON, * in those subclasses (e.g., CustomReaderFragment.restoreViewStateOnInvalidWebViewHistory,
* KiwixReaderFragment.restoreViewStateOnInvalidJSON) to ensure consistent behavior * KiwixReaderFragment.restoreViewStateOnInvalidWebViewHistory) to ensure consistent behavior
* when handling invalid JSON scenarios. * when handling invalid JSON scenarios.
*/ */
abstract fun restoreViewStateOnInvalidJSON() abstract fun restoreViewStateOnInvalidWebViewHistory()
} }
enum class RestoreOrigin { enum class RestoreOrigin {

View File

@ -61,6 +61,7 @@ class MainMenu(
fun onRandomArticleMenuClicked() fun onRandomArticleMenuClicked()
fun onReadAloudMenuClicked() fun onReadAloudMenuClicked()
fun onFullscreenMenuClicked() fun onFullscreenMenuClicked()
fun onSearchMenuClickedMenuClicked()
} }
init { init {
@ -154,7 +155,7 @@ class MainMenu(
} }
private fun navigateToSearch(): Boolean { private fun navigateToSearch(): Boolean {
(activity as CoreMainActivity).openSearch(isOpenedFromTabView = isInTabSwitcher) menuClickListener.onSearchMenuClickedMenuClicked()
return true return true
} }

View File

@ -150,25 +150,26 @@ class CustomReaderFragment : CoreReaderFragment() {
} }
/** /**
* Restores the view state when the attempt to read JSON from shared preferences fails * Restores the view state when the attempt to read web view history from the room database fails
* due to invalid or corrupted data. In this case, it opens the homepage of the zim file, * due to the absence of any history records. In this case, it navigates to the homepage of the
* as custom apps always have the zim file available. * ZIM file, as custom apps are expected to have the ZIM file readily available.
*/ */
override fun restoreViewStateOnInvalidJSON() { override fun restoreViewStateOnInvalidWebViewHistory() {
openHomeScreen() openHomeScreen()
} }
/** /**
* Restores the view state when the JSON data is valid. This method restores the tabs * Restores the view state when the webViewHistory data is valid.
* and loads the last opened article in the specified tab. * This method restores the tabs with webView pages history.
*/ */
override fun restoreViewStateOnValidJSON( override fun restoreViewStateOnValidWebViewHistory(
webViewHistoryItemList: List<WebViewHistoryItem>, webViewHistoryItemList: List<WebViewHistoryItem>,
currentTab: Int, currentTab: Int,
// Unused in custom apps as there is only one ZIM file that is already set. // Unused in custom apps as there is only one ZIM file that is already set.
restoreOrigin: RestoreOrigin restoreOrigin: RestoreOrigin,
onComplete: () -> Unit
) { ) {
restoreTabs(webViewHistoryItemList, currentTab) restoreTabs(webViewHistoryItemList, currentTab, onComplete)
} }
/** /**
@ -183,6 +184,27 @@ class CustomReaderFragment : CoreReaderFragment() {
) )
} }
/**
* Opens a ZIM file or an OBB file based on the validation of available files.
*
* This method uses the `customFileValidator` to check for the presence of required files.
* Depending on the validation results, it performs the following actions:
*
* - If a valid ZIM file is found:
* - It opens the ZIM file and creates a `ZimReaderSource` for it.
* - Saves the book information in the database to be displayed in the `ZimHostFragment`.
* - Manages the external launch and restores the view state if specified.
*
* - If both ZIM and OBB files are found:
* - The ZIM file is deleted, and the OBB file is opened instead.
* - Manages the external launch and restores the view state if specified.
*
* If no valid files are found and the app is not in test mode, the user is navigated to
* the `customDownloadFragment` to facilitate downloading the required files.
*
* @param shouldManageExternalLaunch Indicates whether to manage external launch and
* restore the view state after opening the file. Default is false.
*/
private fun openObbOrZim(shouldManageExternalLaunch: Boolean = false) { private fun openObbOrZim(shouldManageExternalLaunch: Boolean = false) {
customFileValidator.validate( customFileValidator.validate(
onFilesFound = { onFilesFound = {