diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/main/TopLevelDestinationTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/main/TopLevelDestinationTest.kt index 3da9f162c..c1515fa7e 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/main/TopLevelDestinationTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/main/TopLevelDestinationTest.kt @@ -109,7 +109,7 @@ class TopLevelDestinationTest : BaseActivityTest() { fun testTopLevelDestination() { topLevel { clickReaderOnBottomNav { - assertReaderScreenDisplayed() + assertReaderScreenDisplayed(composeTestRule) } clickDownloadOnBottomNav { onlineLibrary { diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt index 5c830c374..44c464c1c 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt @@ -42,6 +42,7 @@ import org.kiwix.kiwixmobile.nav.destination.library.local.NO_FILE_TEXT_TESTING_ import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils.refresh import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView +import org.kiwix.kiwixmobile.testutils.TestUtils.waitUntilTimeout import org.kiwix.kiwixmobile.ui.BOOK_ITEM_TESTING_TAG fun library(func: LibraryRobot.() -> Unit) = LibraryRobot().applyWithViewHierarchyPrinting(func) @@ -102,8 +103,8 @@ class LibraryRobot : BaseRobot() { } fun waitUntilZimFilesRefreshing(composeTestRule: ComposeContentTestRule) { - pauseForBetterTestPerformance() testFlakyView({ + composeTestRule.waitUntilTimeout() composeTestRule.onNodeWithTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG) .assertIsNotDisplayed() }) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/reader/ReaderRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/reader/ReaderRobot.kt index a74c3705d..70989eae2 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/reader/ReaderRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/reader/ReaderRobot.kt @@ -18,15 +18,20 @@ package org.kiwix.kiwixmobile.nav.destination.reader +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onNodeWithTag import applyWithViewHierarchyPrinting import org.kiwix.kiwixmobile.BaseRobot -import org.kiwix.kiwixmobile.Findable.ViewId -import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.main.reader.READER_SCREEN_TESTING_TAG +import org.kiwix.kiwixmobile.testutils.TestUtils.waitUntilTimeout fun reader(func: ReaderRobot.() -> Unit) = ReaderRobot().applyWithViewHierarchyPrinting(func) class ReaderRobot : BaseRobot() { - fun assertReaderScreenDisplayed() { - isVisible(ViewId(R.id.activity_main_root)) + fun assertReaderScreenDisplayed(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + onNodeWithTag(READER_SCREEN_TESTING_TAG).assertExists() + } } } diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryRobot.kt index 1c234ba80..41eeb1e4b 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryRobot.kt @@ -20,11 +20,13 @@ package org.kiwix.kiwixmobile.page.history import android.util.Log import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.longClick +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.longClick import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.web.sugar.Web.onWebView import androidx.test.espresso.web.webdriver.DriverAtoms.findElement @@ -36,11 +38,13 @@ import junit.framework.AssertionFailedError import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.main.reader.TAB_SWITCHER_VIEW_TESTING_TAG import org.kiwix.kiwixmobile.core.page.DELETE_MENU_ICON_TESTING_TAG import org.kiwix.kiwixmobile.core.ui.components.TOOLBAR_TITLE_TESTING_TAG import org.kiwix.kiwixmobile.core.utils.dialog.ALERT_DIALOG_TITLE_TEXT_TESTING_TAG import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView +import org.kiwix.kiwixmobile.testutils.TestUtils.waitUntilTimeout fun navigationHistory(func: NavigationHistoryRobot.() -> Unit) = NavigationHistoryRobot().applyWithViewHierarchyPrinting(func) @@ -55,12 +59,14 @@ class NavigationHistoryRobot : BaseRobot() { isVisible(ViewId(readerFragment)) } - fun closeTabSwitcherIfVisible() { + fun closeTabSwitcherIfVisible(composeTestRule: ComposeContentTestRule) { try { - pauseForBetterTestPerformance() - isVisible(ViewId(R.id.tab_switcher_close_all_tabs)) - pressBack() - } catch (_: Exception) { + composeTestRule.apply { + waitUntilTimeout() + onNodeWithTag(TAB_SWITCHER_VIEW_TESTING_TAG).assertExists() + pressBack() + } + } catch (_: AssertionError) { Log.i( "NAVIGATION_HISTORY_TEST", "Couldn't found tab switcher, probably it is not visible" @@ -91,14 +97,24 @@ class NavigationHistoryRobot : BaseRobot() { ) } - fun longClickOnBackwardButton() { - pauseForBetterTestPerformance() - testFlakyView({ onView(withId(R.id.bottom_toolbar_arrow_back)).perform(longClick()) }) + fun longClickOnBackwardButton(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + testFlakyView({ + onNodeWithContentDescription(context.getString(R.string.go_to_previous_page)) + .performTouchInput { longClick() } + }) + } } - fun longClickOnForwardButton() { - pauseForBetterTestPerformance() - longClickOn(ViewId(R.id.bottom_toolbar_arrow_forward)) + fun longClickOnForwardButton(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + testFlakyView({ + onNodeWithContentDescription(context.getString(R.string.go_to_next_page)) + .performTouchInput { longClick() } + }) + } } fun assertBackwardNavigationHistoryDialogDisplayed(composeTestRule: ComposeContentTestRule) { @@ -117,9 +133,12 @@ class NavigationHistoryRobot : BaseRobot() { } } - fun clickOnBackwardButton() { - pauseForBetterTestPerformance() - clickOn(ViewId(R.id.bottom_toolbar_arrow_back)) + fun clickOnBackwardButton(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + onNodeWithContentDescription(context.getString(R.string.go_to_previous_page)) + .performClick() + } } fun assertForwardNavigationHistoryDialogDisplayed(composeTestRule: ComposeContentTestRule) { diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryTest.kt index da96ca91b..72fdd987d 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/history/NavigationHistoryTest.kt @@ -147,14 +147,14 @@ class NavigationHistoryTest : BaseActivityTest() { } StandardActions.closeDrawer() // close the drawer if open before running the test cases. navigationHistory { - closeTabSwitcherIfVisible() + closeTabSwitcherIfVisible(composeTestRule) checkZimFileLoadedSuccessful(R.id.readerFragment) clickOnAndroidArticle() - longClickOnBackwardButton() + longClickOnBackwardButton(composeTestRule) assertBackwardNavigationHistoryDialogDisplayed(composeTestRule) pressBack() - clickOnBackwardButton() - longClickOnForwardButton() + clickOnBackwardButton(composeTestRule) + longClickOnForwardButton(composeTestRule) assertForwardNavigationHistoryDialogDisplayed(composeTestRule) clickOnDeleteHistory(composeTestRule) assertDeleteDialogDisplayed(composeTestRule) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/KiwixReaderFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/KiwixReaderFragmentTest.kt index bfd6bbd3c..3ac58bb91 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/KiwixReaderFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/KiwixReaderFragmentTest.kt @@ -19,6 +19,7 @@ package org.kiwix.kiwixmobile.reader import android.os.Build +import androidx.compose.ui.test.junit4.createComposeRule import androidx.core.content.edit import androidx.core.net.toUri import androidx.lifecycle.Lifecycle @@ -46,6 +47,7 @@ import org.kiwix.kiwixmobile.BaseActivityTest import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.core.utils.TestingUtils.COMPOSE_TEST_RULE_ORDER import org.kiwix.kiwixmobile.core.utils.TestingUtils.RETRY_RULE_ORDER import org.kiwix.kiwixmobile.main.KiwixMainActivity import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections @@ -64,6 +66,9 @@ class KiwixReaderFragmentTest : BaseActivityTest() { @JvmField val retryRule = RetryRule() + @get:Rule(order = COMPOSE_TEST_RULE_ORDER) + val composeTestRule = createComposeRule() + private lateinit var kiwixMainActivity: KiwixMainActivity @Before @@ -136,10 +141,10 @@ class KiwixReaderFragmentTest : BaseActivityTest() { openKiwixReaderFragmentWithFile(zimFile) reader { checkZimFileLoadedSuccessful(R.id.readerFragment) - clickOnTabIcon() - clickOnClosedAllTabsButton() - clickOnUndoButton() - assertTabRestored() + clickOnTabIcon(composeTestRule) + clickOnClosedAllTabsButton(composeTestRule) + clickOnUndoButton(composeTestRule) + assertTabRestored(composeTestRule) pressBack() checkZimFileLoadedSuccessful(R.id.readerFragment) } @@ -171,6 +176,8 @@ class KiwixReaderFragmentTest : BaseActivityTest() { openKiwixReaderFragmentWithFile(downloadingZimFile) reader { checkZimFileLoadedSuccessful(R.id.readerFragment) + clickOnTabIcon(composeTestRule) + clickOnTabIcon(composeTestRule) // test the whole welcome page is loaded or not assertArticleLoaded("Hydrogène") assertArticleLoaded("Automobile") diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/ReaderRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/ReaderRobot.kt index a85a4e7be..feb384d6e 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/ReaderRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/reader/ReaderRobot.kt @@ -18,10 +18,12 @@ package org.kiwix.kiwixmobile.reader -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onAllNodesWithTag +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.espresso.web.sugar.Web.onWebView import androidx.test.espresso.web.webdriver.DriverAtoms.findElement import androidx.test.espresso.web.webdriver.DriverAtoms.webClick @@ -29,11 +31,13 @@ import androidx.test.espresso.web.webdriver.Locator import applyWithViewHierarchyPrinting import com.adevinta.android.barista.interaction.BaristaSleepInteractions import org.kiwix.kiwixmobile.BaseRobot -import org.kiwix.kiwixmobile.Findable.Text import org.kiwix.kiwixmobile.Findable.ViewId -import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.main.reader.CLOSE_ALL_TABS_BUTTON_TESTING_TAG +import org.kiwix.kiwixmobile.core.main.reader.TAB_MENU_ITEM_TESTING_TAG +import org.kiwix.kiwixmobile.core.main.reader.TAB_TITLE_TESTING_TAG import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView +import org.kiwix.kiwixmobile.testutils.TestUtils.waitUntilTimeout fun reader(func: ReaderRobot.() -> Unit) = ReaderRobot().applyWithViewHierarchyPrinting(func) @@ -45,30 +49,43 @@ class ReaderRobot : BaseRobot() { isVisible(ViewId(readerFragment)) } - fun clickOnTabIcon() { - pauseForBetterTestPerformance() - testFlakyView({ onView(withId(R.id.ic_tab_switcher_text)).perform(click()) }) + fun clickOnTabIcon(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + testFlakyView({ + onNodeWithTag(TAB_MENU_ITEM_TESTING_TAG).performClick() + }) + } } - fun clickOnClosedAllTabsButton() { - pauseForBetterTestPerformance() - clickOn(ViewId(R.id.tab_switcher_close_all_tabs)) + fun clickOnClosedAllTabsButton(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + testFlakyView({ + onNodeWithTag(CLOSE_ALL_TABS_BUTTON_TESTING_TAG).performClick() + }) + } } - fun clickOnUndoButton() { + fun clickOnUndoButton(composeTestRule: ComposeContentTestRule) { try { - onView(withText("UNDO")).perform(click()) - } catch (runtimeException: RuntimeException) { + composeTestRule.apply { + onNodeWithText("UNDO", useUnmergedTree = true) + .performClick() + } + } catch (_: AssertionError) { if (retryCountForClickOnUndoButton > 0) { retryCountForClickOnUndoButton-- - clickOnUndoButton() + clickOnUndoButton(composeTestRule) } } } - fun assertTabRestored() { - pauseForBetterTestPerformance() - isVisible(Text("Test Zim")) + fun assertTabRestored(composeTestRule: ComposeContentTestRule) { + composeTestRule.apply { + waitUntilTimeout() + onAllNodesWithTag(TAB_TITLE_TESTING_TAG)[0].assertTextEquals("Test Zim") + } } private fun pauseForBetterTestPerformance() { diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/search/SearchRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/search/SearchRobot.kt index ba2509ed8..f89947460 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/search/SearchRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/search/SearchRobot.kt @@ -27,9 +27,6 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.web.sugar.Web.onWebView import androidx.test.espresso.web.webdriver.DriverAtoms.findElement import androidx.test.espresso.web.webdriver.Locator @@ -38,7 +35,7 @@ import applyWithViewHierarchyPrinting import com.adevinta.android.barista.interaction.BaristaSleepInteractions import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.Findable.ViewId -import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG import org.kiwix.kiwixmobile.core.search.SEARCH_FIELD_TESTING_TAG import org.kiwix.kiwixmobile.core.search.SEARCH_ITEM_TESTING_TAG import org.kiwix.kiwixmobile.core.ui.components.NAVIGATION_ICON_TESTING_TAG @@ -122,12 +119,16 @@ class SearchRobot : BaseRobot() { composeTestRule.onNodeWithTag(NAVIGATION_ICON_TESTING_TAG).performClick() } - private fun openSearchScreen() { - testFlakyView({ onView(withId(R.id.menu_search)).perform(click()) }) + private fun openSearchScreen(composeTestRule: ComposeContentTestRule) { + testFlakyView( + { + composeTestRule.onNodeWithTag(SEARCH_ICON_TESTING_TAG).performClick() + } + ) } fun searchAndClickOnArticle(searchString: String, composeTestRule: ComposeContentTestRule) { - openSearchScreen() + openSearchScreen(composeTestRule) searchWithFrequentlyTypedWords(searchString, composeTestRule = composeTestRule) clickOnSearchItemInSearchList(composeTestRule) checkZimFileSearchSuccessful(org.kiwix.kiwixmobile.R.id.readerFragment) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/shortcuts/GetContentShortcutTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/shortcuts/GetContentShortcutTest.kt index 1ddd8b795..3085a21e8 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/shortcuts/GetContentShortcutTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/shortcuts/GetContentShortcutTest.kt @@ -128,7 +128,7 @@ class GetContentShortcutTest { onlineLibrary { assertOnlineLibraryFragmentDisplayed(composeTestRule) } topLevel { clickReaderOnBottomNav { - assertReaderScreenDisplayed() + assertReaderScreenDisplayed(composeTestRule) } clickDownloadOnBottomNav { onlineLibrary { assertOnlineLibraryFragmentDisplayed(composeTestRule) } 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 8b65c91fe..abfe10b1b 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 @@ -48,6 +48,7 @@ import org.kiwix.kiwixmobile.core.extensions.toast 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.HIDE_TAB_SWITCHER_DELAY import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromExternalLaunch import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromSearchScreen @@ -62,8 +63,6 @@ import org.kiwix.kiwixmobile.core.utils.files.FileUtils import org.kiwix.kiwixmobile.core.utils.files.Log import java.io.File -private const val HIDE_TAB_SWITCHER_DELAY: Long = 300 - class KiwixReaderFragment : CoreReaderFragment() { private var isFullScreenVideo: Boolean = false diff --git a/app/src/main/res/menu/menu_language.xml b/app/src/main/res/menu/menu_language.xml deleted file mode 100644 index 980a1e234..000000000 --- a/app/src/main/res/menu/menu_language.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/menu/menu_zim_manager.xml b/app/src/main/res/menu/menu_zim_manager.xml deleted file mode 100644 index 9615f69b5..000000000 --- a/app/src/main/res/menu/menu_zim_manager.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/menu/wifi_file_share_items.xml b/app/src/main/res/menu/wifi_file_share_items.xml deleted file mode 100644 index 671bcf2e9..000000000 --- a/app/src/main/res/menu/wifi_file_share_items.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt index a5d38dcf4..d769da320 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt @@ -17,19 +17,12 @@ */ package org.kiwix.kiwixmobile.core.di.modules -import android.app.Activity -import android.view.Menu import dagger.Binds import dagger.Module import dagger.Provides import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.di.ActivityScope -import org.kiwix.kiwixmobile.core.main.KiwixWebView -import org.kiwix.kiwixmobile.core.main.MainMenu -import org.kiwix.kiwixmobile.core.main.MainMenu.Factory -import org.kiwix.kiwixmobile.core.main.MainMenu.MenuClickListener import org.kiwix.kiwixmobile.core.main.MainRepositoryActions -import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower @@ -44,33 +37,5 @@ abstract class ActivityModule { @ActivityScope fun providesMainPresenter(dataSource: DataSource): MainRepositoryActions = MainRepositoryActions(dataSource) - - @Provides - @ActivityScope - fun providesMainMenuFactory( - activity: Activity, - zimReaderContainer: ZimReaderContainer - ): MainMenu.Factory = object : Factory { - override fun create( - menu: Menu, - webViews: MutableList, - urlIsValid: Boolean, - menuClickListener: MenuClickListener, - disableReadAloud: Boolean, - disableTabs: Boolean, - disableSearch: Boolean - ): MainMenu = - MainMenu( - activity, - zimReaderContainer.zimFileReader, - menu, - webViews, - urlIsValid, - disableReadAloud, - disableTabs, - disableSearch, - menuClickListener - ) - } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt deleted file mode 100644 index 415dcaa9c..000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.main - -import android.app.Activity -import android.content.res.Configuration -import android.view.Menu -import android.view.MenuItem -import android.widget.TextView -import androidx.core.view.isVisible -import org.kiwix.kiwixmobile.core.R -import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription -import org.kiwix.kiwixmobile.core.reader.ZimFileReader - -const val REQUEST_FILE_SEARCH = 1236 - -@Suppress("LongParameterList") -class MainMenu( - private val activity: Activity, - zimFileReader: ZimFileReader?, - menu: Menu, - webViews: MutableList, - urlIsValid: Boolean, - disableReadAloud: Boolean = false, - disableTabs: Boolean = false, - private val disableSearch: Boolean = false, - private val menuClickListener: MenuClickListener -) { - interface Factory { - fun create( - menu: Menu, - webViews: MutableList, - urlIsValid: Boolean, - menuClickListener: MenuClickListener, - disableReadAloud: Boolean, - disableTabs: Boolean, - disableSearch: Boolean = false - ): MainMenu - } - - interface MenuClickListener { - fun onTabMenuClicked() - fun onHomeMenuClicked() - fun onAddNoteMenuClicked() - fun onRandomArticleMenuClicked() - fun onReadAloudMenuClicked() - fun onFullscreenMenuClicked() - fun onSearchMenuClickedMenuClicked() - } - - init { - activity.menuInflater.inflate(R.menu.menu_main, menu) - } - - private val search = menu.findItem(R.id.menu_search) - private var tabSwitcher: MenuItem? = menu.findItem(R.id.menu_tab_switcher) - private var tabSwitcherTextView: TextView? = - tabSwitcher?.actionView?.findViewById(R.id.ic_tab_switcher_text) - private val addNote = menu.findItem(R.id.menu_add_note) - private val randomArticle = menu.findItem(R.id.menu_random_article) - private val fullscreen = menu.findItem(R.id.menu_fullscreen) - private var readAloud: MenuItem? = menu.findItem(R.id.menu_read_aloud) - private var isInTabSwitcher: Boolean = false - - init { - if (disableReadAloud) { - readAloud?.isVisible = false - readAloud = null - } - if (disableTabs) { - tabSwitcher?.isVisible = false - tabSwitcherTextView?.isVisible = false - tabSwitcher = null - tabSwitcherTextView = null - } - - if (disableSearch) { - search?.isVisible = false - } - - randomArticle.setShowAsAction( - if (activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - MenuItem.SHOW_AS_ACTION_IF_ROOM - } else { - MenuItem.SHOW_AS_ACTION_NEVER - } - ) - tabSwitcher?.actionView?.apply { - setOnClickListener { menuClickListener.onTabMenuClicked() } - setToolTipWithContentDescription(resources.getString(R.string.switch_tabs)) - } - addNote.menuItemClickListener { menuClickListener.onAddNoteMenuClicked() } - randomArticle.menuItemClickListener { menuClickListener.onRandomArticleMenuClicked() } - readAloud.menuItemClickListener { menuClickListener.onReadAloudMenuClicked() } - fullscreen.menuItemClickListener { menuClickListener.onFullscreenMenuClicked() } - - showWebViewOptions(urlIsValid) - zimFileReader?.let { - onFileOpened(urlIsValid) - } - updateTabIcon(webViews.size) - } - - fun onOptionsItemSelected(item: MenuItem) = - when (item.itemId) { - android.R.id.home -> { - menuClickListener.onHomeMenuClicked() - true - } - - else -> false - } - - fun onFileOpened(urlIsValid: Boolean) { - setVisibility(urlIsValid, randomArticle, search, readAloud, addNote, fullscreen, tabSwitcher) - search.setOnMenuItemClickListener { navigateToSearch() } - } - - fun hideBookSpecificMenuItems() { - setVisibility(false, search, tabSwitcher, randomArticle, addNote, readAloud) - } - - fun showBookSpecificMenuItems() { - setVisibility(true, search, tabSwitcher, randomArticle, addNote, readAloud) - } - - fun showTabSwitcherOptions() { - isInTabSwitcher = true - setVisibility(false, randomArticle, readAloud, addNote, fullscreen) - } - - fun showWebViewOptions(urlIsValid: Boolean) { - isInTabSwitcher = false - fullscreen.isVisible = true - setVisibility(urlIsValid, randomArticle, search, readAloud, addNote, tabSwitcher) - } - - fun updateTabIcon(tabs: Int) { - tabSwitcherTextView?.text = if (tabs > 99) ":D" else "$tabs" - } - - private fun navigateToSearch(): Boolean { - menuClickListener.onSearchMenuClickedMenuClicked() - return true - } - - fun onTextToSpeechStartedTalking() { - readAloud?.setTitle(R.string.menu_read_aloud_stop) - } - - fun onTextToSpeechStoppedTalking() { - readAloud?.setTitle(R.string.menu_read_aloud) - } - - private fun setVisibility(visibility: Boolean, vararg menuItems: MenuItem?) { - menuItems.forEach { - if (it == search && disableSearch) { - it?.isVisible = false - } else { - it?.isVisible = visibility - } - } - } - - fun tryExpandSearch(zimFileReader: ZimFileReader?) { - if (search.isVisible && zimFileReader != null) { - navigateToSearch() - } - } - - fun isInTabSwitcher(): Boolean = isInTabSwitcher -} - -private fun MenuItem?.menuItemClickListener(function: (MenuItem) -> Unit) { - this?.setOnMenuItemClickListener { - function.invoke(it) - true - } -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/TabsAdapter.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/TabsAdapter.kt deleted file mode 100644 index 38f99f8e2..000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/TabsAdapter.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.main - -import android.annotation.SuppressLint -import android.text.TextUtils -import android.util.TypedValue -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.card.MaterialCardView -import org.kiwix.kiwixmobile.core.R -import com.google.android.material.R.attr -import org.kiwix.kiwixmobile.core.extensions.getAttribute -import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat -import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription -import org.kiwix.kiwixmobile.core.extensions.tint -import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight -import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowHeight -import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth -import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml - -class TabsAdapter internal constructor( - private val activity: AppCompatActivity, - private val webViews: List, - private val painter: DarkModeViewPainter -) : RecyclerView.Adapter() { - init { - setHasStableIds(true) - } - - private var listener: TabClickListener? = null - var selected = 0 - - @SuppressLint("ResourceType") override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): ViewHolder { - val context = parent.context - val margin16 = context.resources.getDimensionPixelSize(R.dimen.activity_horizontal_margin) - val closeImageWidthAndHeight = - context.resources.getDimensionPixelSize(R.dimen.close_tab_button_size) - val close = - ImageView(context) - .apply { - id = R.id.tabsAdapterCloseImageView - setImageDrawableCompat(R.drawable.ic_clear_white_24dp) - setToolTipWithContentDescription(resources.getString(R.string.close_tab)) - val outValue = TypedValue() - context.theme.resolveAttribute(android.R.attr.actionBarItemBackground, outValue, true) - setBackgroundResource(outValue.resourceId) - tint(context.getAttribute(attr.colorOnSurface)) - } - val cardView = - MaterialCardView(context) - .apply { - id = R.id.tabsAdapterCardView - useCompatPadding = true - } - val textView = - TextView(context) - .apply { - id = R.id.tabsAdapterTextView - maxLines = 1 - ellipsize = TextUtils.TruncateAt.END - } - val constraintLayout = - ConstraintLayout(context) - .apply { - isFocusableInTouchMode = true - addView( - cardView, - ConstraintLayout.LayoutParams( - activity.getWindowWidth() / 2, - -activity.getToolbarHeight() / 2 + activity.getWindowHeight() / 2 - ) - ) - addView( - close, - ConstraintLayout.LayoutParams(closeImageWidthAndHeight, closeImageWidthAndHeight) - ) - layoutParams = - RecyclerView.LayoutParams( - RecyclerView.LayoutParams.WRAP_CONTENT, - RecyclerView.LayoutParams.MATCH_PARENT - ) - addView( - textView, - ConstraintLayout.LayoutParams(0, ConstraintLayout.LayoutParams.WRAP_CONTENT) - ) - } - ConstraintSet() - .apply { - clone(constraintLayout) - connect(cardView.id, TOP, PARENT_ID, TOP) - connect(cardView.id, BOTTOM, PARENT_ID, BOTTOM) - connect(cardView.id, START, PARENT_ID, START, margin16) - connect(cardView.id, END, PARENT_ID, END, margin16) - connect(close.id, END, cardView.id, END) - connect(close.id, BOTTOM, cardView.id, TOP) - connect(textView.id, BOTTOM, cardView.id, TOP) - connect(textView.id, START, cardView.id, START, margin16 / 8) - connect(textView.id, END, close.id, START) - applyTo(constraintLayout) - } - return ViewHolder(constraintLayout, textView, close, cardView) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val webView = webViews[position] - webView.parent?.let { (it as ViewGroup).removeView(webView) } - val webViewTitle = webView.title.fromHtml().toString() - holder.apply { - title.text = webViewTitle - close.setOnClickListener { v: View -> listener?.onCloseTab(v, adapterPosition) } - materialCardView.apply { - removeAllViews() - // Create a new FrameLayout to hold the web view and custom view - val frameLayout = FrameLayout(context) - // Add the web view to the frame layout - frameLayout.addView(webView) - // Create a custom view that covers the entire - // webView(which prevent to clicks inside the webView) and handles tab selection - val view = - View(context).apply { - layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - setOnClickListener { v: View -> - selected = adapterPosition - listener?.onSelectTab(v, selected) - notifyDataSetChanged() - } - } - // Add the custom view to the frame layout - frameLayout.addView(view) - // Add the frame layout to the material card view - addView( - frameLayout, - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - ) - } - } - if (webViewTitle != activity.getString(R.string.menu_home)) { - painter.update(webView) // if the webView is not opened yet - } - } - - override fun getItemCount(): Int = webViews.size - - override fun getItemId(position: Int): Long = webViews[position].hashCode().toLong() - - fun setTabClickListener(listener: TabClickListener) { - this.listener = listener - } - - interface TabClickListener { - fun onSelectTab(view: View, position: Int) - fun onCloseTab(view: View, position: Int) - } - - class ViewHolder( - view: View, - val title: TextView, - val close: ImageView, - val materialCardView: MaterialCardView - ) : - RecyclerView.ViewHolder(view) -} 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 8de176eb1..e4e2fc251 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 @@ -27,7 +27,6 @@ import android.content.ServiceConnection import android.content.SharedPreferences import android.content.pm.PackageManager import android.content.res.Configuration -import android.graphics.Canvas import android.media.AudioManager import android.media.AudioManager.OnAudioFocusChangeListener import android.net.Uri @@ -44,24 +43,18 @@ import android.view.Gravity.BOTTOM import android.view.Gravity.CENTER_HORIZONTAL import android.view.LayoutInflater import android.view.Menu -import android.view.MotionEvent import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import android.view.animation.AnimationUtils import android.webkit.WebBackForwardList import android.webkit.WebView import android.widget.FrameLayout -import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.AnimRes -import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.SnackbarDuration @@ -83,10 +76,8 @@ import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.navigation.NavigationView @@ -114,14 +105,12 @@ import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity -import org.kiwix.kiwixmobile.core.databinding.FragmentReaderBinding import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.consumeObservable import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isLandScapeMode 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.closeFullScreenMode import org.kiwix.kiwixmobile.core.extensions.showFullScreenMode import org.kiwix.kiwixmobile.core.extensions.snack @@ -140,14 +129,11 @@ import org.kiwix.kiwixmobile.core.main.KiwixTextToSpeech import org.kiwix.kiwixmobile.core.main.KiwixTextToSpeech.OnInitSucceedListener import org.kiwix.kiwixmobile.core.main.KiwixTextToSpeech.OnSpeakingListener import org.kiwix.kiwixmobile.core.main.KiwixWebView -import org.kiwix.kiwixmobile.core.main.MainMenu import org.kiwix.kiwixmobile.core.main.MainRepositoryActions -import org.kiwix.kiwixmobile.core.main.OnSwipeTouchListener 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.TabsAdapter import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS import org.kiwix.kiwixmobile.core.main.WebViewCallback import org.kiwix.kiwixmobile.core.main.WebViewProvider @@ -202,10 +188,10 @@ import java.io.IOException import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject -import kotlin.math.abs import kotlin.math.max const val SEARCH_ITEM_TITLE_KEY = "searchItemTitle" +const val HIDE_TAB_SWITCHER_DELAY: Long = 300 @Suppress("LargeClass") abstract class CoreReaderFragment : @@ -219,15 +205,9 @@ abstract class CoreReaderFragment : ShowDonationDialogCallback { protected val webViewList = mutableStateListOf() private val webUrlsFlow = MutableStateFlow("") - private var fragmentReaderBinding: FragmentReaderBinding? = null - - var toolbar: Toolbar? = null var drawerLayout: DrawerLayout? = null protected var tableDrawerRightContainer: NavigationView? = null - var tabSwitcherRoot: View? = null - - var activityMainRoot: View? = null @JvmField @Inject @@ -262,10 +242,6 @@ abstract class CoreReaderFragment : var painter: DarkModeViewPainter? = null protected var currentWebViewIndex by mutableStateOf(0) private var currentTtsWebViewIndex = 0 - protected var actionBar: ActionBar? = null - protected var mainMenu: MainMenu? = null - - private var tabRecyclerView: RecyclerView? = null private var isFirstTimeMainPageLoaded = true private var isFromManageExternalLaunch = false private val savingTabsMutex = Mutex() @@ -295,7 +271,6 @@ abstract class CoreReaderFragment : private var documentParser: DocumentParser? = null private var tts: KiwixTextToSpeech? = null private var compatCallback: CompatFindActionModeCallback? = null - private var tabsAdapter: TabsAdapter? = null private var zimReaderSource: ZimReaderSource? = null private var actionMode: ActionMode? = null private var tempWebViewForUndo: KiwixWebView? = null @@ -304,7 +279,6 @@ abstract class CoreReaderFragment : private var isFirstRun = false private var tableDrawerAdapter: TableDrawerAdapter? = null private var tableDrawerRight: RecyclerView? = null - private var tabCallback: ItemTouchHelper.Callback? = null private var donationLayout: FrameLayout? = null private var bookmarkingJob: Job? = null private var isBookmarked = false @@ -517,53 +491,13 @@ abstract class CoreReaderFragment : activity?.let { WebView(it).destroy() // Workaround for buggy webViews see #710 } - prepareViews() handleLocaleCheck() - activity?.setSupportActionBar(toolbar) - actionBar = activity?.supportActionBar initHideBackToTopTimer() - initTabCallback() - toolbar?.setOnTouchListener( - object : OnSwipeTouchListener(requireActivity()) { - @SuppressLint("SyntheticAccessor") - override fun onSwipeBottom() { - showTabSwitcher() - } - - override fun onSwipeLeft() { - if (currentWebViewIndex < webViewList.size - 1) { - val current: View? = getCurrentWebView() - startAnimation(current, R.anim.transition_left) - selectTab(currentWebViewIndex + 1) - } - } - - override fun onSwipeRight() { - if (currentWebViewIndex > 0) { - val current: View? = getCurrentWebView() - startAnimation(current, R.anim.transition_right) - selectTab(currentWebViewIndex - 1) - } - } - - override fun onTap(e: MotionEvent?) { - e?.let { - val titleTextView = toolbar?.findFirstTextView() ?: return@onTap - titleTextView.let { - // only initiate search if it is on the reader screen - mainMenu?.tryExpandSearch(zimReaderContainer?.zimFileReader) - } - } - } - } - ) loadDrawerViews() tableDrawerRight = tableDrawerRightContainer?.getHeaderView(0)?.findViewById(R.id.right_drawer_list) addFileReader() - setupTabsAdapter() setTableDrawerInfo() - setTabListener() activity?.let { compatCallback = CompatFindActionModeCallback(it) } @@ -572,13 +506,6 @@ abstract class CoreReaderFragment : loadPrefs() updateTitle() handleIntentExtras(requireActivity().intent) - tabRecyclerView?.let { - it.adapter = tabsAdapter - tabCallback?.let { callBack -> - ItemTouchHelper(callBack).attachToRecyclerView(it) - } - } - // Only check intent on first start of activity. Otherwise the intents will enter infinite loops // when "Don't keep activities" is on. if (savedInstanceState == null) { @@ -700,50 +627,6 @@ abstract class CoreReaderFragment : unsupportedMimeTypeHandler?.setAlertDialogShower(alertDialogShower as AlertDialogShower) } - private fun prepareViews() { - fragmentReaderBinding?.let { readerBinding -> - with(readerBinding.root) { - activityMainRoot = findViewById(R.id.activity_main_root) - toolbar = findViewById(R.id.toolbar) - tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher) - tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view) - donationLayout = findViewById(R.id.donation_layout) - } - } - } - - private fun initTabCallback() { - tabCallback = object : ItemTouchHelper.Callback() { - override fun getMovementFlags( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder - ): Int = makeMovementFlags(0, ItemTouchHelper.UP or ItemTouchHelper.DOWN) - - override fun onChildDraw( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean - ) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) - viewHolder.itemView.alpha = 1 - abs(dY) / viewHolder.itemView.measuredHeight - } - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean = false - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - closeTab(viewHolder.adapterPosition) - } - } - } - @Suppress("MagicNumber") private fun initHideBackToTopTimer() { hideBackToTopTimer = object : CountDownTimer(1200, 1200) { @@ -811,22 +694,6 @@ abstract class CoreReaderFragment : }) } - private fun setTabListener() { - tabsAdapter?.setTabClickListener(object : TabsAdapter.TabClickListener { - override fun onSelectTab(view: View, position: Int) { - hideTabSwitcher() - selectTab(position) - - // Bug Fix #592 - updateBottomToolbarArrowsAlpha() - } - - override fun onCloseTab(view: View, position: Int) { - closeTab(position) - } - }) - } - private fun setTableDrawerInfo() { tableDrawerRight?.apply { layoutManager = LinearLayoutManager(requireActivity()) @@ -836,22 +703,6 @@ abstract class CoreReaderFragment : } } - private fun setupTabsAdapter() { - tabsAdapter = painter?.let { - TabsAdapter( - requireActivity() as AppCompatActivity, - webViewList, - it - ).apply { - registerAdapterDataObserver(object : AdapterDataObserver() { - override fun onChanged() { - readerMenuState?.updateTabIcon(itemCount) - } - }) - } - } - } - private fun addFileReader() { documentParserJs = requireActivity().readFile("js/documentParser.js") documentSections?.clear() @@ -901,7 +752,6 @@ abstract class CoreReaderFragment : ) } showSearchPlaceHolderInToolbar(true) - startAnimation(tabSwitcherRoot, R.anim.slide_down) readerMenuState?.showTabSwitcherOptions() } @@ -926,13 +776,6 @@ abstract class CoreReaderFragment : } } - protected fun startAnimation( - view: View?, - @AnimRes anim: Int - ) { - view?.startAnimation(AnimationUtils.loadAnimation(view.context, anim)) - } - /** * @param shouldCloseZimBook A flag to indicate whether the ZIM book should be closed. * - Default is `true`, which ensures normal behavior for most scenarios. @@ -1074,7 +917,7 @@ abstract class CoreReaderFragment : @Suppress("ReturnCount", "NestedBlockDepth") override fun onBackPressed(activity: AppCompatActivity): FragmentActivityExtensions.Super { when { - tabSwitcherRoot?.visibility == VISIBLE -> { + readerScreenState.value.showTabSwitcher -> { selectTab( if (currentWebViewIndex < webViewList.size) { currentWebViewIndex @@ -1300,16 +1143,11 @@ abstract class CoreReaderFragment : } safelyCancelBookmarkJob() unBindViewsAndBinding() - tabCallback = null hideBackToTopTimer?.cancel() hideBackToTopTimer = null stopOngoingLoadingAndClearWebViewList() - actionBar = null - mainMenu = null - tabRecyclerView?.adapter = null tableDrawerRight?.adapter = null tableDrawerAdapter = null - tabsAdapter = null tempWebViewListForUndo.clear() // create a base Activity class that class this. deleteCachedFiles(requireActivity()) @@ -1336,17 +1174,10 @@ abstract class CoreReaderFragment : @SuppressLint("ClickableViewAccessibility") private fun unBindViewsAndBinding() { - activityMainRoot = null - tabRecyclerView = null - tabSwitcherRoot = null compatCallback?.finish() compatCallback = null - toolbar?.setOnTouchListener(null) - toolbar = null drawerLayout = null tableDrawerRightContainer = null - fragmentReaderBinding?.root?.removeAllViews() - fragmentReaderBinding = null donationLayout?.removeAllViews() donationLayout = null } @@ -1522,18 +1353,7 @@ abstract class CoreReaderFragment : reopenBook() } tempWebViewForUndo?.let { - if (tabSwitcherRoot?.visibility == GONE) { - // Remove the top margin from the webView when the tabSwitcher is not visible. - // We have added this margin in `TabsAdapter` to not show the top margin in tabs. - // `tempWebViewForUndo` saved with that margin so before showing it to the `contentFrame` - // We need to set full width and height for properly showing the content of webView. - it.layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT - ) - } webViewList.add(index, it) - tabsAdapter?.notifyDataSetChanged() readerScreenState.value.snackBarHostState.snack( context?.getString(R.string.tab_restored).orEmpty(), lifecycleScope = lifecycleScope @@ -1555,7 +1375,6 @@ abstract class CoreReaderFragment : currentWebViewIndex = position val webView = safelyGetWebView(position) ?: return safelyAddWebView(webView) - tabsAdapter?.selected = currentWebViewIndex updateBottomToolbarVisibility() loadPrefs() updateUrlFlow() @@ -1999,7 +1818,6 @@ abstract class CoreReaderFragment : private fun restoreDeletedTabs() { if (tempWebViewListForUndo.isNotEmpty()) { webViewList.addAll(tempWebViewListForUndo) - tabsAdapter?.notifyDataSetChanged() readerScreenState.value.snackBarHostState.snack( context?.getString(R.string.tabs_restored).orEmpty(), lifecycleScope = lifecycleScope @@ -2397,10 +2215,8 @@ abstract class CoreReaderFragment : override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - // Forcing redraw of RecyclerView children so that the tabs are properly oriented on rotation - tabRecyclerView?.adapter = tabsAdapter // force redraw of donation layout if it is showing. - if (donationLayout?.isVisible == true) { + if (readerScreenState.value.shouldShowDonationPopup) { showDonationLayout() } } @@ -2647,7 +2463,6 @@ abstract class CoreReaderFragment : return } updateTableOfContents() - tabsAdapter?.notifyDataSetChanged() updateBottomToolbarArrowsAlpha() val zimFileReader = zimReaderContainer?.zimFileReader if (hasValidFileAndUrl(getCurrentWebView()?.url, zimFileReader)) { @@ -2681,13 +2496,19 @@ abstract class CoreReaderFragment : private fun hasValidFileAndUrl(url: String?, zimFileReader: ZimFileReader?): Boolean = url != null && zimFileReader != null - override fun webViewFailedLoading(url: String) { + override fun webViewFailedLoading(failingUrl: String) { if (isAdded) { // If a URL fails to load, update the bookmark toggle. // This fixes the scenario where the previous page is bookmarked and the next // page fails to load, ensuring the bookmark toggle is unset correctly. updateUrlFlow() - Log.d(TAG_KIWIX, String.format(getString(R.string.error_article_url_not_found), url)) + Log.d( + TAG_KIWIX, + String.format( + getString(R.string.error_article_url_not_found), + failingUrl + ) + ) } } @@ -2707,7 +2528,6 @@ abstract class CoreReaderFragment : override fun webViewTitleUpdated(title: String) { updateTabIcon(webViewList.size) - tabsAdapter?.notifyDataSetChanged() } @Suppress("MagicNumber") diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt index 9a9f01f8b..c8cb7cd7c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt @@ -89,7 +89,6 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalWindowInfo @@ -97,6 +96,8 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -146,6 +147,10 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTestingTag" +const val TAB_SWITCHER_VIEW_TESTING_TAG = "tabSwitcherViewTestingTag" +const val READER_SCREEN_TESTING_TAG = "readerScreenTestingTag" +const val CLOSE_ALL_TABS_BUTTON_TESTING_TAG = "closeAllTabsButtonTestingTag" +const val TAB_TITLE_TESTING_TAG = "tabTitleTestingTag" @OptIn(ExperimentalMaterial3Api::class) @Suppress("ComposableLambdaParameterNaming") @@ -157,10 +162,10 @@ fun ReaderScreen( navigationIcon: @Composable () -> Unit ) { val bottomNavHeightInDp = with(LocalDensity.current) { state.bottomNavigationHeight.toDp() } - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior() LaunchedEffect(bottomAppBarScrollBehavior.state.heightOffset) { - onBottomScrollOffsetChanged(scrollBehavior.state.heightOffset) + onBottomScrollOffsetChanged(bottomAppBarScrollBehavior.state.heightOffset) } KiwixDialogTheme { Scaffold( @@ -169,21 +174,21 @@ fun ReaderScreen( ReaderTopBar( state, actionMenuItems, - scrollBehavior, + topAppBarScrollBehavior, navigationIcon ) }, floatingActionButton = { BackToTopFab(state) }, modifier = Modifier .systemBarsPadding() - .nestedScroll(scrollBehavior.nestedScrollConnection) - .nestedScroll(bottomAppBarScrollBehavior.nestedScrollConnection) .padding(bottom = bottomNavHeightInDp) + .semantics { testTag = READER_SCREEN_TESTING_TAG } ) { paddingValues -> ReaderContentLayout( state, Modifier.padding(paddingValues), - bottomAppBarScrollBehavior + bottomAppBarScrollBehavior, + topAppBarScrollBehavior ) } } @@ -195,7 +200,7 @@ fun ReaderScreen( private fun ReaderTopBar( state: ReaderScreenState, actionMenuItems: List, - scrollBehavior: TopAppBarScrollBehavior, + topAppBarScrollBehavior: TopAppBarScrollBehavior, navigationIcon: @Composable () -> Unit, ) { if (!state.shouldShowFullScreenMode && !state.fullScreenItem.first) { @@ -203,7 +208,7 @@ private fun ReaderTopBar( title = if (state.showTabSwitcher) "" else state.readerScreenTitle, navigationIcon = navigationIcon, actionMenuItems = actionMenuItems, - topAppBarScrollBehavior = scrollBehavior, + topAppBarScrollBehavior = topAppBarScrollBehavior, searchBar = searchPlaceHolderIfActive(state.searchPlaceHolderItemForCustomApps) ) @@ -215,7 +220,8 @@ private fun ReaderTopBar( private fun ReaderContentLayout( state: ReaderScreenState, modifier: Modifier = Modifier, - bottomAppBarScrollBehavior: BottomAppBarScrollBehavior + bottomAppBarScrollBehavior: BottomAppBarScrollBehavior, + topAppBarScrollBehavior: TopAppBarScrollBehavior ) { Box(modifier = modifier.fillMaxSize()) { TabSwitcherAnimated(state) @@ -225,7 +231,7 @@ private fun ReaderContentLayout( state.fullScreenItem.first -> ShowFullScreenView(state) else -> { - ShowZIMFileContent(state) + ShowZIMFileContent(state, bottomAppBarScrollBehavior, topAppBarScrollBehavior) ShowProgressBarIfZIMFilePageIsLoading(state) Column(Modifier.align(Alignment.BottomCenter)) { TtsControls(state) @@ -255,11 +261,11 @@ private fun TabSwitcherAnimated(state: ReaderScreenState) { val transitionSpec = remember { slideInVertically( initialOffsetY = { -it }, - animationSpec = tween(durationMillis = 300) + animationSpec = tween(durationMillis = HIDE_TAB_SWITCHER_DELAY.toInt()) ) + fadeIn() togetherWith slideOutVertically( targetOffsetY = { -it }, - animationSpec = tween(durationMillis = 300) + animationSpec = tween(durationMillis = HIDE_TAB_SWITCHER_DELAY.toInt()) ) + fadeOut() } @@ -267,7 +273,9 @@ private fun TabSwitcherAnimated(state: ReaderScreenState) { visible = state.showTabSwitcher, enter = transitionSpec.targetContentEnter, exit = transitionSpec.initialContentExit, - modifier = Modifier.zIndex(1f), + modifier = Modifier + .zIndex(1f) + .semantics { testTag = TAB_SWITCHER_VIEW_TESTING_TAG }, ) { TabSwitcherView( state.kiwixWebViewList, @@ -349,8 +357,13 @@ private fun BoxScope.CloseFullScreenImageButton( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun ShowZIMFileContent(state: ReaderScreenState) { +private fun ShowZIMFileContent( + state: ReaderScreenState, + bottomAppBarScrollBehavior: BottomAppBarScrollBehavior, + topAppBarScrollBehavior: TopAppBarScrollBehavior +) { state.selectedWebView?.let { selectedWebView -> key(selectedWebView) { AndroidView( @@ -359,14 +372,23 @@ private fun ShowZIMFileContent(state: ReaderScreenState) { FrameLayout(context).apply { // Ensure the WebView has no parent before adding (selectedWebView.parent as? ViewGroup)?.removeView(selectedWebView) + selectedWebView.setOnScrollChangeListener(null) + selectedWebView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + val deltaY = (scrollY - oldScrollY).toFloat() + if (deltaY == 0f) return@setOnScrollChangeListener + // SAFELY drive top and bottom app bars + topAppBarScrollBehavior.state.heightOffset -= deltaY + bottomAppBarScrollBehavior.state.heightOffset -= deltaY + } + selectedWebView.layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) addView(selectedWebView) } }, - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) + modifier = Modifier.fillMaxSize() ) - // TODO handle the unnecessary vertical scroll when webView page chnaged. } } } @@ -657,6 +679,7 @@ private fun BoxScope.CloseAllTabButton(onCloseAllTabs: () -> Unit) { .graphicsLayer { rotationZ = rotation } + .semantics { testTag = CLOSE_ALL_TABS_BUTTON_TESTING_TAG } .clickable( enabled = !isAnimating, onClick = { @@ -732,7 +755,8 @@ private fun TabItemHeader( overflow = TextOverflow.Ellipsis, modifier = Modifier .padding(end = FOUR_DP) - .weight(1f), + .weight(1f) + .semantics { testTag = TAB_TITLE_TESTING_TAG }, style = MaterialTheme.typography.labelSmall ) IconButton( diff --git a/core/src/main/res/layout/fragment_reader.xml b/core/src/main/res/layout/fragment_reader.xml deleted file mode 100644 index e339ee94c..000000000 --- a/core/src/main/res/layout/fragment_reader.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - -