mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-22 03:54:18 -04:00
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:
parent
583cc7241e
commit
695446e291
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
@ -77,4 +118,15 @@ fun LanguageScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadingScreen() {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ContentLoadingProgressBar()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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),
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user