Fixed: An extra space appeared at the bottom when navigating to a ZIM page with greater height and then returning to the previous page.

* Removed unnecessary code and files from the project.
* Refactored several UI test cases to align with the Compose UI.
* Fixed: Some lint issues.
This commit is contained in:
MohitMaliFtechiz 2025-06-26 00:52:06 +05:30
parent db22e245b7
commit a51615e249
27 changed files with 166 additions and 1154 deletions

View File

@ -109,7 +109,7 @@ class TopLevelDestinationTest : BaseActivityTest() {
fun testTopLevelDestination() {
topLevel {
clickReaderOnBottomNav {
assertReaderScreenDisplayed()
assertReaderScreenDisplayed(composeTestRule)
}
clickDownloadOnBottomNav {
onlineLibrary {

View File

@ -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()
})

View File

@ -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()
}
}
}

View File

@ -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) {

View File

@ -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)

View File

@ -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")

View File

@ -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() {

View File

@ -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)

View File

@ -128,7 +128,7 @@ class GetContentShortcutTest {
onlineLibrary { assertOnlineLibraryFragmentDisplayed(composeTestRule) }
topLevel {
clickReaderOnBottomNav {
assertReaderScreenDisplayed()
assertReaderScreenDisplayed(composeTestRule)
}
clickDownloadOnBottomNav {
onlineLibrary { assertOnlineLibraryFragmentDisplayed(composeTestRule) }

View File

@ -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

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_language_search"
android:icon="@drawable/action_search"
android:title="@string/search_label"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:iconifiedByDefault="true"
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/menu_language_save"
android:icon="@drawable/ic_check_white_24dp"
android:title="@string/save_languages"
app:showAsAction="ifRoom" />
</menu>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/action_search"
android:title="@string/search_label"
android:visible="true"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:iconifiedByDefault="true"
app:showAsAction="always|collapseActionView" />
<item
android:id="@+id/select_language"
android:icon="@drawable/ic_language_white_24dp"
android:title="@string/pref_language_chooser"
android:visible="true"
app:showAsAction="ifRoom" />
<item
android:id="@+id/get_zim_nearby_device"
android:icon="@drawable/ic_baseline_mobile_screen_share_24px"
android:title="@string/get_content_from_nearby_device"
app:showAsAction="always" />
</menu>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferFragment">
<item
android:id="@+id/menu_item_search_devices"
android:title="@string/search_for_peers"
app:showAsAction="always"
android:icon="@drawable/action_search" />
</menu>

View File

@ -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<KiwixWebView>,
urlIsValid: Boolean,
menuClickListener: MenuClickListener,
disableReadAloud: Boolean,
disableTabs: Boolean,
disableSearch: Boolean
): MainMenu =
MainMenu(
activity,
zimReaderContainer.zimFileReader,
menu,
webViews,
urlIsValid,
disableReadAloud,
disableTabs,
disableSearch,
menuClickListener
)
}
}
}

View File

@ -1,194 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<KiwixWebView>,
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<KiwixWebView>,
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
}
}

View File

@ -1,200 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<KiwixWebView>,
private val painter: DarkModeViewPainter
) : RecyclerView.Adapter<TabsAdapter.ViewHolder>() {
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)
}

View File

@ -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<KiwixWebView>()
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")

View File

@ -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<ActionMenuItem>,
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(

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 Kiwix <android.kiwix.org>
~ 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 <http://www.gnu.org/licenses/>.
~
-->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_fragment_main_drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/reader_fragment_content" />
<TextView
android:id="@+id/no_open_book_text"
style="@style/no_content"
android:text="@string/no_open_book"
android:layout_marginTop="@dimen/material_design_appbar_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.45" />
<Button
android:id="@+id/go_to_library_button_no_open_book"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/open_library"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/no_open_book_text"
app:layout_constraintStart_toStartOf="@+id/no_open_book_text"
app:layout_constraintTop_toBottomOf="@id/no_open_book_text"/>
<RelativeLayout
android:id="@+id/fullscreen_video_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="invisible" />
<FrameLayout
android:id="@+id/donation_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/actionBarItemBackground"
android:minHeight="@dimen/material_minimum_height_and_width"
android:minWidth="@dimen/material_minimum_height_and_width"
android:paddingStart="12dp"
android:paddingEnd="12dp"
tools:ignore="Overdraw">
<TextView
android:id="@+id/ic_tab_switcher_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border_tab_switcher"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:minHeight="20dp"
android:minWidth="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="12"
tools:textColor="@android:color/black" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 Kiwix <android.kiwix.org>
~ 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 <http://www.gnu.org/licenses/>.
~
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/KiwixTheme"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
</merge>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<include layout="@layout/layout_toolbar" />
</com.google.android.material.appbar.AppBarLayout>

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:popupTheme="@style/KiwixTheme"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/toolbarWithSearchPlaceholder"
android:layout_width="match_parent"
android:visibility="gone"
android:background="@drawable/search_placeholder_background"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search_label"
android:padding="5dip"
android:layout_marginStart="5dip"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:drawableRightCompat="@drawable/action_search"
android:drawablePadding="10dip" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
</merge>

View File

@ -1,145 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 Kiwix <android.kiwix.org>
~ 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 <http://www.gnu.org/licenses/>.
~
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/activity_main_content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/activity_main_tab_switcher"
layout="@layout/tab_switcher"
android:visibility="gone" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/fragment_main_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/layout_toolbar" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/main_fragment_progress_view"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/progress_view_height"
android:indeterminate="false"
android:max="100"
android:theme="@style/ThemeOverlay.KiwixTheme.ProgressBar"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:progress="70" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_anchor="@id/bottom_toolbar">
<Button
android:id="@+id/activity_main_button_pause_tts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0.6"
android:text="@string/tts_pause"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/activity_main_button_stop_tts"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/activity_main_button_stop_tts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0.6"
android:text="@string/stop"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/activity_main_button_pause_tts"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/activity_main_tts_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="activity_main_button_pause_tts,activity_main_button_stop_tts"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include layout="@layout/bottom_toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/activity_main_back_to_top_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/action_find_previous"
android:visibility="gone"
app:fabSize="mini"
android:contentDescription="@string/pref_back_to_top"
app:layout_anchor="@id/bottom_toolbar"
app:layout_dodgeInsetEdges="bottom"
tools:visibility="visible" />
</org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout>
<ImageButton
android:id="@+id/activity_main_fullscreen_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fullscreen_control_button_margin"
android:background="?colorOnSurface"
android:contentDescription="@string/menu_exit_full_screen"
android:src="@drawable/fullscreen_exit"
app:tint="?colorSurface"
android:visibility="gone"
android:minWidth="@dimen/material_minimum_height_and_width"
android:minHeight="@dimen/material_minimum_height_and_width"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tab_switcher_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/tab_switcher_close_all_tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:contentDescription="@string/close_all_tabs"
android:src="@drawable/ic_close_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,50 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/menu_search"
android:icon="@drawable/action_search"
android:title="@string/search_label"
android:visible="false"
app:showAsAction="always"
tools:visible="true"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/menu_tab_switcher"
android:title="@string/switch_tabs"
app:actionLayout="@layout/ic_tab_switcher"
app:showAsAction="always" />
<item
android:id="@+id/menu_add_note"
android:icon="@drawable/ic_add_note"
android:title="@string/take_notes"
android:visible="false"
app:showAsAction="never" />
<item
android:id="@+id/menu_random_article"
android:icon="@drawable/action_randomarticle"
android:title="@string/menu_random_article"
android:visible="false"
app:showAsAction="never"
tools:visible="true" />
<item
android:id="@+id/menu_fullscreen"
android:title="@string/menu_full_screen"
android:visible="false"
app:showAsAction="never"
tools:visible="true" />
<item
android:id="@+id/menu_read_aloud"
android:title="@string/menu_read_aloud"
android:visible="false"
app:showAsAction="never"
tools:visible="true" />
</menu>

View File

@ -13,14 +13,16 @@
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/find_prev"
android:icon="@drawable/action_find_previous"
android:title="@string/previous"
app:showAsAction="always" />
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/find_next"
android:icon="@drawable/action_find_next"