From daf5d51bc2ab40bc1526588e156dc1693984bed0 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 5 Sep 2025 22:19:24 +0530 Subject: [PATCH 1/2] Fixed: The Tabs history is not restoring in custom apps. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This happened because we moved the file scanning logic to the IO thread, and inside that, we were also opening the ZIM file in the reader on the IO thread, and then restoring the tabs (which is now done on the Dispatcher’s main thread instead of the UI main thread). Because of this, the WebView was loading freely and saving new history in the database, which cleared the previous history. As a result, the tabs were not restoring. * Now, we are not saving new WebView history while restoring tabs, which avoids this issue in a multi-threading environment. * Made `restoreViewStateOnValidWebViewHistory` and `restoreViewStateOnInvalidWebViewHistory` suspend functions so they can run in the same scope from where we are opening and restoring tabs. --- .../destination/reader/KiwixReaderFragment.kt | 45 +++++++++---------- .../core/main/reader/CoreReaderFragment.kt | 20 +++++++-- .../custom/main/CustomReaderFragment.kt | 6 ++- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt index ffb90cf4e..56f397916 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt @@ -110,6 +110,7 @@ class KiwixReaderFragment : CoreReaderFragment() { if (zimFileUri.isNotEmpty()) { tryOpeningZimFile(zimFileUri) } else { + isWebViewHistoryRestoring = true val restoreOrigin = if (searchItemTitle.isNotEmpty()) FromSearchScreen else FromExternalLaunch manageExternalLaunchAndRestoringViewState(restoreOrigin) @@ -224,7 +225,7 @@ class KiwixReaderFragment : CoreReaderFragment() { } } - override fun restoreViewStateOnInvalidWebViewHistory() { + override suspend fun restoreViewStateOnInvalidWebViewHistory() { Log.d(TAG_KIWIX, "Kiwix normal start, no zimFile loaded last time -> display home page") exitBook() } @@ -242,7 +243,7 @@ class KiwixReaderFragment : CoreReaderFragment() { * @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 suspend fun restoreViewStateOnValidWebViewHistory( webViewHistoryItemList: List, currentTab: Int, restoreOrigin: RestoreOrigin, @@ -250,29 +251,27 @@ class KiwixReaderFragment : CoreReaderFragment() { ) { when (restoreOrigin) { FromExternalLaunch -> { - coreReaderLifeCycleScope?.launch { - if (!isAdded) return@launch - val settings = - activity?.getSharedPreferences(SharedPreferenceUtil.PREF_KIWIX_MOBILE, 0) - val zimReaderSource = fromDatabaseValue(settings?.getString(TAG_CURRENT_FILE, null)) - if (zimReaderSource?.canOpenInLibkiwix() == true) { - if (zimReaderContainer?.zimReaderSource == null) { - openZimFile(zimReaderSource, isFromManageExternalLaunch = true) - Log.d( - TAG_KIWIX, - "Kiwix normal start, Opened last used zimFile: -> ${zimReaderSource.toDatabase()}" - ) - } else { - zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) - } - restoreTabs(webViewHistoryItemList, currentTab, onComplete) - } else { - readerScreenState.value.snackBarHostState.snack( - requireActivity().getString(string.zim_not_opened), - lifecycleScope = lifecycleScope + if (!isAdded) return + val settings = + activity?.getSharedPreferences(SharedPreferenceUtil.PREF_KIWIX_MOBILE, 0) + val zimReaderSource = fromDatabaseValue(settings?.getString(TAG_CURRENT_FILE, null)) + if (zimReaderSource?.canOpenInLibkiwix() == true) { + if (zimReaderContainer?.zimReaderSource == null) { + openZimFile(zimReaderSource, isFromManageExternalLaunch = true) + Log.d( + TAG_KIWIX, + "Kiwix normal start, Opened last used zimFile: -> ${zimReaderSource.toDatabase()}" ) - exitBook() // hide the options for zim file to avoid unexpected UI behavior + } else { + zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) } + restoreTabs(webViewHistoryItemList, currentTab, onComplete) + } else { + readerScreenState.value.snackBarHostState.snack( + requireActivity().getString(string.zim_not_opened), + lifecycleScope = lifecycleScope + ) + exitBook() // hide the options for zim file to avoid unexpected UI behavior } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt index 90fc8119b..b50c40469 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt @@ -172,6 +172,7 @@ import java.io.IOException import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject +import kotlin.concurrent.Volatile import kotlin.math.max const val SEARCH_ITEM_TITLE_KEY = "searchItemTitle" @@ -224,6 +225,8 @@ abstract class CoreReaderFragment : protected var currentWebViewIndex by mutableStateOf(0) private var currentTtsWebViewIndex = 0 private var isFirstTimeMainPageLoaded = true + + @Volatile private var isFromManageExternalLaunch = false private val savingTabsMutex = Mutex() private var searchItemToOpen: SearchItemToOpen? = null @@ -340,6 +343,8 @@ abstract class CoreReaderFragment : */ private var pendingIntent: Intent? = null + @Volatile var isWebViewHistoryRestoring = false + private var storagePermissionForNotesLauncher: ActivityResultLauncher? = registerForActivityResult( ActivityResultContracts.RequestPermission() @@ -2333,7 +2338,9 @@ abstract class CoreReaderFragment : showProgressBarWithProgress(progress) if (progress == 100) { hideProgressBar() - saveTabStates() + if (!isWebViewHistoryRestoring) { + saveTabStates() + } Log.d(TAG_KIWIX, "Loaded URL: " + getCurrentWebView()?.url) } (webView.context as AppCompatActivity).invalidateOptionsMenu() @@ -2434,6 +2441,7 @@ abstract class CoreReaderFragment : restoreViewStateOnInvalidWebViewHistory() // handle the pending intent if any present. handlePendingIntent() + isWebViewHistoryRestoring = false return } restoreViewStateOnValidWebViewHistory( @@ -2446,11 +2454,14 @@ abstract class CoreReaderFragment : // to open the specified item, then sets `searchItemToOpen` to null to prevent // any unexpected behavior on future calls. Similarly, if `findInPageTitle` is set, // it invokes `findInPage` and resets `findInPageTitle` to null. + isWebViewHistoryRestoring = false searchItemToOpen?.let(::openSearchItem) searchItemToOpen = null findInPageTitle?.let(::findInPage) findInPageTitle = null handlePendingIntent() + // When the restoration completes than save the tabs history. + saveTabStates() } }.onFailure { Log.e( @@ -2460,6 +2471,7 @@ abstract class CoreReaderFragment : restoreViewStateOnInvalidWebViewHistory() // handle the pending intent if any present. handlePendingIntent() + isWebViewHistoryRestoring = false } } @@ -2487,7 +2499,7 @@ abstract class CoreReaderFragment : * @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 suspend fun restoreTabs( webViewHistoryItemList: List, currentTab: Int, onComplete: () -> Unit @@ -2612,7 +2624,7 @@ abstract class CoreReaderFragment : * KiwixReaderFragment.restoreViewStateOnValidWebViewHistory) to ensure consistent behavior * when handling valid webViewHistory scenarios. */ - protected abstract fun restoreViewStateOnValidWebViewHistory( + protected abstract suspend fun restoreViewStateOnValidWebViewHistory( webViewHistoryItemList: List, currentTab: Int, restoreOrigin: RestoreOrigin, @@ -2627,7 +2639,7 @@ abstract class CoreReaderFragment : * KiwixReaderFragment.restoreViewStateOnInvalidWebViewHistory) to ensure consistent behavior * when handling invalid JSON scenarios. */ - abstract fun restoreViewStateOnInvalidWebViewHistory() + abstract suspend fun restoreViewStateOnInvalidWebViewHistory() } enum class RestoreOrigin { diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt index 87433e13f..f2c17f225 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt @@ -185,6 +185,7 @@ class CustomReaderFragment : CoreReaderFragment() { // See https://github.com/kiwix/kiwix-android/issues/3541 zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) } else { + isWebViewHistoryRestoring = true coreReaderLifeCycleScope?.launch { openObbOrZim(true) } @@ -197,7 +198,7 @@ class CustomReaderFragment : CoreReaderFragment() { * due to the absence of any history records. In this case, it navigates to the homepage of the * ZIM file, as custom apps are expected to have the ZIM file readily available. */ - override fun restoreViewStateOnInvalidWebViewHistory() { + override suspend fun restoreViewStateOnInvalidWebViewHistory() { openHomeScreen() } @@ -205,7 +206,7 @@ class CustomReaderFragment : CoreReaderFragment() { * Restores the view state when the webViewHistory data is valid. * This method restores the tabs with webView pages history. */ - override fun restoreViewStateOnValidWebViewHistory( + override suspend fun restoreViewStateOnValidWebViewHistory( webViewHistoryItemList: List, currentTab: Int, // Unused in custom apps as there is only one ZIM file that is already set. @@ -430,6 +431,7 @@ class CustomReaderFragment : CoreReaderFragment() { super.onResume() if (appSettingsLaunched) { appSettingsLaunched = false + isWebViewHistoryRestoring = true coreReaderLifeCycleScope?.launch { openObbOrZim(true) } From 47055aabbc6675ba96a7942b0e56b9ad95d8f911 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Sat, 6 Sep 2025 15:08:08 +0530 Subject: [PATCH 2/2] Fixed: When searching for a page, after opening the searched page it immediately opened the main page of that ZIM file. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This happened because ZIM file scanning was moved to the IO thread while the WebView was restored on the main thread (a WebView can only be accessed from the main thread) as part of the fix for #710. As a result, the main page sometimes loaded into the current WebView — on some pages it appeared immediately after the searched page. We have now fixed this behavior. * Previously, the ZIM file was always fetched and loaded in ZimFileReader whenever the reader screen became visible. This was redundant for custom apps, since they only use a single ZIM file. Once the ZIM file is set in the reader, there is no need to fetch and load it again. We have improved this as well: if there is no ZIM file in ZimFileReader, it will fetch it from the assets folder or storage and load it into the reader. If the ZIM file is already loaded, it will simply restore the previous history in the reader. This reduces unnecessary fetching and setting of the ZIM file. Since custom ZIM files can be large, avoiding redundant fetching and loading saves both time and computation. --- .../nav/destination/reader/KiwixReaderFragment.kt | 2 +- .../core/main/reader/CoreReaderFragment.kt | 9 ++++----- .../kiwixmobile/custom/main/CustomReaderFragment.kt | 13 +++++++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt index 56f397916..1d0009c1b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt @@ -81,7 +81,6 @@ class KiwixReaderFragment : CoreReaderFragment() { activity.navigate(KiwixDestination.Library.route, navOptions) }) } - activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) activity.enableLeftDrawer() openPageInBookFromNavigationArguments() } @@ -111,6 +110,7 @@ class KiwixReaderFragment : CoreReaderFragment() { tryOpeningZimFile(zimFileUri) } else { isWebViewHistoryRestoring = true + isFromManageExternalLaunch = true val restoreOrigin = if (searchItemTitle.isNotEmpty()) FromSearchScreen else FromExternalLaunch manageExternalLaunchAndRestoringViewState(restoreOrigin) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt index b50c40469..fe5c455a2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt @@ -226,8 +226,7 @@ abstract class CoreReaderFragment : private var currentTtsWebViewIndex = 0 private var isFirstTimeMainPageLoaded = true - @Volatile - private var isFromManageExternalLaunch = false + @Volatile var isFromManageExternalLaunch = false private val savingTabsMutex = Mutex() private var searchItemToOpen: SearchItemToOpen? = null private var findInPageTitle: String? = null @@ -2309,6 +2308,9 @@ abstract class CoreReaderFragment : } updateBottomToolbarVisibility() updateNightMode() + if (!isWebViewHistoryRestoring) { + saveTabStates() + } } } @@ -2338,9 +2340,6 @@ abstract class CoreReaderFragment : showProgressBarWithProgress(progress) if (progress == 100) { hideProgressBar() - if (!isWebViewHistoryRestoring) { - saveTabStates() - } Log.d(TAG_KIWIX, "Loaded URL: " + getCurrentWebView()?.url) } (webView.context as AppCompatActivity).invalidateOptionsMenu() diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt index f2c17f225..d3931a420 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt @@ -186,8 +186,13 @@ class CustomReaderFragment : CoreReaderFragment() { zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) } else { isWebViewHistoryRestoring = true + isFromManageExternalLaunch = true coreReaderLifeCycleScope?.launch { - openObbOrZim(true) + if (zimReaderContainer?.zimFileReader == null || zimReaderContainer?.zimReaderSource?.exists() == false) { + openObbOrZim(true) + } else { + manageExternalLaunchAndRestoringViewState() + } } } customMainActivity?.consumeObservable(PAGE_URL_KEY) @@ -433,7 +438,11 @@ class CustomReaderFragment : CoreReaderFragment() { appSettingsLaunched = false isWebViewHistoryRestoring = true coreReaderLifeCycleScope?.launch { - openObbOrZim(true) + if (zimReaderContainer?.zimFileReader == null) { + openObbOrZim(true) + } else { + manageExternalLaunchAndRestoringViewState() + } } } }