diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt index 20a8d70b8..39271a9b8 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt @@ -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() } diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageRobot.kt index 6ef70a157..b20f9228c 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageRobot.kt @@ -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() + } } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageFragment.kt index 7731c05ef..c67232bdc 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageFragment.kt @@ -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 } } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt new file mode 100644 index 000000000..3caf93403 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt @@ -0,0 +1,273 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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, + 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().apply { value = Content(languages) }, + selectLanguageItem = {}, + filterText = {}, + saveLanguages = {}, + onNavigationClick = {} + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageDelegate.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageDelegate.kt deleted file mode 100644 index f1ef53c29..000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageDelegate.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * 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 . - * - */ - -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> : - AbsDelegateAdapter { - class HeaderDelegate : LanguageDelegate() { - 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() { - override val itemClass = LanguageItem::class.java - - override fun createViewHolder(parent: ViewGroup) = - LanguageViewHolder( - parent.viewBinding(ItemLanguageBinding::inflate, false), - clickAction - ) - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageListViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageListViewHolder.kt deleted file mode 100644 index dd560e3b7..000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageListViewHolder.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * 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 . - * - */ - -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(override val containerView: View) : - BaseViewHolder(containerView) { - class HeaderViewHolder(private val headerDateBinding: HeaderDateBinding) : - LanguageListViewHolder(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(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) } - } - } - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarTextField.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarTextField.kt new file mode 100644 index 000000000..1ae29826d --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarTextField.kt @@ -0,0 +1,140 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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), + ) + } + ) + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/HeaderText.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/HeaderText.kt new file mode 100644 index 000000000..81232e2cc --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/HeaderText.kt @@ -0,0 +1,45 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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 + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt new file mode 100644 index 000000000..c640d4648 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt @@ -0,0 +1,95 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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 + ) + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageList.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageList.kt new file mode 100644 index 000000000..6226c4542 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageList.kt @@ -0,0 +1,87 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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, + 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) } + ) + } + } + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageListItem.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageListItem.kt similarity index 91% rename from app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageListItem.kt rename to app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageListItem.kt index 988a1629c..a0223c96c 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageListItem.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageListItem.kt @@ -1,6 +1,6 @@ /* * Kiwix Android - * Copyright (c) 2019 Kiwix + * Copyright (c) 2025 Kiwix * 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 diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt similarity index 55% rename from app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageAdapter.kt rename to app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt index 1d809a1b2..495f60cb3 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/adapter/LanguageAdapter.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt @@ -1,6 +1,6 @@ /* * Kiwix Android - * Copyright (c) 2019 Kiwix + * Copyright (c) 2025 Kiwix * 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 . * */ -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 -) : BaseDelegateAdapter(*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() + } } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/Action.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/Action.kt index bbf14702c..78fe3deef 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/Action.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/Action.kt @@ -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) : Action() diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt index c89ede0f7..2c115042f 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt @@ -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 diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/State.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/State.kt index cb8769ed4..45ca13783 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/State.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/State.kt @@ -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() diff --git a/app/src/main/res/layout/activity_language.xml b/app/src/main/res/layout/activity_language.xml deleted file mode 100644 index d1bc2a370..000000000 --- a/app/src/main/res/layout/activity_language.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/navigation/kiwix_nav_graph.xml b/app/src/main/res/navigation/kiwix_nav_graph.xml index 13be224cf..8bf77c014 100644 --- a/app/src/main/res/navigation/kiwix_nav_graph.xml +++ b/app/src/main/res/navigation/kiwix_nav_graph.xml @@ -120,8 +120,7 @@ + android:label="LanguageFragment" /> - * 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 . - * - */ - -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() - 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() - 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) - } - } -} diff --git a/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt index 045c07d88..176e62d08 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt @@ -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 diff --git a/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/StateTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/StateTest.kt index d1a8ae59b..79d83810d 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/StateTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/StateTest.kt @@ -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