Fixed: When selecting page items the changes are not reflecting in UI.

* Improved the clicking on page item.
* Fixed: Page items are not showing updating after deleting.
* Fixed: Switch was not disabling when selecting the page items.
* Added SearchView, navigationIcon, and menuItems to page fragment.
This commit is contained in:
MohitMaliFtechiz 2025-04-11 14:56:09 +05:30
parent bceebd5de4
commit acbf1bfec3
8 changed files with 150 additions and 72 deletions

View File

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

View File

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

View File

@ -24,13 +24,20 @@ 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.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.referentialEqualityPolicy
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
@ -44,7 +51,6 @@ 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
@ -52,14 +58,21 @@ 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<*, *>
@ -74,7 +87,26 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
abstract val searchQueryHint: String
abstract val pageAdapter: PageAdapter
abstract val switchIsChecked: Boolean
abstract val deleteIconTitle: String
abstract val deleteIconTitle: Int
private val pageState: MutableState<PageState<*>> =
mutableStateOf(
NotesState(
emptyList(),
true,
""
),
policy = referentialEqualityPolicy()
)
/**
* Controls the visibility of the "Switch", and its controls.
*
* A [Triple] containing:
* - [String]: The text displayed with switch.
* - [Boolean]: Whether the switch is checked or not.
* - [Boolean]: Whether the switch is enabled or disabled.
*/
private val pageSwitchItem = mutableStateOf(Triple("", true, true))
private var fragmentPageBinding: FragmentPageBinding? = null
override val fragmentToolbar: Toolbar? by lazy {
fragmentPageBinding?.root?.findViewById(R.id.toolbar)
@ -119,7 +151,7 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
}
)
}
menu.findItem(R.id.menu_pages_clear).title = deleteIconTitle // Bug fix #3825
// menu.findItem(R.id.menu_pages_clear).title = deleteIconTitle // Bug fix #3825
}
@Suppress("ReturnCount")
@ -155,16 +187,16 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
}
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
}
// 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))
}
// fragmentPageBinding?.pageSwitch?.setOnCheckedChangeListener { _, isChecked ->
// pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked))
// }
pageViewModel.state.observe(viewLifecycleOwner, Observer(::render))
// hides keyboard when scrolled
@ -184,38 +216,85 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
): View? {
return ComposeView(requireContext()).apply {
setContent {
val isSearchActive = remember { mutableStateOf(false) }
PageScreen(
pageState = pageViewModel.state,
effects = pageViewModel.effects,
pageState = pageState.value,
pageSwitchItem = pageSwitchItem.value,
screenTitle = screenTitle,
noItemsString = noItemsString,
switchString = switchString,
searchQueryHint = searchQueryHint,
switchIsChecked = switchIsChecked,
onSwitchChanged = { isChecked ->
pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked))
onSwitchChanged = { onSwitchCheckedChanged(it) },
itemClickListener = this@PageFragment,
navigationIcon = {
NavigationIcon(
iconItem = navigationIconItem(isSearchActive.value),
onClick = navigationIconClick(isSearchActive)
)
},
onItemClick = { pageViewModel.actions.offer(Action.OnItemClick(it)) },
onItemLongClick = { pageViewModel.actions.offer(Action.OnItemLongClick(it)) }
actionMenuItems = actionMenuList(
isSearchActive = isSearchActive.value,
onSearchClick = { isSearchActive.value = true },
onDeleteClick = { pageViewModel.actions.offer(Action.UserClickedDeleteButton) }
)
)
}
}
}
private fun onSwitchCheckedChanged(isChecked: Boolean): () -> Unit = {
pageSwitchItem.value = pageSwitchItem.value.copy(second = isChecked)
pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked))
}
private fun navigationIconItem(isSearchActive: Boolean) = if (isSearchActive) {
IconItem.Drawable(R.drawable.ic_close_white_24dp)
} else {
IconItem.Vector(Icons.AutoMirrored.Filled.ArrowBack)
}
private fun navigationIconClick(isSearchActive: MutableState<Boolean>): () -> Unit = {
if (isSearchActive.value) {
isSearchActive.value = false
pageViewModel.actions.offer(Action.Exit)
} else {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
}
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
pageState.value = state
pageSwitchItem.value = Triple(switchString, switchIsChecked, !state.isInSelectionState)
if (state.isInSelectionState) {
if (actionMode == null) {
actionMode =

View File

@ -39,6 +39,7 @@ 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
@ -48,13 +49,15 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
@Composable
fun PageListItem(
page: Page,
onClick: () -> Unit,
onLongClick: () -> Unit
itemClickListener: OnItemClickListener
) {
Row(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
.combinedClickable(
onClick = { itemClickListener.onItemClick(page) },
onLongClick = { itemClickListener.onItemLongClick(page) }
)
.background(MaterialTheme.colorScheme.surface)
.padding(
horizontal = SIXTEEN_DP,

View File

@ -32,55 +32,51 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.MutableLiveData
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
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.page.viewmodel.PageState
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
@Suppress("LongParameterList", "IgnoredReturnValue", "UnusedParameter")
@Suppress(
"LongParameterList",
"IgnoredReturnValue",
"UnusedParameter",
"ComposableLambdaParameterNaming"
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PageScreen(
pageState: MutableLiveData<out PageState<out Page>>,
effects: PublishProcessor<SideEffect<*>>,
pageState: PageState<out Page>,
pageSwitchItem: Triple<String, Boolean, Boolean>,
screenTitle: Int,
noItemsString: String,
switchString: String,
searchQueryHint: String,
switchIsChecked: Boolean,
onSwitchChanged: (Boolean) -> Unit,
onItemClick: (Page) -> Unit,
onItemLongClick: (Page) -> Unit
itemClickListener: OnItemClickListener,
actionMenuItems: List<ActionMenuItem>,
navigationIcon: @Composable () -> Unit,
) {
val context = LocalActivity.current as CoreMainActivity
val state by pageState.observeAsState()
LaunchedEffect(Unit) {
effects.subscribe { it.invokeWith(context) }
}
KiwixTheme {
Scaffold(
topBar = {
Column {
KiwixAppBar(
titleId = screenTitle,
navigationIcon = {},
navigationIcon = navigationIcon,
actionMenuItems = actionMenuItems
)
// hide switches for custom apps, see more info here https://github.com/kiwix/kiwix-android/issues/3523
if (!context.isCustomApp()) {
Row(
modifier = Modifier
@ -88,14 +84,18 @@ fun PageScreen(
.padding(horizontal = SIXTEEN_DP, vertical = EIGHT_DP),
verticalAlignment = Alignment.CenterVertically
) {
Text(switchString, modifier = Modifier.weight(1f))
Switch(checked = switchIsChecked, onCheckedChange = onSwitchChanged)
Text(pageSwitchItem.first, modifier = Modifier.weight(1f))
Switch(
checked = pageSwitchItem.second,
onCheckedChange = onSwitchChanged,
enabled = pageSwitchItem.third
)
}
}
}
}
) { padding ->
val items = state?.visiblePageItems.orEmpty()
val items = pageState.pageItems
Box(modifier = Modifier.padding(padding)) {
if (items.isEmpty()) {
Text(
@ -105,13 +105,12 @@ fun PageScreen(
)
} else {
LazyColumn {
items(items) { item ->
items(pageState.visiblePageItems) { item ->
when (item) {
is Page -> {
PageListItem(
page = item,
onClick = { onItemClick(item) },
onLongClick = { onItemLongClick(item) }
itemClickListener = itemClickListener
)
}

View File

@ -19,8 +19,8 @@ class BookmarksFragment : PageFragment() {
override val screenTitle: Int by lazy { 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 }

View File

@ -22,8 +22,8 @@ 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: Int by lazy { R.string.history }
override val deleteIconTitle: String by lazy {
getString(R.string.pref_clear_all_history_title)
override val deleteIconTitle: Int by lazy {
R.string.pref_clear_all_history_title
}
override val switchIsChecked: Boolean by lazy { sharedPreferenceUtil.showHistoryAllBooks }

View File

@ -38,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 }