Feat: Language fragment to jetpack compose screen

- De-coupled and lifted the states of language screen.
- added preview to language screen to better manage the ui.
- added tests.
- updated the animation logic to Launched effects to avoid circular dependency.
This commit is contained in:
jaskaran 2025-03-17 13:41:06 +05:30 committed by MohitMaliFtechiz
parent e939ff770d
commit 3e42aa3725
20 changed files with 794 additions and 431 deletions

View File

@ -19,6 +19,7 @@ package org.kiwix.kiwixmobile.language
import android.Manifest
import android.app.Instrumentation
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.content.edit
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
@ -50,6 +51,9 @@ class LanguageFragmentTest {
@JvmField
val retryRule = RetryRule()
@get:Rule
val composeTestRule = createComposeRule()
private val permissions =
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
@ -61,9 +65,7 @@ class LanguageFragmentTest {
var permissionRules: GrantPermissionRule =
GrantPermissionRule.grant(*permissions)
private val instrumentation: Instrumentation by lazy {
InstrumentationRegistry.getInstrumentation()
}
private val instrumentation: Instrumentation by lazy(InstrumentationRegistry::getInstrumentation)
init {
AccessibilityChecks.enable().setRunChecksFromRootView(true)
@ -108,45 +110,81 @@ class LanguageFragmentTest {
// search and de-select if german language already selected
clickOnLanguageIcon()
clickOnLanguageSearchIcon()
searchLanguage("german")
deSelectLanguageIfAlreadySelected()
clickOnSaveLanguageIcon()
clickOnLanguageSearchIcon(composeTestRule)
searchLanguage(
composeTestRule = composeTestRule,
searchLanguage = "german"
)
deSelectLanguageIfAlreadySelected(
composeTestRule = composeTestRule,
matchLanguage = "German"
)
clickOnSaveLanguageIcon(composeTestRule)
// search and de-select if italian language already selected
clickOnLanguageIcon()
clickOnLanguageSearchIcon()
searchLanguage("italiano")
deSelectLanguageIfAlreadySelected()
clickOnSaveLanguageIcon()
clickOnLanguageSearchIcon(composeTestRule)
searchLanguage(
composeTestRule = composeTestRule,
searchLanguage = "italiano"
)
deSelectLanguageIfAlreadySelected(
composeTestRule = composeTestRule,
matchLanguage = "Italian"
)
clickOnSaveLanguageIcon(composeTestRule)
// Search and save language for german
clickOnLanguageIcon()
clickOnLanguageSearchIcon()
searchLanguage("german")
selectLanguage("German")
clickOnSaveLanguageIcon()
clickOnLanguageSearchIcon(composeTestRule)
searchLanguage(
composeTestRule = composeTestRule,
searchLanguage = "german"
)
selectLanguage(
composeTestRule = composeTestRule,
matchLanguage = "German"
)
clickOnSaveLanguageIcon(composeTestRule)
// Search and save language for italian
clickOnLanguageIcon()
clickOnLanguageSearchIcon()
searchLanguage("italiano")
selectLanguage("Italian")
clickOnSaveLanguageIcon()
clickOnLanguageSearchIcon(composeTestRule)
searchLanguage(
composeTestRule = composeTestRule,
searchLanguage = "italiano"
)
selectLanguage(
composeTestRule = composeTestRule,
matchLanguage = "Italian"
)
clickOnSaveLanguageIcon(composeTestRule)
// verify is german language selected
clickOnLanguageIcon()
clickOnLanguageSearchIcon()
searchLanguage("german")
checkIsLanguageSelected()
clickOnSaveLanguageIcon()
clickOnLanguageSearchIcon(composeTestRule)
searchLanguage(
composeTestRule = composeTestRule,
searchLanguage = "german"
)
checkIsLanguageSelected(
composeTestRule = composeTestRule,
matchLanguage = "German"
)
clickOnSaveLanguageIcon(composeTestRule)
// verify is italian language selected
clickOnLanguageIcon()
clickOnLanguageSearchIcon()
searchLanguage("italiano")
checkIsLanguageSelected()
clickOnSaveLanguageIcon()
clickOnLanguageSearchIcon(composeTestRule)
searchLanguage(
composeTestRule = composeTestRule,
searchLanguage = "italiano"
)
checkIsLanguageSelected(
composeTestRule = composeTestRule,
matchLanguage = "Italian"
)
clickOnSaveLanguageIcon(composeTestRule)
}
LeakAssertions.assertNoLeaks()
}

View File

@ -18,27 +18,28 @@
package org.kiwix.kiwixmobile.language
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId
import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh
import junit.framework.AssertionFailedError
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.R
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.language.composables.LANGUAGE_ITEM_CHECKBOX_TESTING_TAG
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
import org.kiwix.kiwixmobile.utils.RecyclerViewMatcher
fun language(func: LanguageRobot.() -> Unit) = LanguageRobot().applyWithViewHierarchyPrinting(func)
@ -82,57 +83,57 @@ class LanguageRobot : BaseRobot() {
}
fun clickOnLanguageIcon() {
// Wait for a few seconds to properly saved selected language.
// Wait for a few seconds to properly save selected language.
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
clickOn(ViewId(R.id.select_language))
}
fun clickOnLanguageSearchIcon() {
testFlakyView({ onView(withId(R.id.menu_language_search)).perform(click()) })
fun clickOnSaveLanguageIcon(composeTestRule: ComposeContentTestRule) {
composeTestRule.onNodeWithTag(SAVE_ICON_TESTING_TAG)
.performClick()
}
fun searchLanguage(searchLanguage: String) {
isVisible(ViewId(androidx.appcompat.R.id.search_src_text)).text = searchLanguage
fun clickOnLanguageSearchIcon(composeTestRule: ComposeContentTestRule) {
composeTestRule.onNodeWithTag(SEARCH_ICON_TESTING_TAG).performClick()
}
fun selectLanguage(matchLanguage: String) {
testFlakyView({ clickOn(Text(matchLanguage)) })
fun searchLanguage(
composeTestRule: ComposeContentTestRule,
searchLanguage: String
) {
val searchField = composeTestRule.onNodeWithTag(SEARCH_FIELD_TESTING_TAG)
searchField.performTextInput(text = searchLanguage)
}
fun clickOnSaveLanguageIcon() {
clickOn(ViewId(R.id.menu_language_save))
}
fun checkIsLanguageSelected() {
// Wait for a second to properly visible the searched language on top.
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
onView(
RecyclerViewMatcher(R.id.language_recycler_view).atPositionOnView(
1,
R.id.item_language_checkbox
)
).check(
matches(isChecked())
)
}
fun deSelectLanguageIfAlreadySelected() {
// Wait for a second to properly visible the searched language on top.
// error prone
fun deSelectLanguageIfAlreadySelected(
composeTestRule: ComposeContentTestRule,
matchLanguage: String
) {
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
try {
onView(
RecyclerViewMatcher(R.id.language_recycler_view).atPositionOnView(
1,
R.id.item_language_checkbox
)
).check(matches(isNotChecked()))
} catch (assertionError: AssertionFailedError) {
onView(
RecyclerViewMatcher(R.id.language_recycler_view).atPositionOnView(
1,
R.id.item_language_checkbox
)
).perform(click())
composeTestRule.onNodeWithTag("$LANGUAGE_ITEM_CHECKBOX_TESTING_TAG$matchLanguage")
.assertIsOff()
} catch (noMatchingNodeException: AssertionError) {
composeTestRule.onNodeWithTag("$LANGUAGE_ITEM_CHECKBOX_TESTING_TAG$matchLanguage")
.performClick()
}
}
fun selectLanguage(
composeTestRule: ComposeContentTestRule,
matchLanguage: String
) {
composeTestRule.onNodeWithText(matchLanguage)
.performClick()
}
fun checkIsLanguageSelected(
composeTestRule: ComposeContentTestRule,
matchLanguage: String
) {
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
composeTestRule.onNodeWithTag("$LANGUAGE_ITEM_CHECKBOX_TESTING_TAG$matchLanguage")
.assertIsOn()
}
}

View File

@ -20,46 +20,18 @@ package org.kiwix.kiwixmobile.language
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.ViewGroup
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.lifecycle.Observer
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.disposables.CompositeDisposable
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.R.drawable
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.cachedComponent
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.extensions.closeKeyboard
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.extensions.setUpSearchView
import org.kiwix.kiwixmobile.core.extensions.viewModel
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
import org.kiwix.kiwixmobile.databinding.ActivityLanguageBinding
import org.kiwix.kiwixmobile.language.adapter.LanguageAdapter
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.HeaderDelegate
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.LanguageItemDelegate
import org.kiwix.kiwixmobile.language.viewmodel.Action
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import org.kiwix.kiwixmobile.language.viewmodel.State
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import javax.inject.Inject
class LanguageFragment : BaseFragment() {
@ -67,16 +39,8 @@ class LanguageFragment : BaseFragment() {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var composeView: ComposeView
private val compositeDisposable = CompositeDisposable()
private var activityLanguageBinding: ActivityLanguageBinding? = null
private var searchView: SearchView? = null
private val languageAdapter =
LanguageAdapter(
LanguageItemDelegate { languageViewModel.actions.offer(Select(it)) },
HeaderDelegate()
)
override fun inject(baseActivity: BaseActivity) {
baseActivity.cachedComponent.inject(this)
}
@ -84,24 +48,15 @@ class LanguageFragment : BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as CoreMainActivity
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
it.setHomeAsUpIndicator(drawable.ic_clear_white_24dp)
it.setTitle(string.select_languages)
composeView.setContent {
LanguageScreen(
viewModelState = languageViewModel.state,
onNavigationClick = activity.onBackPressedDispatcher::onBackPressed,
selectLanguageItem = { languageViewModel.actions.offer(Action.Select(it)) },
filterText = { languageViewModel.actions.offer(Action.Filter(it)) },
saveLanguages = { languageViewModel.actions.offer(Action.SaveAll) },
)
}
// set the contentDescription to navigation back button
toolbar.getToolbarNavigationIcon()?.setToolTipWithContentDescription(
getString(string.toolbar_back_button_content_description)
)
activityLanguageBinding?.languageRecyclerView?.run {
adapter = languageAdapter
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
languageViewModel.state.observe(viewLifecycleOwner, Observer(::render))
compositeDisposable.add(
languageViewModel.effects.subscribe(
{
@ -116,63 +71,14 @@ class LanguageFragment : BaseFragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
setupMenu()
activityLanguageBinding = ActivityLanguageBinding.inflate(inflater, container, false)
return activityLanguageBinding?.root
}
private fun setupMenu() {
(requireActivity() as MenuHost).addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_language, menu)
searchView = menu.findItem(R.id.menu_language_search).actionView as SearchView
searchView?.apply {
setUpSearchView(requireActivity())
setOnQueryTextListener(
SimpleTextListener { query, _ ->
languageViewModel.actions.offer(Filter(query))
}
)
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.menu_language_save -> {
closeKeyboard()
languageViewModel.actions.offer(Action.SaveAll)
true
}
else -> false
}
}
},
viewLifecycleOwner,
Lifecycle.State.RESUMED
)
}
private fun render(state: State) =
when (state) {
Loading -> activityLanguageBinding?.languageProgressbar?.show()
is Content -> {
activityLanguageBinding?.languageProgressbar?.hide()
languageAdapter.items = state.viewItems
}
Saving -> Unit
): View {
return ComposeView(requireContext()).also {
composeView = it
}
}
override fun onDestroyView() {
super.onDestroyView()
compositeDisposable.clear()
activityLanguageBinding?.root?.removeAllViews()
searchView?.setOnQueryTextListener(null)
searchView = null
activityLanguageBinding?.languageRecyclerView?.adapter = null
activityLanguageBinding = null
}
}

View File

@ -0,0 +1,273 @@
/*
* 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.language
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
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.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.language.composables.AppBarTextField
import org.kiwix.kiwixmobile.language.composables.LanguageList
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.composables.LoadingIndicator
import org.kiwix.kiwixmobile.language.viewmodel.State
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
const val SEARCH_ICON_TESTING_TAG = "search"
const val SAVE_ICON_TESTING_TAG = "saveLanguages"
const val SEARCH_FIELD_TESTING_TAG = "searchField"
@Suppress("all")
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun LanguageScreen(
viewModelState: MutableLiveData<State>,
selectLanguageItem: (LanguageItem) -> Unit,
filterText: (String) -> Unit,
onNavigationClick: () -> Unit,
saveLanguages: () -> Unit
) {
val state by viewModelState.observeAsState(State.Loading)
val context = LocalContext.current
var searchText by remember { mutableStateOf("") }
var isSearchActive by remember { mutableStateOf(false) }
var updateListState by remember { mutableStateOf(false) }
val listState: LazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
val scrollToTop = {
coroutineScope.launch {
listState.scrollToItem(0)
}
}
KiwixTheme {
Scaffold(
topBar = {
KiwixAppBar(
R.string.select_languages,
{
NavigationIcon(
iconItem = if (isSearchActive) {
IconItem.Vector(Icons.AutoMirrored.Filled.ArrowBack)
} else {
IconItem.Drawable(
R.drawable.ic_close_white_24dp
)
},
onClick = {
if (isSearchActive) {
isSearchActive = false
searchText = ""
filterText(searchText)
} else {
onNavigationClick()
scrollToTop()
}
}
)
},
listOfNotNull(
// First item: conditionally include based on search state
when {
!isSearchActive -> ActionMenuItem(
icon = IconItem.Drawable(R.drawable.action_search),
contentDescription = R.string.search_label,
onClick = {
isSearchActive = true
},
iconTint = Color.White,
isEnabled = true,
testingTag = SEARCH_ICON_TESTING_TAG
)
searchText.isNotEmpty() -> ActionMenuItem(
icon = IconItem.Drawable(R.drawable.ic_clear_white_24dp),
contentDescription = R.string.search_label,
onClick = {
searchText = ""
filterText(searchText)
},
iconTint = Color.White,
isEnabled = true,
testingTag = ""
)
else -> null // Handle the case when both conditions are false
},
// Second item: always included
ActionMenuItem(
icon = IconItem.Vector(Icons.Default.Check),
contentDescription = R.string.save_languages,
onClick = {
saveLanguages()
updateListState = true
},
iconTint = Color.White,
isEnabled = true,
testingTag = SAVE_ICON_TESTING_TAG
)
),
searchBar = if (isSearchActive) {
{
AppBarTextField(
value = searchText,
onValueChange = {
searchText = it
filterText(it)
},
testTag = SEARCH_FIELD_TESTING_TAG,
hint = stringResource(R.string.search_label),
keyboardOptions = KeyboardOptions.Default,
keyboardActions = KeyboardActions.Default
)
}
} else {
null
}
)
}
) {
Column(modifier = Modifier.fillMaxSize()) {
// spacer to account for top app bar
Spacer(modifier = Modifier.height(56.dp))
when (state) {
State.Loading, State.Saving -> {
LoadingIndicator()
}
is Content -> {
val viewItem = if (!updateListState) {
(state as Content).viewItems
} else {
emptyList()
}
LaunchedEffect(viewItem) {
snapshotFlow(listState::firstVisibleItemIndex)
.collect {
if (listState.firstVisibleItemIndex == 2) {
listState.animateScrollToItem(0)
}
}
}
LanguageList(
context = context,
listState = listState,
viewItem = viewItem,
selectLanguageItem = {
selectLanguageItem(it)
}
)
}
}
}
}
}
}
@Preview
@Composable
fun LanguageScreenPreview() {
val languages = listOf(
Language(
id = 1,
active = true,
occurencesOfLanguage = 142,
language = "English",
languageLocalized = "English",
languageCode = "eng",
languageCodeISO2 = "en"
),
Language(
id = 2,
active = true,
occurencesOfLanguage = 86,
language = "German",
languageLocalized = "Deutsch",
languageCode = "deu",
languageCodeISO2 = "de"
),
Language(
id = 3,
active = true,
occurencesOfLanguage = 72,
language = "Italian",
languageLocalized = "Italiano",
languageCode = "ita",
languageCodeISO2 = "it"
),
Language(
id = 4,
active = true,
occurencesOfLanguage = 93,
language = "French",
languageLocalized = "Français",
languageCode = "fra",
languageCodeISO2 = "fr"
),
Language(
id = 5,
active = true,
occurencesOfLanguage = 104,
language = "Spanish",
languageLocalized = "Español",
languageCode = "spa",
languageCodeISO2 = "es"
)
)
LanguageScreen(
viewModelState = MutableLiveData<State>().apply { value = Content(languages) },
selectLanguageItem = {},
filterText = {},
saveLanguages = {},
onNavigationClick = {}
)
}

View File

@ -1,52 +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.language.adapter
import android.view.ViewGroup
import org.kiwix.kiwixmobile.core.base.adapter.AbsDelegateAdapter
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.viewBinding
import org.kiwix.kiwixmobile.databinding.HeaderDateBinding
import org.kiwix.kiwixmobile.databinding.ItemLanguageBinding
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.HeaderViewHolder
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.LanguageViewHolder
sealed class LanguageDelegate<I : LanguageListItem, out VH : LanguageListViewHolder<I>> :
AbsDelegateAdapter<I, LanguageListItem, VH> {
class HeaderDelegate : LanguageDelegate<HeaderItem, HeaderViewHolder>() {
override val itemClass = HeaderItem::class.java
override fun createViewHolder(parent: ViewGroup) =
HeaderViewHolder(
parent.viewBinding(HeaderDateBinding::inflate, false)
)
}
class LanguageItemDelegate(private val clickAction: (LanguageItem) -> Unit) :
LanguageDelegate<LanguageItem, LanguageViewHolder>() {
override val itemClass = LanguageItem::class.java
override fun createViewHolder(parent: ViewGroup) =
LanguageViewHolder(
parent.viewBinding(ItemLanguageBinding::inflate, false),
clickAction
)
}
}

View File

@ -1,70 +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.language.adapter
import android.view.View
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.adapter.BaseViewHolder
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.databinding.HeaderDateBinding
import org.kiwix.kiwixmobile.databinding.ItemLanguageBinding
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
sealed class LanguageListViewHolder<in T : LanguageListItem>(override val containerView: View) :
BaseViewHolder<T>(containerView) {
class HeaderViewHolder(private val headerDateBinding: HeaderDateBinding) :
LanguageListViewHolder<HeaderItem>(headerDateBinding.root) {
override fun bind(item: HeaderItem) {
headerDateBinding.headerDate.setText(
if (item.id == HeaderItem.SELECTED) {
R.string.your_languages
} else {
R.string.other_languages
}
)
}
}
class LanguageViewHolder(
private val itemLanguageBinding: ItemLanguageBinding,
val clickAction: (LanguageItem) -> Unit
) : LanguageListViewHolder<LanguageItem>(itemLanguageBinding.root) {
override fun bind(item: LanguageItem) {
val language = item.language
itemLanguageBinding.itemLanguageName.text = language.language
itemLanguageBinding.itemLanguageLocalizedName.text = language.languageLocalized
itemLanguageBinding.itemLanguageBooksCount.text =
containerView.context
.getString(R.string.books_count, language.occurencesOfLanguage)
itemLanguageBinding.itemLanguageCheckbox.apply {
setToolTipWithContentDescription(
containerView.context.getString(R.string.select_language_content_description)
)
isChecked = language.active
}
itemLanguageBinding.itemLanguageClickableArea.apply {
setToolTipWithContentDescription(
containerView.context.getString(R.string.select_language_content_description)
)
setOnClickListener { clickAction(item) }
}
}
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.language.composables
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TextFieldDefaults.indicatorLine
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
@Suppress("all")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBarTextField(
value: String,
hint: String,
testTag: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
) {
val interactionSource = remember(::MutableInteractionSource)
val textStyle = LocalTextStyle.current
val colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
focusedContainerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
errorContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
)
val focusRequester = FocusRequester()
SideEffect(focusRequester::requestFocus)
var textFieldValue by remember {
mutableStateOf(TextFieldValue(value, TextRange(value.length)))
}
textFieldValue = textFieldValue.copy(text = value)
CompositionLocalProvider(
LocalTextSelectionColors provides LocalTextSelectionColors.current
) {
BasicTextField(
modifier = Modifier
.testTag(testTag)
.width(200.dp)
.padding(start = 20.dp)
.indicatorLine(
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors
)
.focusRequester(focusRequester)
.semantics {
contentDescription = "searchField"
},
value = textFieldValue,
onValueChange = {
textFieldValue = it
onValueChange(it.text.replace("\n", ""))
},
textStyle = textStyle.copy(color = Color.White),
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = true,
decorationBox = { innerTextField ->
// places text field with placeholder and appropriate bottom padding
TextFieldDefaults.DecorationBox(
value = value,
innerTextField = innerTextField,
enabled = true,
singleLine = true,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
isError = false,
placeholder = {
Text(
text = hint,
color = Color.LightGray
)
},
colors = colors,
contentPadding = PaddingValues(bottom = 4.dp),
)
}
)
}
}

View File

@ -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.language.composables
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.kiwix.kiwixmobile.core.R
@Composable
fun HeaderText(item: LanguageListItem.HeaderItem) {
Text(
text = when (item.id) {
LanguageListItem.HeaderItem.SELECTED -> stringResource(R.string.your_languages)
LanguageListItem.HeaderItem.OTHER -> stringResource(R.string.other_languages)
else -> ""
},
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp),
fontSize = 16.sp,
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}

View File

@ -0,0 +1,95 @@
/*
* 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.language.composables
import android.content.Context
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Checkbox
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.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.dp
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
const val LANGUAGE_ITEM_CHECKBOX_TESTING_TAG = "languageItemCheckboxTestingTag"
@Composable
fun LanguageItemRow(
context: Context,
modifier: Modifier,
item: LanguageItem,
onCheckedChange: (LanguageItem) -> Unit
) {
val language = item.language
Row(
modifier = modifier
.fillMaxWidth()
.height(64.dp)
.semantics {
contentDescription = context.getString(R.string.select_language_content_description)
}
.clickable {
onCheckedChange(item)
},
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
modifier = Modifier
.padding(16.dp)
.semantics {
testTag = "$LANGUAGE_ITEM_CHECKBOX_TESTING_TAG${language.language}"
},
checked = language.active,
onCheckedChange = {
onCheckedChange(item)
}
)
Column {
Text(
text = language.language,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = language.languageLocalized,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondary
)
}
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.books_count, language.occurencesOfLanguage),
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondary
)
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.language.composables
import android.content.Context
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
@Composable
fun LanguageList(
context: Context,
listState: LazyListState,
viewItem: List<LanguageListItem>,
selectLanguageItem: (LanguageItem) -> Unit,
) {
LazyColumn(
state = listState
) {
items(
items = viewItem,
key = { item ->
when (item) {
is HeaderItem -> "header_${item.id}"
is LanguageItem -> "language_${item.language.id}"
}
}
) { item ->
when (item) {
is HeaderItem -> HeaderText(item)
is LanguageItem -> LanguageItemRow(
context = context,
modifier = Modifier
.animateItem(
fadeInSpec = tween(durationMillis = 250),
fadeOutSpec = tween(durationMillis = 100),
placementSpec = spring(
stiffness = Spring.StiffnessLow,
dampingRatio = Spring.DampingRatioLowBouncy
)
)
.fillMaxWidth()
.height(64.dp)
.semantics {
contentDescription =
context.getString(R.string.select_language_content_description)
}
.clickable {
selectLanguageItem(item)
},
item = item,
onCheckedChange = { selectLanguageItem(it) }
)
}
}
}
}

View File

@ -1,6 +1,6 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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
@ -16,7 +16,7 @@
*
*/
package org.kiwix.kiwixmobile.language.adapter
package org.kiwix.kiwixmobile.language.composables
import org.kiwix.kiwixmobile.core.zim_manager.Language

View File

@ -1,6 +1,6 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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
@ -15,13 +15,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.language.adapter
import org.kiwix.kiwixmobile.core.base.adapter.AdapterDelegate
import org.kiwix.kiwixmobile.core.base.adapter.BaseDelegateAdapter
package org.kiwix.kiwixmobile.language.composables
class LanguageAdapter(
vararg delegates: AdapterDelegate<LanguageListItem>
) : BaseDelegateAdapter<LanguageListItem>(*delegates) {
override fun getIdFor(item: LanguageListItem) = item.id
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun LoadingIndicator() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}

View File

@ -19,7 +19,7 @@
package org.kiwix.kiwixmobile.language.viewmodel
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
sealed class Action {
data class UpdateLanguages(val languages: List<Language>) : Action()

View File

@ -18,13 +18,13 @@
package org.kiwix.kiwixmobile.language.viewmodel
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select

View File

@ -19,9 +19,9 @@
package org.kiwix.kiwixmobile.language.viewmodel
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
sealed class State {
object Loading : State()

View File

@ -1,32 +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">
<include layout="@layout/layout_standard_app_bar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/language_recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/pref_language_title"
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"
android:clipToPadding="false"
tools:listitem="@layout/item_language" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/language_progressbar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/app_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -120,8 +120,7 @@
<fragment
android:id="@+id/languageFragment"
android:name="org.kiwix.kiwixmobile.language.LanguageFragment"
android:label="LanguageFragment"
tools:layout="@layout/activity_language" />
android:label="LanguageFragment" />
<fragment
android:id="@+id/zimHostFragment"
android:name="org.kiwix.kiwixmobile.webserver.ZimHostFragment"

View File

@ -1,76 +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.language.adapter
import android.view.ViewGroup
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.viewBinding
import org.kiwix.kiwixmobile.databinding.HeaderDateBinding
import org.kiwix.kiwixmobile.databinding.ItemLanguageBinding
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.HeaderDelegate
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.LanguageItemDelegate
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.HeaderViewHolder
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.LanguageViewHolder
class LanguageDelegateTest {
@Nested
inner class HeaderDelegateTests {
@Test
fun `class is header item`() {
assertThat(HeaderDelegate().itemClass).isEqualTo(HeaderItem::class.java)
}
@Test
fun `creates HeaderViewHolder`() {
val parent = mockk<ViewGroup>()
mockkObject(ViewGroupExtensions)
every { parent.viewBinding(HeaderDateBinding::inflate, false) } returns mockk()
every { parent.viewBinding(HeaderDateBinding::inflate, false).root } returns mockk()
assertThat(HeaderDelegate().createViewHolder(parent))
.isInstanceOf(HeaderViewHolder::class.java)
}
}
@Nested
inner class LanguageItemDelegateTests {
@Test
fun `class is lanuguage item`() {
assertThat(LanguageItemDelegate {}.itemClass).isEqualTo(LanguageItem::class.java)
}
@Test
fun `creates LanguageViewHolder`() {
val parent = mockk<ViewGroup>()
mockkObject(ViewGroupExtensions)
every { parent.viewBinding(ItemLanguageBinding::inflate, false) } returns mockk()
every { parent.viewBinding(ItemLanguageBinding::inflate, false).root } returns mockk()
val clickAction = mockk<(LanguageItem) -> Unit>()
assertThat(LanguageItemDelegate(clickAction).createViewHolder(parent))
.isInstanceOf(LanguageViewHolder::class.java)
}
}
}

View File

@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select

View File

@ -21,8 +21,8 @@ package org.kiwix.kiwixmobile.language.viewmodel
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.sharedFunctions.language