From 695446e291c748ab6762311e1408717ae99f53c3 Mon Sep 17 00:00:00 2001 From: jaskaran Date: Fri, 21 Mar 2025 13:11:09 +0530 Subject: [PATCH] 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 --- .../kiwixmobile/language/LanguageFragment.kt | 92 ++++++-------- .../kiwixmobile/language/LanguageScreen.kt | 112 +++++++++++++----- .../composables/AppBarNavigationIcon.kt | 43 ------- .../language/composables/HeaderText.kt | 7 +- .../language/composables/LanguageItemRow.kt | 3 +- .../language/composables/LoadingIndicator.kt | 36 ------ .../core/ui/components}/AppBarTextField.kt | 6 +- .../components/ContentLoadingProgressBar.kt | 20 +++- 8 files changed, 144 insertions(+), 175 deletions(-) delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarNavigationIcon.kt delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt rename {app/src/main/java/org/kiwix/kiwixmobile/language/composables => core/src/main/java/org/kiwix/kiwixmobile/core/ui/components}/AppBarTextField.kt (96%) 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 d367d3321..01c9a0ebd 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageFragment.kt @@ -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,54 +75,44 @@ class LanguageFragment : BaseFragment() { } KiwixTheme { - Scaffold(topBar = { - KiwixAppBar( - titleId = R.string.select_languages, - navigationIcon = { - AppBarNavigationIcon( - isSearchActive = isSearchActive, - onClick = { - if (isSearchActive) { - isSearchActive = false - resetSearchState() - } else { - activity.onBackPressedDispatcher.onBackPressed() - } - } - ) - }, - 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( + 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) } - ) - }) { - LanguageScreen( - languageViewModel = languageViewModel - ) - } + ), + 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 + resetSearchState() + } else { + activity.onBackPressedDispatcher.onBackPressed() + } + } + ) + } + ) } } compositeAdd(activity) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt index 74ed1e03e..f4dc4322e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/LanguageScreen.kt @@ -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,40 +50,83 @@ import org.kiwix.kiwixmobile.language.viewmodel.State.Content @Composable fun LanguageScreen( - languageViewModel: LanguageViewModel + searchText: String, + isSearchActive: Boolean, + languageViewModel: LanguageViewModel, + actionMenuItemList: List, + 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)) - when (state) { - State.Loading, State.Saving -> { - LoadingIndicator() - } - - is Content -> { - val viewItem = (state as Content).viewItems - - LaunchedEffect(viewItem) { - snapshotFlow(listState::firstVisibleItemIndex) - .collect { - if (listState.firstVisibleItemIndex == 2) { - listState.animateScrollToItem(0) - } - } + Scaffold(topBar = { + KiwixAppBar( + titleId = R.string.select_languages, + navigationIcon = content, + actionMenuItems = actionMenuItemList, + searchBar = if (isSearchActive) { + { + AppBarTextField( + value = searchText, + testTag = appBarTextFieldTestTag, + onValueChange = onAppBarValueChange + ) } - LanguageList( - context = context, - listState = listState, - viewItem = viewItem, - selectLanguageItem = { languageItem -> - languageViewModel.actions.offer(Action.Select(languageItem)) - } + } 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 -> { + LoadingScreen() + } + + is Content -> { + val viewItem = (state as Content).viewItems + + LaunchedEffect(viewItem) { + snapshotFlow(listState::firstVisibleItemIndex) + .collect { + if (listState.firstVisibleItemIndex == 2) { + listState.animateScrollToItem(0) + } + } + } + + LanguageList( + context = context, + listState = listState, + viewItem = viewItem, + selectLanguageItem = { languageItem -> + languageViewModel.actions.offer(Action.Select(languageItem)) + } + ) + } } } } } + +@Composable +fun LoadingScreen() { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + ContentLoadingProgressBar() + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarNavigationIcon.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarNavigationIcon.kt deleted file mode 100644 index 49efe5878..000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarNavigationIcon.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.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 - ) -} 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 index cad50e36c..de30addb2 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/HeaderText.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/HeaderText.kt @@ -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 ) 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 index c640d4648..2343cc8c5 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt @@ -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 ) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt deleted file mode 100644 index 495f60cb3..000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LoadingIndicator.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.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/composables/AppBarTextField.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/AppBarTextField.kt similarity index 96% rename from app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarTextField.kt rename to core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/AppBarTextField.kt index 70821cdbd..44d7d84c2 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/AppBarTextField.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/AppBarTextField.kt @@ -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), diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/ContentLoadingProgressBar.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/ContentLoadingProgressBar.kt index 140a6997d..d79d0f93f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/ContentLoadingProgressBar.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/ContentLoadingProgressBar.kt @@ -42,11 +42,21 @@ fun ContentLoadingProgressBar( ) { when (progressBarStyle) { ProgressBarStyle.CIRCLE -> { - CircularProgressIndicator( - modifier = modifier, - color = progressBarColor, - trackColor = progressBarTrackColor - ) + 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 -> {