feat: language fragment to jetpack compose

- added inner padding to scaffolding to account for topAppBar.
- moved AppBarTextField to ui/components so it can be re-used.
- shifted hardcoded values to ComposeDimens.
- Added ContentLoadingProgressBar modified it to support
Indeterminate mode
- further decoupling of composable functions
This commit is contained in:
jaskaran 2025-03-21 13:11:09 +05:30 committed by MohitMaliFtechiz
parent 583cc7241e
commit 695446e291
8 changed files with 144 additions and 175 deletions

View File

@ -18,14 +18,13 @@
package org.kiwix.kiwixmobile.language
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -39,12 +38,10 @@ import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.extensions.viewModel
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.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.language.composables.AppBarNavigationIcon
import org.kiwix.kiwixmobile.language.composables.AppBarTextField
import org.kiwix.kiwixmobile.language.viewmodel.Action
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import javax.inject.Inject
@ -64,7 +61,6 @@ class LanguageFragment : BaseFragment() {
baseActivity.cachedComponent.inject(this)
}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as CoreMainActivity
@ -79,12 +75,33 @@ class LanguageFragment : BaseFragment() {
}
KiwixTheme {
Scaffold(topBar = {
KiwixAppBar(
titleId = R.string.select_languages,
navigationIcon = {
AppBarNavigationIcon(
LanguageScreen(
searchText = searchText,
isSearchActive = isSearchActive,
appBarTextFieldTestTag = SEARCH_FIELD_TESTING_TAG,
languageViewModel = languageViewModel,
actionMenuItemList = appBarActionMenuList(
searchText = searchText,
isSearchActive = isSearchActive,
onSearchClick = { isSearchActive = true },
onClearClick = { resetSearchState() },
onSaveClick = {
languageViewModel.actions.offer(Action.SaveAll)
}
),
onAppBarValueChange = {
searchText = it
languageViewModel.actions.offer(Action.Filter(it))
},
content = {
NavigationIcon(
iconItem = if (isSearchActive) {
IconItem.Vector(Icons.AutoMirrored.Filled.ArrowBack)
} else {
IconItem.Drawable(
R.drawable.ic_close_white_24dp
)
},
onClick = {
if (isSearchActive) {
isSearchActive = false
@ -94,40 +111,9 @@ class LanguageFragment : BaseFragment() {
}
}
)
},
actionMenuItems = appBarActionMenuList(
searchText = searchText,
isSearchActive = isSearchActive,
onSearchClick = {
isSearchActive = true
},
onClearClick = {
resetSearchState()
},
onSaveClick = {
languageViewModel.actions.offer(Action.SaveAll)
}
),
searchBar = if (isSearchActive) {
{
AppBarTextField(
value = searchText,
onValueChange = {
searchText = it
languageViewModel.actions.offer(Action.Filter(it))
}
)
}
} else {
null
}
)
}) {
LanguageScreen(
languageViewModel = languageViewModel
)
}
}
}
compositeAdd(activity)
}

View File

@ -18,22 +18,31 @@
package org.kiwix.kiwixmobile.language
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
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.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.ui.components.AppBarTextField
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.language.composables.LanguageList
import org.kiwix.kiwixmobile.language.composables.LoadingIndicator
import org.kiwix.kiwixmobile.language.viewmodel.Action
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import org.kiwix.kiwixmobile.language.viewmodel.State
@ -41,18 +50,49 @@ import org.kiwix.kiwixmobile.language.viewmodel.State.Content
@Composable
fun LanguageScreen(
languageViewModel: LanguageViewModel
searchText: String,
isSearchActive: Boolean,
languageViewModel: LanguageViewModel,
actionMenuItemList: List<ActionMenuItem>,
onAppBarValueChange: (String) -> Unit,
appBarTextFieldTestTag: String,
content: @Composable() () -> Unit,
) {
val state by languageViewModel.state.observeAsState(State.Loading)
val context = LocalContext.current
val listState: LazyListState = rememberLazyListState()
val context = LocalContext.current
Column(modifier = Modifier.fillMaxSize()) {
// spacer to account for top app bar
Spacer(modifier = Modifier.height(56.dp))
Scaffold(topBar = {
KiwixAppBar(
titleId = R.string.select_languages,
navigationIcon = content,
actionMenuItems = actionMenuItemList,
searchBar = if (isSearchActive) {
{
AppBarTextField(
value = searchText,
testTag = appBarTextFieldTestTag,
onValueChange = onAppBarValueChange
)
}
} else {
null
}
)
}) { innerPadding ->
Column(
modifier = Modifier.fillMaxSize()
// setting bottom padding to zero to avoid accounting for Bottom bar
.padding(
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(LocalLayoutDirection.current),
end = innerPadding.calculateEndPadding(LocalLayoutDirection.current),
bottom = 0.dp
)
) {
when (state) {
State.Loading, State.Saving -> {
LoadingIndicator()
LoadingScreen()
}
is Content -> {
@ -66,6 +106,7 @@ fun LanguageScreen(
}
}
}
LanguageList(
context = context,
listState = listState,
@ -78,3 +119,14 @@ fun LanguageScreen(
}
}
}
}
@Composable
fun LoadingScreen() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
ContentLoadingProgressBar()
}
}

View File

@ -1,43 +0,0 @@
/*
* 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.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
import org.kiwix.kiwixmobile.core.ui.models.IconItem
@Composable
fun AppBarNavigationIcon(
isSearchActive: Boolean,
onClick: () -> Unit
) {
NavigationIcon(
iconItem = if (isSearchActive) {
IconItem.Vector(Icons.AutoMirrored.Filled.ArrowBack)
} else {
IconItem.Drawable(
R.drawable.ic_close_white_24dp
)
},
onClick = onClick
)
}

View File

@ -24,9 +24,8 @@ 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
import org.kiwix.kiwixmobile.core.utils.ComposeDimens
@Composable
fun HeaderText(
@ -40,8 +39,8 @@ fun HeaderText(
else -> ""
},
modifier = modifier
.padding(horizontal = 16.dp, vertical = 8.dp),
fontSize = 16.sp,
.padding(horizontal = ComposeDimens.SIXTEEN_DP, vertical = ComposeDimens.EIGHT_DP),
fontSize = ComposeDimens.FOURTEEN_SP,
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)

View File

@ -38,6 +38,7 @@ 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.core.utils.ComposeDimens
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
const val LANGUAGE_ITEM_CHECKBOX_TESTING_TAG = "languageItemCheckboxTestingTag"
@ -87,7 +88,7 @@ fun LanguageItemRow(
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.books_count, language.occurencesOfLanguage),
modifier = Modifier.padding(16.dp),
modifier = Modifier.padding(ComposeDimens.SIXTEEN_DP),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondary
)

View File

@ -1,36 +0,0 @@
/*
* 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.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun LoadingIndicator() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}

View File

@ -16,7 +16,7 @@
*
*/
package org.kiwix.kiwixmobile.language.composables
package org.kiwix.kiwixmobile.core.ui.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
@ -48,12 +48,12 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.language.SEARCH_FIELD_TESTING_TAG
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBarTextField(
value: String,
testTag: String = "",
onValueChange: (String) -> Unit
) {
val interactionSource = remember(::MutableInteractionSource)
@ -78,7 +78,7 @@ fun AppBarTextField(
) {
BasicTextField(
modifier = Modifier
.testTag(SEARCH_FIELD_TESTING_TAG)
.testTag(testTag)
.width(200.dp)
.padding(start = 20.dp)
.focusRequester(focusRequester),

View File

@ -42,12 +42,22 @@ fun ContentLoadingProgressBar(
) {
when (progressBarStyle) {
ProgressBarStyle.CIRCLE -> {
if (progress == ZERO) {
// Indeterminate mode - will spin continuously
CircularProgressIndicator(
modifier = modifier,
color = progressBarColor
)
} else {
// Determinate mode - shows specific progress
CircularProgressIndicator(
modifier = modifier,
progress = { progress.toFloat() / HUNDERED },
color = progressBarColor,
trackColor = progressBarTrackColor
)
}
}
ProgressBarStyle.HORIZONTAL -> {
LinearProgressIndicator(