mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 18:56:44 -04:00
Merge pull request #4284 from kiwix/Fixes#4243
Migrated `BookmarkFragment`, `HistoryFragment`, and `NotesFragment` to Jetpack Compose.
This commit is contained in:
commit
201c16bd8c
@ -38,6 +38,7 @@ import org.kiwix.kiwixmobile.Findable.StringId.TextId
|
||||
import org.kiwix.kiwixmobile.Findable.ViewId
|
||||
import org.kiwix.kiwixmobile.R
|
||||
import org.kiwix.kiwixmobile.core.R.string
|
||||
import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.language.composables.LANGUAGE_ITEM_CHECKBOX_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils
|
||||
|
||||
|
@ -119,13 +119,13 @@ class TopLevelDestinationTest : BaseActivityTest() {
|
||||
}
|
||||
}
|
||||
clickBookmarksOnNavDrawer {
|
||||
assertBookMarksDisplayed()
|
||||
clickOnTrashIcon()
|
||||
assertBookMarksDisplayed(composeTestRule)
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteBookmarksDialogDisplayed()
|
||||
}
|
||||
clickHistoryOnSideNav {
|
||||
assertHistoryDisplayed()
|
||||
clickOnTrashIcon()
|
||||
assertHistoryDisplayed(composeTestRule)
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteHistoryDialogDisplayed()
|
||||
}
|
||||
clickHostBooksOnSideNav(ZimHostRobot::assertMenuWifiHotspotDiplayed)
|
||||
|
@ -121,9 +121,8 @@ class NoteFragmentTest : BaseActivityTest() {
|
||||
it.navigate(R.id.notesFragment)
|
||||
}
|
||||
note {
|
||||
assertToolbarExist()
|
||||
assertNoteRecyclerViewExist()
|
||||
assertSwitchWidgetExist()
|
||||
assertToolbarExist(composeTestRule)
|
||||
assertSwitchWidgetExist(composeTestRule)
|
||||
}
|
||||
LeakAssertions.assertNoLeaks()
|
||||
}
|
||||
@ -140,9 +139,8 @@ class NoteFragmentTest : BaseActivityTest() {
|
||||
saveNote(composeTestRule)
|
||||
pressBack()
|
||||
openNoteFragment()
|
||||
assertToolbarExist()
|
||||
assertNoteRecyclerViewExist()
|
||||
clickOnSavedNote()
|
||||
assertToolbarExist(composeTestRule)
|
||||
clickOnSavedNote(composeTestRule)
|
||||
clickOnOpenNote()
|
||||
assertNoteSaved(composeTestRule)
|
||||
// to close the note dialog.
|
||||
@ -164,9 +162,8 @@ class NoteFragmentTest : BaseActivityTest() {
|
||||
|
||||
note {
|
||||
openNoteFragment()
|
||||
assertToolbarExist()
|
||||
assertNoteRecyclerViewExist()
|
||||
clickOnSavedNote()
|
||||
assertToolbarExist(composeTestRule)
|
||||
clickOnSavedNote(composeTestRule)
|
||||
clickOnOpenNote()
|
||||
assertNoteSaved(composeTestRule)
|
||||
pressBack()
|
||||
@ -189,9 +186,8 @@ class NoteFragmentTest : BaseActivityTest() {
|
||||
saveNote(composeTestRule)
|
||||
pressBack()
|
||||
openNoteFragment()
|
||||
assertToolbarExist()
|
||||
assertNoteRecyclerViewExist()
|
||||
clickOnSavedNote()
|
||||
assertToolbarExist(composeTestRule)
|
||||
clickOnSavedNote(composeTestRule)
|
||||
clickOnOpenNote()
|
||||
assertNoteSaved(composeTestRule)
|
||||
// to close the note dialog.
|
||||
@ -213,14 +209,13 @@ class NoteFragmentTest : BaseActivityTest() {
|
||||
saveNote(composeTestRule)
|
||||
pressBack()
|
||||
openNoteFragment()
|
||||
assertToolbarExist()
|
||||
assertNoteRecyclerViewExist()
|
||||
clickOnSavedNote()
|
||||
assertToolbarExist(composeTestRule)
|
||||
clickOnSavedNote(composeTestRule)
|
||||
clickOnOpenNote()
|
||||
assertNoteSaved(composeTestRule)
|
||||
clickOnDeleteIcon(composeTestRule)
|
||||
pressBack()
|
||||
assertNoNotesTextDisplayed()
|
||||
assertNoNotesTextDisplayed(composeTestRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,12 +247,11 @@ class NoteFragmentTest : BaseActivityTest() {
|
||||
// delete the notes if any saved to properly run the test scenario
|
||||
note {
|
||||
openNoteFragment()
|
||||
assertToolbarExist()
|
||||
assertNoteRecyclerViewExist()
|
||||
clickOnTrashIcon()
|
||||
assertToolbarExist(composeTestRule)
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteNoteDialogDisplayed()
|
||||
clickOnDeleteButton()
|
||||
assertNoNotesTextDisplayed()
|
||||
assertNoNotesTextDisplayed(composeTestRule)
|
||||
pressBack()
|
||||
}
|
||||
}
|
||||
|
@ -22,18 +22,15 @@ import android.content.Context
|
||||
import androidx.compose.ui.test.assertTextContains
|
||||
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.performClick
|
||||
import androidx.compose.ui.test.performTextReplacement
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.espresso.Espresso.closeSoftKeyboard
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.espresso.web.sugar.Web.onWebView
|
||||
import androidx.test.espresso.web.webdriver.DriverAtoms.findElement
|
||||
@ -42,11 +39,14 @@ import com.adevinta.android.barista.interaction.BaristaSleepInteractions
|
||||
import org.kiwix.kiwixmobile.BaseRobot
|
||||
import org.kiwix.kiwixmobile.Findable.StringId.TextId
|
||||
import org.kiwix.kiwixmobile.Findable.Text
|
||||
import org.kiwix.kiwixmobile.Findable.ViewId
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.main.ADD_NOTE_TEXT_FILED_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.main.DELETE_MENU_BUTTON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.main.SAVE_MENU_BUTTON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.DELETE_MENU_ICON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.NO_ITEMS_TEXT_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.PAGE_ITEM_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.SWITCH_TEXT_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.ui.components.TOOLBAR_TITLE_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
||||
@ -57,16 +57,20 @@ fun note(func: NoteRobot.() -> Unit) = NoteRobot().apply(func)
|
||||
class NoteRobot : BaseRobot() {
|
||||
private val noteText = "Test Note"
|
||||
|
||||
fun assertToolbarExist() {
|
||||
isVisible(ViewId(R.id.toolbar))
|
||||
fun assertToolbarExist(composeTestRule: ComposeContentTestRule) {
|
||||
testFlakyView({
|
||||
composeTestRule.waitForIdle()
|
||||
composeTestRule.onNodeWithTag(TOOLBAR_TITLE_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.pref_notes))
|
||||
})
|
||||
}
|
||||
|
||||
fun assertNoteRecyclerViewExist() {
|
||||
isVisible(ViewId(R.id.recycler_view))
|
||||
}
|
||||
|
||||
fun assertSwitchWidgetExist() {
|
||||
isVisible(ViewId(R.id.page_switch))
|
||||
fun assertSwitchWidgetExist(composeTestRule: ComposeContentTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(SWITCH_TEXT_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.notes_from_all_books))
|
||||
}
|
||||
}
|
||||
|
||||
fun clickOnNoteMenuItem(context: Context) {
|
||||
@ -117,15 +121,11 @@ class NoteRobot : BaseRobot() {
|
||||
testFlakyView({ onView(withText(R.string.pref_notes)).perform(click()) })
|
||||
}
|
||||
|
||||
fun clickOnSavedNote() {
|
||||
testFlakyView({
|
||||
onView(withId(R.id.recycler_view)).perform(
|
||||
actionOnItemAtPosition<RecyclerView.ViewHolder>(
|
||||
0,
|
||||
click()
|
||||
)
|
||||
)
|
||||
})
|
||||
fun clickOnSavedNote(composeTestRule: ComposeContentTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onAllNodesWithTag(PAGE_ITEM_TESTING_TAG)[0].performClick()
|
||||
}
|
||||
}
|
||||
|
||||
fun clickOnOpenNote() {
|
||||
@ -158,8 +158,14 @@ class NoteRobot : BaseRobot() {
|
||||
})
|
||||
}
|
||||
|
||||
fun clickOnTrashIcon() {
|
||||
testFlakyView({ onView(withContentDescription(R.string.pref_clear_notes)).perform(click()) })
|
||||
fun clickOnTrashIcon(composeTestRule: ComposeContentTestRule) {
|
||||
testFlakyView({
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(DELETE_MENU_ICON_TESTING_TAG)
|
||||
.performClick()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun assertDeleteNoteDialogDisplayed() {
|
||||
@ -171,8 +177,12 @@ class NoteRobot : BaseRobot() {
|
||||
testFlakyView({ onView(ViewMatchers.withText("DELETE")).perform(click()) })
|
||||
}
|
||||
|
||||
fun assertNoNotesTextDisplayed() {
|
||||
testFlakyView({ isVisible(TextId(R.string.no_notes)) })
|
||||
fun assertNoNotesTextDisplayed(composeTestRule: ComposeContentTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(NO_ITEMS_TEXT_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.no_notes))
|
||||
}
|
||||
}
|
||||
|
||||
fun assertHomePageIsLoadedOfTestZimFile() {
|
||||
|
@ -18,25 +18,29 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.page.bookmarks
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollToNode
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.longClick
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import applyWithViewHierarchyPrinting
|
||||
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed
|
||||
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
|
||||
import org.kiwix.kiwixmobile.BaseRobot
|
||||
import org.kiwix.kiwixmobile.Findable.StringId.ContentDesc
|
||||
import org.kiwix.kiwixmobile.Findable.StringId.TextId
|
||||
import org.kiwix.kiwixmobile.Findable.Text
|
||||
import org.kiwix.kiwixmobile.Findable.ViewId
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.page.DELETE_MENU_ICON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.NO_ITEMS_TEXT_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.PAGE_LIST_TEST_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.SWITCH_TEXT_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
||||
@ -49,12 +53,20 @@ fun bookmarks(func: BookmarksRobot.() -> Unit) =
|
||||
class BookmarksRobot : BaseRobot() {
|
||||
private var retryCountForBookmarkAddedButton = 5
|
||||
|
||||
fun assertBookMarksDisplayed() {
|
||||
assertDisplayed(R.string.bookmarks_from_current_book)
|
||||
fun assertBookMarksDisplayed(composeTestRule: ComposeContentTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(SWITCH_TEXT_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.bookmarks_from_current_book))
|
||||
}
|
||||
}
|
||||
|
||||
fun clickOnTrashIcon() {
|
||||
clickOn(ContentDesc(R.string.pref_clear_all_bookmarks_title))
|
||||
fun clickOnTrashIcon(composeTestRule: ComposeContentTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(DELETE_MENU_ICON_TESTING_TAG)
|
||||
.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
fun assertDeleteBookmarksDialogDisplayed() {
|
||||
@ -66,8 +78,12 @@ class BookmarksRobot : BaseRobot() {
|
||||
testFlakyView({ onView(withText("DELETE")).perform(click()) })
|
||||
}
|
||||
|
||||
fun assertNoBookMarkTextDisplayed() {
|
||||
testFlakyView({ isVisible(TextId(R.string.no_bookmarks)) })
|
||||
fun assertNoBookMarkTextDisplayed(composeTestRule: ComposeTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(NO_ITEMS_TEXT_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.no_bookmarks))
|
||||
}
|
||||
}
|
||||
|
||||
fun clickOnSaveBookmarkImage() {
|
||||
@ -97,14 +113,20 @@ class BookmarksRobot : BaseRobot() {
|
||||
}
|
||||
}
|
||||
|
||||
fun assertBookmarkSaved() {
|
||||
fun assertBookmarkSaved(composeTestRule: ComposeContentTestRule) {
|
||||
pauseForBetterTestPerformance()
|
||||
isVisible(Text("Test Zim"))
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
composeTestRule.onNodeWithText("Test Zim").assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
fun assertBookmarkRemoved() {
|
||||
fun assertBookmarkRemoved(composeTestRule: ComposeTestRule) {
|
||||
pauseForBetterTestPerformance()
|
||||
onView(withText("Test Zim")).check(ViewAssertions.doesNotExist())
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
composeTestRule.onNodeWithText("Test Zim").assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
private fun pauseForBetterTestPerformance() {
|
||||
@ -118,13 +140,19 @@ class BookmarksRobot : BaseRobot() {
|
||||
})
|
||||
}
|
||||
|
||||
fun testAllBookmarkShowing(bookmarkList: ArrayList<LibkiwixBookmarkItem>) {
|
||||
bookmarkList.forEachIndexed { index, libkiwixBookmarkItem ->
|
||||
testFlakyView({
|
||||
onView(withId(R.id.recycler_view))
|
||||
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(index))
|
||||
.check(matches(hasDescendant(withText(libkiwixBookmarkItem.title))))
|
||||
})
|
||||
fun testAllBookmarkShowing(
|
||||
bookmarkList: ArrayList<LibkiwixBookmarkItem>,
|
||||
composeTestRule: ComposeTestRule
|
||||
) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
bookmarkList.forEachIndexed { index, libkiwixBookmarkItem ->
|
||||
testFlakyView({
|
||||
composeTestRule.onNodeWithTag(PAGE_LIST_TEST_TAG)
|
||||
.performScrollToNode(hasText(libkiwixBookmarkItem.title))
|
||||
composeTestRule.onNodeWithText(libkiwixBookmarkItem.title).assertExists()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.page.bookmarks
|
||||
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@ -42,6 +43,7 @@ import org.kiwix.kiwixmobile.core.main.CoreReaderFragment
|
||||
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
|
||||
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.main.topLevel
|
||||
@ -59,6 +61,9 @@ class LibkiwixBookmarkTest : BaseActivityTest() {
|
||||
@JvmField
|
||||
val retryRule = RetryRule()
|
||||
|
||||
@get:Rule(order = COMPOSE_TEST_RULE_ORDER)
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private lateinit var kiwixMainActivity: KiwixMainActivity
|
||||
|
||||
@Before
|
||||
@ -119,20 +124,20 @@ class LibkiwixBookmarkTest : BaseActivityTest() {
|
||||
bookmarks {
|
||||
// delete any bookmark if already saved to properly perform this test case.
|
||||
longClickOnSaveBookmarkImage()
|
||||
clickOnTrashIcon()
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteBookmarksDialogDisplayed()
|
||||
clickOnDeleteButton()
|
||||
assertNoBookMarkTextDisplayed()
|
||||
assertNoBookMarkTextDisplayed(composeTestRule)
|
||||
pressBack()
|
||||
// Test saving bookmark
|
||||
clickOnSaveBookmarkImage()
|
||||
clickOnOpenSavedBookmarkButton()
|
||||
assertBookmarkSaved()
|
||||
assertBookmarkSaved(composeTestRule)
|
||||
pressBack()
|
||||
// Test removing bookmark
|
||||
clickOnSaveBookmarkImage()
|
||||
longClickOnSaveBookmarkImage()
|
||||
assertBookmarkRemoved()
|
||||
assertBookmarkRemoved(composeTestRule)
|
||||
pressBack()
|
||||
// Save the bookmark to test whether it remains saved after the application restarts or not.
|
||||
clickOnSaveBookmarkImage()
|
||||
@ -142,7 +147,7 @@ class LibkiwixBookmarkTest : BaseActivityTest() {
|
||||
@Test
|
||||
fun testBookmarkRemainsSavedOrNot() {
|
||||
topLevel {
|
||||
clickBookmarksOnNavDrawer(BookmarksRobot::assertBookmarkSaved)
|
||||
clickBookmarksOnNavDrawer { assertBookmarkSaved(composeTestRule) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,10 +165,10 @@ class LibkiwixBookmarkTest : BaseActivityTest() {
|
||||
bookmarks {
|
||||
// delete any bookmark if already saved to properly perform this test case.
|
||||
longClickOnSaveBookmarkImage()
|
||||
clickOnTrashIcon()
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteBookmarksDialogDisplayed()
|
||||
clickOnDeleteButton()
|
||||
assertNoBookMarkTextDisplayed()
|
||||
assertNoBookMarkTextDisplayed(composeTestRule)
|
||||
pressBack()
|
||||
}
|
||||
val navHostFragment: NavHostFragment =
|
||||
@ -198,7 +203,7 @@ class LibkiwixBookmarkTest : BaseActivityTest() {
|
||||
bookmarks {
|
||||
// test all the saved bookmarks are showing on the bookmarks screen
|
||||
openBookmarkScreen()
|
||||
testAllBookmarkShowing(bookmarkList)
|
||||
testAllBookmarkShowing(bookmarkList, composeTestRule)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,23 +18,35 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.page.history
|
||||
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import applyWithViewHierarchyPrinting
|
||||
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed
|
||||
import org.kiwix.kiwixmobile.BaseRobot
|
||||
import org.kiwix.kiwixmobile.Findable.StringId.ContentDesc
|
||||
import org.kiwix.kiwixmobile.Findable.StringId.TextId
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.page.DELETE_MENU_ICON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.page.SWITCH_TEXT_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
||||
|
||||
fun history(func: HistoryRobot.() -> Unit) = HistoryRobot().applyWithViewHierarchyPrinting(func)
|
||||
|
||||
class HistoryRobot : BaseRobot() {
|
||||
fun assertHistoryDisplayed() {
|
||||
assertDisplayed(R.string.history_from_current_book)
|
||||
fun assertHistoryDisplayed(composeTestRule: ComposeTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(SWITCH_TEXT_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.history_from_current_book))
|
||||
}
|
||||
}
|
||||
|
||||
fun clickOnTrashIcon() {
|
||||
clickOn(ContentDesc(R.string.pref_clear_all_history_title))
|
||||
fun clickOnTrashIcon(composeTestRule: ComposeTestRule) {
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(DELETE_MENU_ICON_TESTING_TAG)
|
||||
.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
fun assertDeleteHistoryDialogDisplayed() {
|
||||
|
@ -123,13 +123,13 @@ class GetContentShortcutTest {
|
||||
}
|
||||
}
|
||||
clickBookmarksOnNavDrawer {
|
||||
assertBookMarksDisplayed()
|
||||
clickOnTrashIcon()
|
||||
assertBookMarksDisplayed(composeTestRule)
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteBookmarksDialogDisplayed()
|
||||
}
|
||||
clickHistoryOnSideNav {
|
||||
assertHistoryDisplayed()
|
||||
clickOnTrashIcon()
|
||||
assertHistoryDisplayed(composeTestRule)
|
||||
clickOnTrashIcon(composeTestRule)
|
||||
assertDeleteHistoryDialogDisplayed()
|
||||
}
|
||||
clickHostBooksOnSideNav(ZimHostRobot::assertMenuWifiHotspotDiplayed)
|
||||
|
@ -38,6 +38,7 @@ import org.kiwix.kiwixmobile.core.base.BaseActivity
|
||||
import org.kiwix.kiwixmobile.core.base.BaseFragment
|
||||
import org.kiwix.kiwixmobile.core.extensions.viewModel
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
|
||||
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||
@ -46,7 +47,6 @@ import org.kiwix.kiwixmobile.language.viewmodel.Action
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
const val SEARCH_ICON_TESTING_TAG = "search"
|
||||
const val SAVE_ICON_TESTING_TAG = "saveLanguages"
|
||||
const val SEARCH_FIELD_TESTING_TAG = "searchField"
|
||||
|
||||
|
@ -133,7 +133,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
||||
private val disposable = CompositeDisposable()
|
||||
private var permissionDeniedLayoutShowing = false
|
||||
private var zimFileUri: Uri? = null
|
||||
val libraryScreenState = mutableStateOf(
|
||||
private val libraryScreenState = mutableStateOf(
|
||||
LocalLibraryScreenState(
|
||||
fileSelectListState = FileSelectListState(emptyList()),
|
||||
snackBarHostState = SnackbarHostState(),
|
||||
@ -156,13 +156,15 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
||||
val isGranted = permissionResult.values.all { it }
|
||||
val isPermanentlyDenied = readStorageHasBeenPermanentlyDenied(isGranted)
|
||||
permissionDeniedLayoutShowing = isPermanentlyDenied
|
||||
updateLibraryScreenState(
|
||||
noFilesViewItem = Triple(
|
||||
requireActivity().resources.getString(string.grant_read_storage_permission),
|
||||
requireActivity().resources.getString(string.go_to_settings_label),
|
||||
isPermanentlyDenied
|
||||
if (permissionDeniedLayoutShowing) {
|
||||
updateLibraryScreenState(
|
||||
noFilesViewItem = Triple(
|
||||
requireActivity().resources.getString(string.grant_read_storage_permission),
|
||||
requireActivity().resources.getString(string.go_to_settings_label),
|
||||
true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject(baseActivity: BaseActivity) {
|
||||
|
@ -44,7 +44,8 @@ import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.extensions.faviconToPainter
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.BOOK_ICON_SIZE
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
||||
@ -54,8 +55,8 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.ArticleCount
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
|
||||
|
||||
const val BOOK_ITEM_CHECKBOX_TESTING_TAG = "bookItemCheckboxTestingTag"
|
||||
const val BOOK_ITEM_TESTING_TAG = "bookItemTestingTag"
|
||||
@ -115,7 +116,7 @@ private fun BookContent(
|
||||
if (selectionMode == SelectionMode.MULTI) {
|
||||
BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick, index)
|
||||
}
|
||||
BookIcon(bookOnDisk.book.faviconToPainter())
|
||||
BookIcon(Base64String(bookOnDisk.book.favicon).toPainter())
|
||||
BookDetails(Modifier.weight(1f), bookOnDisk)
|
||||
}
|
||||
}
|
||||
|
@ -94,13 +94,11 @@
|
||||
<fragment
|
||||
android:id="@+id/bookmarksFragment"
|
||||
android:name="org.kiwix.kiwixmobile.core.page.bookmark.BookmarksFragment"
|
||||
android:label="BookmarksFragment"
|
||||
tools:layout="@layout/fragment_page" />
|
||||
android:label="BookmarksFragment" />
|
||||
<fragment
|
||||
android:id="@+id/notesFragment"
|
||||
android:name="org.kiwix.kiwixmobile.core.page.notes.NotesFragment"
|
||||
android:label="NotesFragment"
|
||||
tools:layout="@layout/fragment_page" />
|
||||
android:label="NotesFragment" />
|
||||
<fragment
|
||||
android:id="@+id/introFragment"
|
||||
android:name="org.kiwix.kiwixmobile.intro.IntroFragment"
|
||||
@ -115,8 +113,7 @@
|
||||
<fragment
|
||||
android:id="@+id/historyFragment"
|
||||
android:name="org.kiwix.kiwixmobile.core.page.history.HistoryFragment"
|
||||
android:label="HistoryFragment"
|
||||
tools:layout="@layout/fragment_page" />
|
||||
android:label="HistoryFragment" />
|
||||
<fragment
|
||||
android:id="@+id/languageFragment"
|
||||
android:name="org.kiwix.kiwixmobile.language.LanguageFragment"
|
||||
|
@ -21,6 +21,13 @@ package org.kiwix.kiwixmobile.core.downloader.model
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
|
||||
@JvmInline
|
||||
value class Base64String(private val encodedString: String?) {
|
||||
@ -35,3 +42,13 @@ value class Base64String(private val encodedString: String?) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Base64String.toPainter(): Painter {
|
||||
val bitmap = remember(this) { toBitmap() }
|
||||
return if (bitmap != null) {
|
||||
BitmapPainter(bitmap.asImageBitmap())
|
||||
} else {
|
||||
painterResource(id = R.drawable.default_zim_file_icon)
|
||||
}
|
||||
}
|
||||
|
@ -18,15 +18,7 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.core.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import org.kiwix.kiwixmobile.core.CoreApp
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.core.utils.BookUtils
|
||||
import org.kiwix.kiwixmobile.core.utils.NetworkUtils
|
||||
@ -62,14 +54,3 @@ fun Book.buildSearchableText(bookUtils: BookUtils): String =
|
||||
append("|")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
@Composable
|
||||
fun Book.faviconToPainter(): Painter {
|
||||
val base64String = Base64String(favicon)
|
||||
val bitmap = remember(base64String) { base64String.toBitmap() }
|
||||
return if (bitmap != null) {
|
||||
BitmapPainter(bitmap.asImageBitmap())
|
||||
} else {
|
||||
painterResource(id = R.drawable.default_zim_file_icon)
|
||||
}
|
||||
}
|
||||
|
@ -21,44 +21,41 @@ package org.kiwix.kiwixmobile.core.page
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.MenuHost
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.referentialEqualityPolicy
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.base.BaseFragment
|
||||
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
|
||||
import org.kiwix.kiwixmobile.core.databinding.FragmentPageBinding
|
||||
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
|
||||
import org.kiwix.kiwixmobile.core.extensions.closeKeyboard
|
||||
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
|
||||
import org.kiwix.kiwixmobile.core.extensions.setUpSearchView
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.OnItemClickListener
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.Page
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.PageAdapter
|
||||
import org.kiwix.kiwixmobile.core.page.notes.viewmodel.NotesState
|
||||
import org.kiwix.kiwixmobile.core.page.viewmodel.Action
|
||||
import org.kiwix.kiwixmobile.core.page.viewmodel.PageState
|
||||
import org.kiwix.kiwixmobile.core.page.viewmodel.PageViewModel
|
||||
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
|
||||
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||
import org.kiwix.kiwixmobile.core.utils.SimpleRecyclerViewScrollListener
|
||||
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
|
||||
import javax.inject.Inject
|
||||
|
||||
const val SEARCH_ICON_TESTING_TAG = "search"
|
||||
const val DELETE_MENU_ICON_TESTING_TAG = "deleteMenuIconTestingTag"
|
||||
|
||||
abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActivityExtensions {
|
||||
abstract val pageViewModel: PageViewModel<*, *>
|
||||
|
||||
@ -67,18 +64,44 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
|
||||
private var actionMode: ActionMode? = null
|
||||
val compositeDisposable = CompositeDisposable()
|
||||
abstract val screenTitle: String
|
||||
abstract val screenTitle: Int
|
||||
abstract val noItemsString: String
|
||||
abstract val switchString: String
|
||||
abstract val searchQueryHint: String
|
||||
abstract val pageAdapter: PageAdapter
|
||||
abstract val switchIsChecked: Boolean
|
||||
abstract val deleteIconTitle: String
|
||||
private var fragmentPageBinding: FragmentPageBinding? = null
|
||||
override val fragmentToolbar: Toolbar? by lazy {
|
||||
fragmentPageBinding?.root?.findViewById(R.id.toolbar)
|
||||
}
|
||||
override val fragmentTitle: String? by lazy { screenTitle }
|
||||
abstract val deleteIconTitle: Int
|
||||
private val pageState: MutableState<PageState<*>> =
|
||||
mutableStateOf(
|
||||
NotesState(
|
||||
emptyList(),
|
||||
true,
|
||||
""
|
||||
),
|
||||
policy = referentialEqualityPolicy()
|
||||
)
|
||||
|
||||
private val pageScreenState = mutableStateOf(
|
||||
// Initial values are empty because this is an abstract class.
|
||||
// Before the view is created, the abstract variables have no values.
|
||||
// We update this state in `onViewCreated`, once the view is created and the
|
||||
// abstract variables are initialized.
|
||||
PageFragmentScreenState(
|
||||
pageState = pageState.value,
|
||||
isSearchActive = false,
|
||||
searchQueryHint = "",
|
||||
searchText = "",
|
||||
searchValueChangedListener = {},
|
||||
screenTitle = ZERO,
|
||||
noItemsString = "",
|
||||
switchString = "",
|
||||
switchIsChecked = true,
|
||||
switchIsEnabled = true,
|
||||
onSwitchCheckedChanged = {},
|
||||
deleteIconTitle = ZERO,
|
||||
clearSearchButtonClickListener = {}
|
||||
)
|
||||
)
|
||||
|
||||
private val actionModeCallback: ActionMode.Callback =
|
||||
object : ActionMode.Callback {
|
||||
@ -104,77 +127,23 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMenu() {
|
||||
(requireActivity() as MenuHost).addMenuProvider(
|
||||
object : MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_page, menu)
|
||||
val search = menu.findItem(R.id.menu_page_search).actionView as SearchView
|
||||
search.apply {
|
||||
setUpSearchView(requireActivity())
|
||||
queryHint = searchQueryHint
|
||||
setOnQueryTextListener(
|
||||
SimpleTextListener { query, _ ->
|
||||
pageViewModel.actions.offer(Action.Filter(query))
|
||||
}
|
||||
)
|
||||
}
|
||||
menu.findItem(R.id.menu_pages_clear).title = deleteIconTitle // Bug fix #3825
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
android.R.id.home -> {
|
||||
pageViewModel.actions.offer(Action.Exit)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_pages_clear -> {
|
||||
pageViewModel.actions.offer(Action.UserClickedDeleteButton)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
viewLifecycleOwner,
|
||||
Lifecycle.State.RESUMED
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupMenu()
|
||||
val activity = requireActivity() as CoreMainActivity
|
||||
fragmentPageBinding?.recyclerView?.apply {
|
||||
layoutManager =
|
||||
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
|
||||
adapter = pageAdapter
|
||||
fragmentTitle?.let(::setToolTipWithContentDescription)
|
||||
}
|
||||
fragmentPageBinding?.noPage?.text = noItemsString
|
||||
|
||||
fragmentPageBinding?.pageSwitch?.apply {
|
||||
text = switchString
|
||||
isChecked = switchIsChecked
|
||||
// hide switches for custom apps, see more info here https://github.com/kiwix/kiwix-android/issues/3523
|
||||
visibility = if (requireActivity().isCustomApp()) GONE else VISIBLE
|
||||
}
|
||||
compositeDisposable.add(pageViewModel.effects.subscribe { it.invokeWith(activity) })
|
||||
fragmentPageBinding?.pageSwitch?.setOnCheckedChangeListener { _, isChecked ->
|
||||
pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked))
|
||||
}
|
||||
pageViewModel.state.observe(viewLifecycleOwner, Observer(::render))
|
||||
|
||||
// hides keyboard when scrolled
|
||||
fragmentPageBinding?.recyclerView?.addOnScrollListener(
|
||||
SimpleRecyclerViewScrollListener { _, newState ->
|
||||
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
fragmentPageBinding?.recyclerView?.closeKeyboard()
|
||||
}
|
||||
}
|
||||
pageScreenState.value = pageScreenState.value.copy(
|
||||
searchQueryHint = searchQueryHint,
|
||||
searchText = "",
|
||||
searchValueChangedListener = { onTextChanged(it) },
|
||||
clearSearchButtonClickListener = { onTextChanged("") },
|
||||
screenTitle = screenTitle,
|
||||
noItemsString = noItemsString,
|
||||
switchString = switchString,
|
||||
switchIsChecked = switchIsChecked,
|
||||
onSwitchCheckedChanged = { onSwitchCheckedChanged(it).invoke() },
|
||||
deleteIconTitle = deleteIconTitle
|
||||
)
|
||||
val activity = requireActivity() as CoreMainActivity
|
||||
compositeDisposable.add(pageViewModel.effects.subscribe { it.invokeWith(activity) })
|
||||
pageViewModel.state.observe(viewLifecycleOwner, Observer(::render))
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
@ -182,24 +151,121 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
fragmentPageBinding = FragmentPageBinding.inflate(inflater, container, false)
|
||||
return fragmentPageBinding?.root
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
PageScreen(
|
||||
state = pageScreenState.value,
|
||||
itemClickListener = this@PageFragment,
|
||||
navigationIcon = {
|
||||
NavigationIcon(
|
||||
onClick = navigationIconClick()
|
||||
)
|
||||
},
|
||||
actionMenuItems = actionMenuList(
|
||||
isSearchActive = pageScreenState.value.isSearchActive,
|
||||
onSearchClick = {
|
||||
// Set the `isSearchActive` when the search button is clicked.
|
||||
pageScreenState.value = pageScreenState.value.copy(isSearchActive = true)
|
||||
},
|
||||
onDeleteClick = { pageViewModel.actions.offer(Action.UserClickedDeleteButton) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes to the search text input.
|
||||
* - Updates the UI state with the latest search query.
|
||||
* - Sends a filter action to the ViewModel to perform search/filtering logic.
|
||||
*
|
||||
* @param searchText The current text entered in the search bar.
|
||||
*/
|
||||
private fun onTextChanged(searchText: String) {
|
||||
pageScreenState.value = pageScreenState.value.copy(searchText = searchText)
|
||||
pageViewModel.actions.offer(Action.Filter(searchText))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lambda to handle switch toggle changes.
|
||||
* - Updates the UI state to reflect the new checked status.
|
||||
* - Sends an action to the ViewModel to handle the toggle event (e.g., show all items or filter).
|
||||
*
|
||||
* @param isChecked The new checked state of the switch.
|
||||
*/
|
||||
private fun onSwitchCheckedChanged(isChecked: Boolean): () -> Unit = {
|
||||
pageScreenState.value = pageScreenState.value.copy(switchIsChecked = isChecked)
|
||||
pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked))
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the click event for the navigation icon.
|
||||
* - If search is active, it deactivates the search mode and clears the search text.
|
||||
* - Otherwise, it triggers the default back navigation.
|
||||
*/
|
||||
private fun navigationIconClick(): () -> Unit = {
|
||||
if (pageScreenState.value.isSearchActive) {
|
||||
pageScreenState.value = pageScreenState.value.copy(isSearchActive = false)
|
||||
onTextChanged("")
|
||||
} else {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the list of action menu items for the app bar.
|
||||
*
|
||||
* @param isSearchActive Whether the search mode is currently active.
|
||||
* @param onSearchClick Callback to invoke when the search icon is clicked.
|
||||
* @param onDeleteClick Callback to invoke when the delete icon is clicked.
|
||||
* @return A list of [ActionMenuItem]s to be displayed in the app bar.
|
||||
*
|
||||
* - Shows the search icon only when search is not active.
|
||||
* - Always includes the delete icon, with a content description for accessibility (#3825).
|
||||
*/
|
||||
private fun actionMenuList(
|
||||
isSearchActive: Boolean,
|
||||
onSearchClick: () -> Unit,
|
||||
onDeleteClick: () -> Unit
|
||||
): List<ActionMenuItem> {
|
||||
return listOfNotNull(
|
||||
when {
|
||||
!isSearchActive -> ActionMenuItem(
|
||||
icon = IconItem.Drawable(R.drawable.action_search),
|
||||
contentDescription = R.string.search_label,
|
||||
onClick = onSearchClick,
|
||||
testingTag = SEARCH_ICON_TESTING_TAG
|
||||
)
|
||||
|
||||
else -> null
|
||||
},
|
||||
ActionMenuItem(
|
||||
icon = IconItem.Vector(Icons.Default.Delete),
|
||||
// Adding content description for #3825.
|
||||
contentDescription = deleteIconTitle,
|
||||
onClick = onDeleteClick,
|
||||
testingTag = DELETE_MENU_ICON_TESTING_TAG
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
compositeDisposable.clear()
|
||||
fragmentPageBinding?.apply {
|
||||
recyclerView.adapter = null
|
||||
root.removeAllViews()
|
||||
}
|
||||
fragmentPageBinding = null
|
||||
}
|
||||
|
||||
private fun render(state: PageState<*>) {
|
||||
pageAdapter.items = state.visiblePageItems
|
||||
fragmentPageBinding?.pageSwitch?.isEnabled = !state.isInSelectionState
|
||||
fragmentPageBinding?.noPage?.visibility = if (state.pageItems.isEmpty()) VISIBLE else GONE
|
||||
pageScreenState.value = pageScreenState.value.copy(
|
||||
switchIsEnabled = !state.isInSelectionState,
|
||||
// First, assign the existing state to force Compose to recognize a change.
|
||||
// This helps when internal properties of items (like `isSelected`) change,
|
||||
// but the list reference itself remains the same — Compose won't detect it otherwise.
|
||||
pageState = pageState.value
|
||||
)
|
||||
// Then, assign the actual updated state to trigger full recomposition.
|
||||
pageScreenState.value = pageScreenState.value.copy(
|
||||
pageState = state
|
||||
)
|
||||
if (state.isInSelectionState) {
|
||||
if (actionMode == null) {
|
||||
actionMode =
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (c) 2025 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.page
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.kiwix.kiwixmobile.core.page.viewmodel.PageState
|
||||
|
||||
/**
|
||||
* Represents the UI state for the PageFragment Screen.
|
||||
* A Base screen for Bookmarks, History, and Notes screens.
|
||||
*
|
||||
* This data class encapsulates all UI-related states in a single object,
|
||||
* reducing complexity in the Fragment.
|
||||
*/
|
||||
data class PageFragmentScreenState(
|
||||
val pageState: PageState<*>,
|
||||
val isSearchActive: Boolean,
|
||||
val searchQueryHint: String,
|
||||
val searchText: String,
|
||||
val searchValueChangedListener: (String) -> Unit,
|
||||
val clearSearchButtonClickListener: () -> Unit,
|
||||
@StringRes val screenTitle: Int,
|
||||
val noItemsString: String,
|
||||
val switchString: String,
|
||||
val switchIsChecked: Boolean,
|
||||
val switchIsEnabled: Boolean = true,
|
||||
val onSwitchCheckedChanged: (Boolean) -> Unit,
|
||||
@StringRes val deleteIconTitle: Int
|
||||
)
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (c) 2025 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.page
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.style.TextOverflow
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.OnItemClickListener
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.Page
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.PAGE_LIST_ITEM_FAVICON_SIZE
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
|
||||
const val PAGE_ITEM_TESTING_TAG = "pageItemTestingTag"
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PageListItem(
|
||||
page: Page,
|
||||
itemClickListener: OnItemClickListener
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(
|
||||
onClick = { itemClickListener.onItemClick(page) },
|
||||
onLongClick = { itemClickListener.onItemLongClick(page) }
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(
|
||||
horizontal = SIXTEEN_DP,
|
||||
vertical = EIGHT_DP
|
||||
)
|
||||
.semantics { testTag = PAGE_ITEM_TESTING_TAG },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = if (page.isSelected) {
|
||||
painterResource(id = R.drawable.ic_check_circle_blue_24dp)
|
||||
} else {
|
||||
Base64String(page.favicon).toPainter()
|
||||
},
|
||||
contentDescription = stringResource(R.string.fav_icon),
|
||||
modifier = Modifier
|
||||
.size(PAGE_LIST_ITEM_FAVICON_SIZE)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(SIXTEEN_DP))
|
||||
|
||||
Text(
|
||||
text = page.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
247
core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt
Normal file
247
core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt
Normal file
@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (c) 2025 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.page
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.OnItemClickListener
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.Page
|
||||
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.DateItem
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView
|
||||
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.AlabasterWhite
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.Black
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.White
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOURTEEN_SP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.PAGE_SWITCH_LEFT_RIGHT_MARGIN
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.PAGE_SWITCH_ROW_BOTTOM_MARGIN
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
import org.threeten.bp.LocalDate
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
import org.threeten.bp.format.DateTimeParseException
|
||||
|
||||
const val SWITCH_TEXT_TESTING_TAG = "switchTextTestingTag"
|
||||
const val NO_ITEMS_TEXT_TESTING_TAG = "noItemsTextTestingTag"
|
||||
const val PAGE_LIST_TEST_TAG = "pageListTestingTag"
|
||||
|
||||
@Suppress("ComposableLambdaParameterNaming")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PageScreen(
|
||||
state: PageFragmentScreenState,
|
||||
itemClickListener: OnItemClickListener,
|
||||
actionMenuItems: List<ActionMenuItem>,
|
||||
navigationIcon: @Composable () -> Unit
|
||||
) {
|
||||
KiwixTheme {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Column {
|
||||
KiwixAppBar(
|
||||
titleId = state.screenTitle,
|
||||
navigationIcon = navigationIcon,
|
||||
actionMenuItems = actionMenuItems,
|
||||
searchBar = searchBarIfActive(state)
|
||||
)
|
||||
PageSwitchRow(state)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
val items = state.pageState.pageItems
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = padding.calculateTopPadding(),
|
||||
start = padding.calculateStartPadding(LocalLayoutDirection.current),
|
||||
end = padding.calculateEndPadding(LocalLayoutDirection.current)
|
||||
)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
if (items.isEmpty()) {
|
||||
Text(
|
||||
text = state.noItemsString,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.semantics { testTag = NO_ITEMS_TEXT_TESTING_TAG }
|
||||
)
|
||||
} else {
|
||||
PageList(
|
||||
state = state,
|
||||
itemClickListener = itemClickListener
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PageList(
|
||||
state: PageFragmentScreenState,
|
||||
itemClickListener: OnItemClickListener
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.isScrollInProgress }
|
||||
.collect { isScrolling ->
|
||||
if (isScrolling) {
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow((context as? Activity)?.currentFocus?.windowToken, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(state = listState, modifier = Modifier.semantics { testTag = PAGE_LIST_TEST_TAG }) {
|
||||
items(state.pageState.visiblePageItems) { item ->
|
||||
when (item) {
|
||||
is Page -> PageListItem(page = item, itemClickListener = itemClickListener)
|
||||
is DateItem -> DateItemText(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun searchBarIfActive(
|
||||
state: PageFragmentScreenState
|
||||
): (@Composable () -> Unit)? = if (state.isSearchActive) {
|
||||
{
|
||||
KiwixSearchView(
|
||||
placeholder = state.searchQueryHint,
|
||||
value = state.searchText,
|
||||
testTag = "",
|
||||
onValueChange = { state.searchValueChangedListener(it) },
|
||||
onClearClick = { state.clearSearchButtonClickListener.invoke() }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PageSwitchRow(
|
||||
state: PageFragmentScreenState
|
||||
) {
|
||||
val context = LocalActivity.current as CoreMainActivity
|
||||
// hide switches for custom apps, see more info here https://github.com/kiwix/kiwix-android/issues/3523
|
||||
if (!context.isCustomApp()) {
|
||||
val switchTextColor = if (isSystemInDarkTheme()) {
|
||||
AlabasterWhite
|
||||
} else {
|
||||
White
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Black)
|
||||
.padding(bottom = PAGE_SWITCH_ROW_BOTTOM_MARGIN),
|
||||
horizontalArrangement = Arrangement.Absolute.Right,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
state.switchString,
|
||||
color = switchTextColor,
|
||||
style = TextStyle(fontSize = FOURTEEN_SP),
|
||||
modifier = Modifier.testTag(SWITCH_TEXT_TESTING_TAG)
|
||||
)
|
||||
Switch(
|
||||
checked = state.switchIsChecked,
|
||||
onCheckedChange = { state.onSwitchCheckedChanged(it) },
|
||||
enabled = state.switchIsEnabled,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = PAGE_SWITCH_LEFT_RIGHT_MARGIN),
|
||||
colors = SwitchDefaults.colors(
|
||||
uncheckedTrackColor = White
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DateItemText(dateItem: DateItem) {
|
||||
Text(
|
||||
text = getFormattedDateLabel(dateItem.dateString),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(SIXTEEN_DP)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getFormattedDateLabel(dateString: String): String {
|
||||
val today = LocalDate.now()
|
||||
val yesterday = today.minusDays(1)
|
||||
|
||||
val parsedDate = parseDateSafely(dateString)
|
||||
return when (parsedDate) {
|
||||
today -> stringResource(R.string.time_today)
|
||||
yesterday -> stringResource(R.string.time_yesterday)
|
||||
else -> dateString
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDateSafely(dateString: String): LocalDate? {
|
||||
return try {
|
||||
LocalDate.parse(dateString, DateTimeFormatter.ofPattern("d MMM yyyy"))
|
||||
} catch (_: DateTimeParseException) {
|
||||
null
|
||||
}
|
||||
}
|
@ -16,11 +16,11 @@ class BookmarksFragment : PageFragment() {
|
||||
PageAdapter(PageItemDelegate(this))
|
||||
}
|
||||
|
||||
override val screenTitle: String by lazy { getString(R.string.bookmarks) }
|
||||
override val screenTitle: Int = R.string.bookmarks
|
||||
override val noItemsString: String by lazy { getString(R.string.no_bookmarks) }
|
||||
override val switchString: String by lazy { getString(R.string.bookmarks_from_current_book) }
|
||||
override val deleteIconTitle: String by lazy {
|
||||
getString(R.string.pref_clear_all_bookmarks_title)
|
||||
override val deleteIconTitle: Int by lazy {
|
||||
R.string.pref_clear_all_bookmarks_title
|
||||
}
|
||||
override val switchIsChecked: Boolean by lazy { sharedPreferenceUtil.showBookmarksAllBooks }
|
||||
|
||||
|
@ -21,9 +21,9 @@ class HistoryFragment : PageFragment() {
|
||||
|
||||
override val noItemsString: String by lazy { getString(R.string.no_history) }
|
||||
override val switchString: String by lazy { getString(R.string.history_from_current_book) }
|
||||
override val screenTitle: String by lazy { getString(R.string.history) }
|
||||
override val deleteIconTitle: String by lazy {
|
||||
getString(R.string.pref_clear_all_history_title)
|
||||
override val screenTitle: Int = R.string.history
|
||||
override val deleteIconTitle: Int by lazy {
|
||||
R.string.pref_clear_all_history_title
|
||||
}
|
||||
override val switchIsChecked: Boolean by lazy { sharedPreferenceUtil.showHistoryAllBooks }
|
||||
|
||||
|
@ -30,8 +30,7 @@ import org.kiwix.kiwixmobile.core.page.notes.viewmodel.NotesViewModel
|
||||
class NotesFragment : PageFragment() {
|
||||
override val pageViewModel by lazy { viewModel<NotesViewModel>(viewModelFactory) }
|
||||
|
||||
override val screenTitle: String
|
||||
get() = getString(R.string.pref_notes)
|
||||
override val screenTitle: Int = R.string.pref_notes
|
||||
|
||||
override val pageAdapter: PageAdapter by lazy {
|
||||
PageAdapter(PageDelegate.PageItemDelegate(this))
|
||||
@ -39,8 +38,8 @@ class NotesFragment : PageFragment() {
|
||||
|
||||
override val noItemsString: String by lazy { getString(R.string.no_notes) }
|
||||
override val switchString: String by lazy { getString(R.string.notes_from_all_books) }
|
||||
override val deleteIconTitle: String by lazy {
|
||||
getString(R.string.pref_clear_notes)
|
||||
override val deleteIconTitle: Int by lazy {
|
||||
R.string.pref_clear_notes
|
||||
}
|
||||
override val switchIsChecked: Boolean by lazy { sharedPreferenceUtil.showNotesAllBooks }
|
||||
|
||||
|
@ -39,8 +39,9 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens
|
||||
|
||||
@Composable
|
||||
fun KiwixSearchView(
|
||||
modifier: Modifier,
|
||||
modifier: Modifier = Modifier,
|
||||
value: String,
|
||||
placeholder: String = stringResource(R.string.search_label),
|
||||
testTag: String = "",
|
||||
onValueChange: (String) -> Unit,
|
||||
onClearClick: () -> Unit
|
||||
@ -65,7 +66,7 @@ fun KiwixSearchView(
|
||||
value = value,
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.search_label),
|
||||
text = placeholder,
|
||||
color = Color.LightGray,
|
||||
fontSize = ComposeDimens.EIGHTEEN_SP
|
||||
)
|
||||
|
@ -106,4 +106,9 @@ object ComposeDimens {
|
||||
val HELP_SCREEN_ITEM_TITLE_TEXT_SIZE = 20.sp
|
||||
val HELP_SCREEN_ITEM_TITLE_LETTER_SPACING = 0.0125.em
|
||||
val HELP_SCREEN_ARROW_ICON_SIZE = 35.dp
|
||||
|
||||
// Page dimens
|
||||
val PAGE_LIST_ITEM_FAVICON_SIZE = 40.dp
|
||||
val PAGE_SWITCH_LEFT_RIGHT_MARGIN = 10.dp
|
||||
val PAGE_SWITCH_ROW_BOTTOM_MARGIN = 8.dp
|
||||
}
|
||||
|
@ -1,51 +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="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
app:popupTheme="@style/KiwixTheme"
|
||||
tools:showIn="@layout/fragment_search" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/page_switch"
|
||||
style="@style/switch_style" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_page"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.KiwixTheme.Headline5"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar"
|
||||
tools:listitem="@layout/item_bookmark_history" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
x
Reference in New Issue
Block a user