mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-08 06:42:21 -04:00
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:
parent
e939ff770d
commit
3e42aa3725
@ -19,6 +19,7 @@ package org.kiwix.kiwixmobile.language
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Instrumentation
|
import android.app.Instrumentation
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
@ -50,6 +51,9 @@ class LanguageFragmentTest {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val retryRule = RetryRule()
|
val retryRule = RetryRule()
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
private val permissions =
|
private val permissions =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
@ -61,9 +65,7 @@ class LanguageFragmentTest {
|
|||||||
var permissionRules: GrantPermissionRule =
|
var permissionRules: GrantPermissionRule =
|
||||||
GrantPermissionRule.grant(*permissions)
|
GrantPermissionRule.grant(*permissions)
|
||||||
|
|
||||||
private val instrumentation: Instrumentation by lazy {
|
private val instrumentation: Instrumentation by lazy(InstrumentationRegistry::getInstrumentation)
|
||||||
InstrumentationRegistry.getInstrumentation()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
AccessibilityChecks.enable().setRunChecksFromRootView(true)
|
AccessibilityChecks.enable().setRunChecksFromRootView(true)
|
||||||
@ -108,45 +110,81 @@ class LanguageFragmentTest {
|
|||||||
|
|
||||||
// search and de-select if german language already selected
|
// search and de-select if german language already selected
|
||||||
clickOnLanguageIcon()
|
clickOnLanguageIcon()
|
||||||
clickOnLanguageSearchIcon()
|
clickOnLanguageSearchIcon(composeTestRule)
|
||||||
searchLanguage("german")
|
searchLanguage(
|
||||||
deSelectLanguageIfAlreadySelected()
|
composeTestRule = composeTestRule,
|
||||||
clickOnSaveLanguageIcon()
|
searchLanguage = "german"
|
||||||
|
)
|
||||||
|
deSelectLanguageIfAlreadySelected(
|
||||||
|
composeTestRule = composeTestRule,
|
||||||
|
matchLanguage = "German"
|
||||||
|
)
|
||||||
|
clickOnSaveLanguageIcon(composeTestRule)
|
||||||
|
|
||||||
// search and de-select if italian language already selected
|
// search and de-select if italian language already selected
|
||||||
clickOnLanguageIcon()
|
clickOnLanguageIcon()
|
||||||
clickOnLanguageSearchIcon()
|
clickOnLanguageSearchIcon(composeTestRule)
|
||||||
searchLanguage("italiano")
|
searchLanguage(
|
||||||
deSelectLanguageIfAlreadySelected()
|
composeTestRule = composeTestRule,
|
||||||
clickOnSaveLanguageIcon()
|
searchLanguage = "italiano"
|
||||||
|
)
|
||||||
|
deSelectLanguageIfAlreadySelected(
|
||||||
|
composeTestRule = composeTestRule,
|
||||||
|
matchLanguage = "Italian"
|
||||||
|
)
|
||||||
|
clickOnSaveLanguageIcon(composeTestRule)
|
||||||
|
|
||||||
// Search and save language for german
|
// Search and save language for german
|
||||||
clickOnLanguageIcon()
|
clickOnLanguageIcon()
|
||||||
clickOnLanguageSearchIcon()
|
clickOnLanguageSearchIcon(composeTestRule)
|
||||||
searchLanguage("german")
|
searchLanguage(
|
||||||
selectLanguage("German")
|
composeTestRule = composeTestRule,
|
||||||
clickOnSaveLanguageIcon()
|
searchLanguage = "german"
|
||||||
|
)
|
||||||
|
selectLanguage(
|
||||||
|
composeTestRule = composeTestRule,
|
||||||
|
matchLanguage = "German"
|
||||||
|
)
|
||||||
|
clickOnSaveLanguageIcon(composeTestRule)
|
||||||
|
|
||||||
// Search and save language for italian
|
// Search and save language for italian
|
||||||
clickOnLanguageIcon()
|
clickOnLanguageIcon()
|
||||||
clickOnLanguageSearchIcon()
|
clickOnLanguageSearchIcon(composeTestRule)
|
||||||
searchLanguage("italiano")
|
searchLanguage(
|
||||||
selectLanguage("Italian")
|
composeTestRule = composeTestRule,
|
||||||
clickOnSaveLanguageIcon()
|
searchLanguage = "italiano"
|
||||||
|
)
|
||||||
|
selectLanguage(
|
||||||
|
composeTestRule = composeTestRule,
|
||||||
|
matchLanguage = "Italian"
|
||||||
|
)
|
||||||
|
clickOnSaveLanguageIcon(composeTestRule)
|
||||||
|
|
||||||
// verify is german language selected
|
// verify is german language selected
|
||||||
clickOnLanguageIcon()
|
clickOnLanguageIcon()
|
||||||
clickOnLanguageSearchIcon()
|
clickOnLanguageSearchIcon(composeTestRule)
|
||||||
searchLanguage("german")
|
searchLanguage(
|
||||||
checkIsLanguageSelected()
|
composeTestRule = composeTestRule,
|
||||||
clickOnSaveLanguageIcon()
|
searchLanguage = "german"
|
||||||
|
)
|
||||||
|
checkIsLanguageSelected(
|
||||||
|
composeTestRule = composeTestRule,
|
||||||
|
matchLanguage = "German"
|
||||||
|
)
|
||||||
|
clickOnSaveLanguageIcon(composeTestRule)
|
||||||
|
|
||||||
// verify is italian language selected
|
// verify is italian language selected
|
||||||
clickOnLanguageIcon()
|
clickOnLanguageIcon()
|
||||||
clickOnLanguageSearchIcon()
|
clickOnLanguageSearchIcon(composeTestRule)
|
||||||
searchLanguage("italiano")
|
searchLanguage(
|
||||||
checkIsLanguageSelected()
|
composeTestRule = composeTestRule,
|
||||||
clickOnSaveLanguageIcon()
|
searchLanguage = "italiano"
|
||||||
|
)
|
||||||
|
checkIsLanguageSelected(
|
||||||
|
composeTestRule = composeTestRule,
|
||||||
|
matchLanguage = "Italian"
|
||||||
|
)
|
||||||
|
clickOnSaveLanguageIcon(composeTestRule)
|
||||||
}
|
}
|
||||||
LeakAssertions.assertNoLeaks()
|
LeakAssertions.assertNoLeaks()
|
||||||
}
|
}
|
||||||
|
@ -18,27 +18,28 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.language
|
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.Espresso.onView
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
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.isDisplayed
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
|
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import applyWithViewHierarchyPrinting
|
import applyWithViewHierarchyPrinting
|
||||||
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
|
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
|
||||||
import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh
|
import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh
|
||||||
import junit.framework.AssertionFailedError
|
|
||||||
import org.kiwix.kiwixmobile.BaseRobot
|
import org.kiwix.kiwixmobile.BaseRobot
|
||||||
import org.kiwix.kiwixmobile.Findable.StringId.TextId
|
import org.kiwix.kiwixmobile.Findable.StringId.TextId
|
||||||
import org.kiwix.kiwixmobile.Findable.Text
|
|
||||||
import org.kiwix.kiwixmobile.Findable.ViewId
|
import org.kiwix.kiwixmobile.Findable.ViewId
|
||||||
import org.kiwix.kiwixmobile.R
|
import org.kiwix.kiwixmobile.R
|
||||||
import org.kiwix.kiwixmobile.core.R.string
|
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
|
||||||
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
|
||||||
import org.kiwix.kiwixmobile.utils.RecyclerViewMatcher
|
|
||||||
|
|
||||||
fun language(func: LanguageRobot.() -> Unit) = LanguageRobot().applyWithViewHierarchyPrinting(func)
|
fun language(func: LanguageRobot.() -> Unit) = LanguageRobot().applyWithViewHierarchyPrinting(func)
|
||||||
|
|
||||||
@ -82,57 +83,57 @@ class LanguageRobot : BaseRobot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clickOnLanguageIcon() {
|
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())
|
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
|
||||||
clickOn(ViewId(R.id.select_language))
|
clickOn(ViewId(R.id.select_language))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickOnLanguageSearchIcon() {
|
fun clickOnSaveLanguageIcon(composeTestRule: ComposeContentTestRule) {
|
||||||
testFlakyView({ onView(withId(R.id.menu_language_search)).perform(click()) })
|
composeTestRule.onNodeWithTag(SAVE_ICON_TESTING_TAG)
|
||||||
|
.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchLanguage(searchLanguage: String) {
|
fun clickOnLanguageSearchIcon(composeTestRule: ComposeContentTestRule) {
|
||||||
isVisible(ViewId(androidx.appcompat.R.id.search_src_text)).text = searchLanguage
|
composeTestRule.onNodeWithTag(SEARCH_ICON_TESTING_TAG).performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectLanguage(matchLanguage: String) {
|
fun searchLanguage(
|
||||||
testFlakyView({ clickOn(Text(matchLanguage)) })
|
composeTestRule: ComposeContentTestRule,
|
||||||
|
searchLanguage: String
|
||||||
|
) {
|
||||||
|
val searchField = composeTestRule.onNodeWithTag(SEARCH_FIELD_TESTING_TAG)
|
||||||
|
searchField.performTextInput(text = searchLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickOnSaveLanguageIcon() {
|
// error prone
|
||||||
clickOn(ViewId(R.id.menu_language_save))
|
fun deSelectLanguageIfAlreadySelected(
|
||||||
}
|
composeTestRule: ComposeContentTestRule,
|
||||||
|
matchLanguage: String
|
||||||
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.
|
|
||||||
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
|
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
|
||||||
try {
|
try {
|
||||||
onView(
|
composeTestRule.onNodeWithTag("$LANGUAGE_ITEM_CHECKBOX_TESTING_TAG$matchLanguage")
|
||||||
RecyclerViewMatcher(R.id.language_recycler_view).atPositionOnView(
|
.assertIsOff()
|
||||||
1,
|
} catch (noMatchingNodeException: AssertionError) {
|
||||||
R.id.item_language_checkbox
|
composeTestRule.onNodeWithTag("$LANGUAGE_ITEM_CHECKBOX_TESTING_TAG$matchLanguage")
|
||||||
)
|
.performClick()
|
||||||
).check(matches(isNotChecked()))
|
|
||||||
} catch (assertionError: AssertionFailedError) {
|
|
||||||
onView(
|
|
||||||
RecyclerViewMatcher(R.id.language_recycler_view).atPositionOnView(
|
|
||||||
1,
|
|
||||||
R.id.item_language_checkbox
|
|
||||||
)
|
|
||||||
).perform(click())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,46 +20,18 @@ package org.kiwix.kiwixmobile.language
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
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.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
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.cachedComponent
|
||||||
import org.kiwix.kiwixmobile.core.base.BaseActivity
|
import org.kiwix.kiwixmobile.core.base.BaseActivity
|
||||||
import org.kiwix.kiwixmobile.core.base.BaseFragment
|
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.extensions.viewModel
|
||||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
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
|
||||||
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.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
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LanguageFragment : BaseFragment() {
|
class LanguageFragment : BaseFragment() {
|
||||||
@ -67,16 +39,8 @@ class LanguageFragment : BaseFragment() {
|
|||||||
|
|
||||||
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
|
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||||
|
|
||||||
|
private lateinit var composeView: ComposeView
|
||||||
private val compositeDisposable = CompositeDisposable()
|
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) {
|
override fun inject(baseActivity: BaseActivity) {
|
||||||
baseActivity.cachedComponent.inject(this)
|
baseActivity.cachedComponent.inject(this)
|
||||||
}
|
}
|
||||||
@ -84,24 +48,15 @@ class LanguageFragment : BaseFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val activity = requireActivity() as CoreMainActivity
|
val activity = requireActivity() as CoreMainActivity
|
||||||
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
|
composeView.setContent {
|
||||||
activity.setSupportActionBar(toolbar)
|
LanguageScreen(
|
||||||
|
viewModelState = languageViewModel.state,
|
||||||
activity.supportActionBar?.let {
|
onNavigationClick = activity.onBackPressedDispatcher::onBackPressed,
|
||||||
it.setDisplayHomeAsUpEnabled(true)
|
selectLanguageItem = { languageViewModel.actions.offer(Action.Select(it)) },
|
||||||
it.setHomeAsUpIndicator(drawable.ic_clear_white_24dp)
|
filterText = { languageViewModel.actions.offer(Action.Filter(it)) },
|
||||||
it.setTitle(string.select_languages)
|
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(
|
compositeDisposable.add(
|
||||||
languageViewModel.effects.subscribe(
|
languageViewModel.effects.subscribe(
|
||||||
{
|
{
|
||||||
@ -116,63 +71,14 @@ class LanguageFragment : BaseFragment() {
|
|||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
setupMenu()
|
return ComposeView(requireContext()).also {
|
||||||
activityLanguageBinding = ActivityLanguageBinding.inflate(inflater, container, false)
|
composeView = it
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
activityLanguageBinding?.root?.removeAllViews()
|
|
||||||
searchView?.setOnQueryTextListener(null)
|
|
||||||
searchView = null
|
|
||||||
activityLanguageBinding?.languageRecyclerView?.adapter = null
|
|
||||||
activityLanguageBinding = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Kiwix Android
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* 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
|
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Kiwix Android
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* 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/>.
|
* 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
|
package org.kiwix.kiwixmobile.language.composables
|
||||||
import org.kiwix.kiwixmobile.core.base.adapter.BaseDelegateAdapter
|
|
||||||
|
|
||||||
class LanguageAdapter(
|
import androidx.compose.foundation.layout.Box
|
||||||
vararg delegates: AdapterDelegate<LanguageListItem>
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
) : BaseDelegateAdapter<LanguageListItem>(*delegates) {
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
override fun getIdFor(item: LanguageListItem) = item.id
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
@ -19,7 +19,7 @@
|
|||||||
package org.kiwix.kiwixmobile.language.viewmodel
|
package org.kiwix.kiwixmobile.language.viewmodel
|
||||||
|
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
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 {
|
sealed class Action {
|
||||||
data class UpdateLanguages(val languages: List<Language>) : Action()
|
data class UpdateLanguages(val languages: List<Language>) : Action()
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.language.viewmodel
|
package org.kiwix.kiwixmobile.language.viewmodel
|
||||||
|
|
||||||
|
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import org.kiwix.kiwixmobile.core.base.SideEffect
|
import org.kiwix.kiwixmobile.core.base.SideEffect
|
||||||
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
|
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.Filter
|
||||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
|
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
|
||||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
|
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
package org.kiwix.kiwixmobile.language.viewmodel
|
package org.kiwix.kiwixmobile.language.viewmodel
|
||||||
|
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
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.adapter.LanguageListItem.HeaderItem
|
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
|
||||||
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
|
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
|
||||||
|
|
||||||
sealed class State {
|
sealed class State {
|
||||||
object Loading : State()
|
object Loading : State()
|
||||||
|
@ -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>
|
|
@ -120,8 +120,7 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/languageFragment"
|
android:id="@+id/languageFragment"
|
||||||
android:name="org.kiwix.kiwixmobile.language.LanguageFragment"
|
android:name="org.kiwix.kiwixmobile.language.LanguageFragment"
|
||||||
android:label="LanguageFragment"
|
android:label="LanguageFragment" />
|
||||||
tools:layout="@layout/activity_language" />
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/zimHostFragment"
|
android:id="@+id/zimHostFragment"
|
||||||
android:name="org.kiwix.kiwixmobile.webserver.ZimHostFragment"
|
android:name="org.kiwix.kiwixmobile.webserver.ZimHostFragment"
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
|
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
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.Filter
|
||||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
|
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
|
||||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
|
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
|
||||||
|
@ -21,8 +21,8 @@ package org.kiwix.kiwixmobile.language.viewmodel
|
|||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
|
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
|
||||||
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
|
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
|
||||||
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
|
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
|
||||||
import org.kiwix.sharedFunctions.language
|
import org.kiwix.sharedFunctions.language
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user