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 bf3883224e..d9be58a4f3 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 @@ -34,7 +34,6 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toFile import androidx.drawerlayout.widget.DrawerLayout -import androidx.lifecycle.Observer import com.google.android.material.bottomnavigation.BottomNavigationView import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.cachedComponent @@ -42,8 +41,6 @@ import org.kiwix.kiwixmobile.core.R.anim import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super.ShouldCall -import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.consumeObservable -import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigationResult import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setupDrawerToggle import org.kiwix.kiwixmobile.core.extensions.coreMainActivity import org.kiwix.kiwixmobile.core.extensions.isFileExist @@ -54,17 +51,12 @@ import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreReaderFragment import org.kiwix.kiwixmobile.core.main.CoreWebViewClient -import org.kiwix.kiwixmobile.core.main.FIND_IN_PAGE_SEARCH_STRING import org.kiwix.kiwixmobile.core.main.ToolbarScrollingKiwixWebView -import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.TAG_CURRENT_FILE -import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import org.kiwix.kiwixmobile.core.utils.files.FileUtils import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getAssetFileDescriptorFromUri -import org.kiwix.kiwixmobile.core.utils.titleToUrl -import org.kiwix.kiwixmobile.core.utils.urlSuffixToParsableUrl import java.io.File private const val HIDE_TAB_SWITCHER_DELAY: Long = 300 @@ -89,30 +81,6 @@ class KiwixReaderFragment : CoreReaderFragment() { toolbar?.let(activity::setupDrawerToggle) setFragmentContainerBottomMarginToSizeOfNavBar() openPageInBookFromNavigationArguments() - - requireActivity().observeNavigationResult( - FIND_IN_PAGE_SEARCH_STRING, - viewLifecycleOwner, - Observer(::findInPage) - ) - requireActivity().observeNavigationResult( - TAG_FILE_SEARCHED, - viewLifecycleOwner, - Observer(::openSearchItem) - ) - } - - private fun openSearchItem(item: SearchItemToOpen) { - item.pageUrl?.let(::loadUrlWithCurrentWebview) ?: kotlin.run { - // For handling the previously saved recent searches - zimReaderContainer?.titleToUrl(item.pageTitle)?.let { - if (item.shouldOpenInNewTab) { - createNewTab() - } - loadUrlWithCurrentWebview(zimReaderContainer?.urlSuffixToParsableUrl(it)) - } - } - requireActivity().consumeObservable(TAG_FILE_SEARCHED) } private fun openPageInBookFromNavigationArguments() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index c9e236e7f5..2f16e56213 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -78,6 +78,7 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.core.widget.ContentLoadingProgressBar import androidx.drawerlayout.widget.DrawerLayout import androidx.lifecycle.Lifecycle +import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -108,7 +109,9 @@ import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao import org.kiwix.kiwixmobile.core.downloader.fetch.DOWNLOAD_NOTIFICATION_TITLE +import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.consumeObservable import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission +import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigationResult import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.findFirstTextView import org.kiwix.kiwixmobile.core.extensions.isFileExist @@ -132,6 +135,7 @@ import org.kiwix.kiwixmobile.core.read_aloud.ReadAloudService.Companion.ACTION_P import org.kiwix.kiwixmobile.core.read_aloud.ReadAloudService.Companion.ACTION_STOP_TTS import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer +import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen import org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight import org.kiwix.kiwixmobile.core.utils.ExternalLinkOpener @@ -154,6 +158,8 @@ import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.utils.files.FileUtils.deleteCachedFiles import org.kiwix.kiwixmobile.core.utils.files.FileUtils.readFile +import org.kiwix.kiwixmobile.core.utils.titleToUrl +import org.kiwix.kiwixmobile.core.utils.urlSuffixToParsableUrl import java.io.File import java.io.IOException import java.text.SimpleDateFormat @@ -388,6 +394,18 @@ abstract class CoreReaderFragment : return FragmentActivityExtensions.Super.ShouldCall } + /** + * Configures the selection handler for the WebView. + * Subclasses like CustomReaderFragment override this method to customize + * the behavior of the selection handler menu. In this specific implementation, + * it sets up a menu item for reading aloud selected text. + * If the custom app is set to disable the read-aloud feature, + * the menu item will be hidden by CustomReaderFragment. + * it provides additional customization for custom apps. + * + * WARNING: If modifying this method, ensure thorough testing with custom apps + * to verify proper functionality. + */ protected open fun configureWebViewSelectionHandler(menu: Menu?) { menu?.findItem(R.id.menu_speak_text)?.setOnMenuItemClickListener { if (tts?.isInitialized == false) { @@ -488,6 +506,16 @@ abstract class CoreReaderFragment : readAloudService?.registerCallBack(this@CoreReaderFragment) } } + requireActivity().observeNavigationResult( + FIND_IN_PAGE_SEARCH_STRING, + viewLifecycleOwner, + Observer(::findInPage) + ) + requireActivity().observeNavigationResult( + TAG_FILE_SEARCHED, + viewLifecycleOwner, + Observer(::openSearchItem) + ) } private fun initTabCallback() { @@ -535,6 +563,12 @@ abstract class CoreReaderFragment : } } + /** + * Abstract method to be implemented by subclasses for loading drawer-related views. + * Subclasses like CustomReaderFragment and KiwixReaderFragment should override this method + * to set up specific views for both the left and right drawers, such as custom containers + * or navigation views. + */ protected abstract fun loadDrawerViews() override fun onCreateView( inflater: LayoutInflater, @@ -752,6 +786,14 @@ abstract class CoreReaderFragment : setTopMarginToWebViews(0) } + /** + * Sets the lock mode for the drawer, controlling whether the drawer can be opened or closed. + * Subclasses like CustomReaderFragment override this method to provide custom + * behavior, such as disabling the sidebar when configured not to show it. + * + * WARNING: If modifying this method, ensure thorough testing with custom apps + * to verify proper functionality. + */ protected open fun setDrawerLockMode(lockMode: Int) { drawerLayout?.setDrawerLockMode(lockMode) } @@ -1297,6 +1339,12 @@ abstract class CoreReaderFragment : } } + /** + * Abstract method to be implemented by (KiwixReaderFragment, CustomReaderFragment) + * for creating a new tab. + * Subclasses like CustomReaderFragment, KiwixReaderFragment override this method + * to define the specific behavior for creating a new tab. + */ protected abstract fun createNewTab() /** Creates the full screen AddNoteDialog, which is a DialogFragment */ @@ -1645,6 +1693,16 @@ abstract class CoreReaderFragment : openSearch("", isOpenedFromTabView = false, isVoice) } + private fun openSearchItem(item: SearchItemToOpen) { + zimReaderContainer?.titleToUrl(item.pageTitle)?.let { + if (item.shouldOpenInNewTab) { + createNewTab() + } + loadUrlWithCurrentWebview(zimReaderContainer?.urlSuffixToParsableUrl(it)) + } + requireActivity().consumeObservable(TAG_FILE_SEARCHED) + } + private fun handleIntentActions(intent: Intent) { Log.d(TAG_KIWIX, "action" + requireActivity().intent?.action) startIntentBasedOnAction(intent) @@ -1784,7 +1842,7 @@ abstract class CoreReaderFragment : } } - protected fun findInPage(title: String?) { + private fun findInPage(title: String?) { // if the search is localized trigger find in page UI. compatCallback?.apply { setActive() @@ -1796,6 +1854,15 @@ abstract class CoreReaderFragment : } } + /** + * Creates the main menu for the reader. + * Subclasses may override this method to customize the main menu creation process. + * For custom apps like CustomReaderFragment, this method dynamically generates the menu + * based on the app's configuration, considering features like "read aloud" and "tabs." + * + * WARNING: If modifying this method, ensure thorough testing with custom apps + * to verify proper functionality. + */ protected open fun createMainMenu(menu: Menu?): MainMenu? = menu?.let { menuFactory?.create( @@ -1981,6 +2048,17 @@ abstract class CoreReaderFragment : } } + /** + * Displays a dialog prompting the user to open the provided URL in a new tab. + * CustomReaderFragment override this method to customize the + * behavior of the "Open in New Tab" dialog. In this specific implementation, + * If the custom app is set to disable the tabs feature, + * it will not show the dialog with the ability to open the URL in a new tab, + * it provide additional customization for custom apps. + * + * WARNING: If modifying this method, ensure thorough testing with custom apps + * to verify proper functionality. + */ protected open fun showOpenInNewTabDialog(url: String) { alertDialogShower?.show( KiwixDialog.YesNoDialog.OpenInNewTab, @@ -2125,11 +2203,27 @@ abstract class CoreReaderFragment : } } + /** + * Restores the view state after successfully reading valid JSON from shared preferences. + * Developers modifying this method in subclasses, such as CustomReaderFragment and + * KiwixReaderFragment, should review and consider the implementations in those subclasses + * (e.g., CustomReaderFragment.restoreViewStateOnValidJSON, + * KiwixReaderFragment.restoreViewStateOnValidJSON) to ensure consistent behavior + * when handling valid JSON scenarios. + */ protected abstract fun restoreViewStateOnValidJSON( zimArticles: String?, zimPositions: String?, currentTab: Int ) + /** + * Restores the view state when the attempt to read JSON from shared preferences fails + * due to invalid or corrupted data. Developers modifying this method in subclasses, such as + * CustomReaderFragment and KiwixReaderFragment, should review and consider the implementations + * in those subclasses (e.g., CustomReaderFragment.restoreViewStateOnInvalidJSON, + * KiwixReaderFragment.restoreViewStateOnInvalidJSON) to ensure consistent behavior + * when handling invalid JSON scenarios. + */ abstract fun restoreViewStateOnInvalidJSON() } 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 20672ff4ee..4d0d98af28 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 @@ -26,23 +26,15 @@ import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.drawerlayout.widget.DrawerLayout -import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import org.kiwix.kiwixmobile.core.base.BaseActivity -import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions -import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigationResult import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setupDrawerToggle import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.main.CoreReaderFragment -import org.kiwix.kiwixmobile.core.main.FIND_IN_PAGE_SEARCH_STRING import org.kiwix.kiwixmobile.core.main.MainMenu -import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen import org.kiwix.kiwixmobile.core.utils.LanguageUtils -import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp -import org.kiwix.kiwixmobile.core.utils.titleToUrl -import org.kiwix.kiwixmobile.core.utils.urlSuffixToParsableUrl import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.custom.BuildConfig import org.kiwix.kiwixmobile.custom.R @@ -82,34 +74,6 @@ class CustomReaderFragment : CoreReaderFragment() { toolbar?.let { setupDrawerToggle(it) } } loadPageFromNavigationArguments() - - requireActivity().observeNavigationResult( - FIND_IN_PAGE_SEARCH_STRING, - viewLifecycleOwner, - Observer(::findInPage) - ) - requireActivity().observeNavigationResult( - TAG_FILE_SEARCHED, - viewLifecycleOwner, - Observer(::openSearchItem) - ) - } - } - - override fun onBackPressed(activity: AppCompatActivity): FragmentActivityExtensions.Super { - requireActivity().finish() - return super.onBackPressed(activity) - } - - private fun openSearchItem(item: SearchItemToOpen) { - item.pageUrl?.let(::loadUrlWithCurrentWebview) ?: kotlin.run { - // For handling the previously saved recent searches - zimReaderContainer?.titleToUrl(item.pageTitle)?.apply { - if (item.shouldOpenInNewTab) { - createNewTab() - } - loadUrlWithCurrentWebview(zimReaderContainer?.urlSuffixToParsableUrl(this)) - } } } @@ -124,10 +88,19 @@ class CustomReaderFragment : CoreReaderFragment() { requireArguments().clear() } + /** + * Restores the view state when the attempt to read JSON from shared preferences fails + * due to invalid or corrupted data. In this case, it opens the homepage of the zim file, + * as custom apps always have the zim file available. + */ override fun restoreViewStateOnInvalidJSON() { openHomeScreen() } + /** + * Restores the view state when the JSON data is valid. This method restores the tabs + * and loads the last opened article in the specified tab. + */ override fun restoreViewStateOnValidJSON( zimArticles: String?, zimPositions: String?, @@ -136,6 +109,11 @@ class CustomReaderFragment : CoreReaderFragment() { restoreTabs(zimArticles, zimPositions, currentTab) } + /** + * Sets the locking mode for the sidebar in a custom app. If the app is configured not to show the sidebar, + * this function disables the sidebar by locking it in the closed position through the parent class. + * https://developer.android.com/reference/kotlin/androidx/drawerlayout/widget/DrawerLayout#LOCK_MODE_LOCKED_CLOSED() + */ override fun setDrawerLockMode(lockMode: Int) { super.setDrawerLockMode( if (BuildConfig.DISABLE_SIDEBAR) DrawerLayout.LOCK_MODE_LOCKED_CLOSED @@ -205,11 +183,23 @@ class CustomReaderFragment : CoreReaderFragment() { return false } + /** + * This method is overridden to set the IDs of the `drawerLayout` and `tableDrawerRightContainer` + * specific to the custom module in the `CoreReaderFragment`. Since we have an app and a custom module, + * and `CoreReaderFragment` is a common class for both modules, we set the IDs of the custom module + * in the parent class to ensure proper integration. + */ override fun loadDrawerViews() { drawerLayout = requireActivity().findViewById(R.id.custom_drawer_container) tableDrawerRightContainer = requireActivity().findViewById(R.id.activity_main_nav_view) } + /** + * Overrides the method to create the main menu for the app. The custom app can be configured to disable + * features like "read aloud" and "tabs," and this method dynamically generates the menu based on the + * provided configuration. It takes into account whether read aloud and tabs are enabled or disabled + * and creates the menu accordingly. + */ override fun createMainMenu(menu: Menu?): MainMenu? { return menu?.let { menuFactory?.create( @@ -223,11 +213,22 @@ class CustomReaderFragment : CoreReaderFragment() { } } + /** + * Overrides the method to control the functionality of showing the "Open In New Tab" dialog. + * When a user long-clicks on an article, the app typically prompts the "ShowOpenInNewTabDialog." + * However, if a custom app is configured to disable the use of tabs, this function restricts + * the dialog from appearing. + */ override fun showOpenInNewTabDialog(url: String) { if (BuildConfig.DISABLE_TABS) return super.showOpenInNewTabDialog(url) } + /** + * Overrides the method to configure the WebView selection handler. When the "read aloud" feature is disabled + * in a custom app, this function hides the corresponding option from the menu that appears when the user selects + * text in the WebView. This prevents the "read aloud" option from being displayed in the menu when it's disabled. + */ override fun configureWebViewSelectionHandler(menu: Menu?) { if (BuildConfig.DISABLE_READ_ALOUD) { menu?.findItem(org.kiwix.kiwixmobile.core.R.id.menu_speak_text)?.isVisible = false