refactor VocabularyMenu and FAB components to support dynamic text visibility based on scroll state and update Zipf frequency UI in VocabularyCard
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package eu.gaudian.translator.view.composable
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
@@ -55,7 +56,8 @@ data class FabMenuItem(
|
||||
fun AppFabMenu(
|
||||
items: List<FabMenuItem>,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null
|
||||
title: String? = null,
|
||||
showFabText: Boolean = true
|
||||
) {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -111,14 +113,16 @@ fun AppFabMenu(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateContentSize()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = AppIcons.Add,
|
||||
contentDescription = stringResource(R.string.cd_toggle_menu),
|
||||
modifier = Modifier.rotate(iconRotationAngle)
|
||||
)
|
||||
if (title != null) {
|
||||
if (title != null && showFabText) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
|
||||
@@ -22,6 +22,7 @@ import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
@Composable
|
||||
fun VocabularyMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
showFabText : Boolean = true
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
@@ -48,7 +49,7 @@ fun VocabularyMenu(
|
||||
)
|
||||
)
|
||||
|
||||
AppFabMenu(items = menuItems, modifier = modifier, title = stringResource(R.string.label_add_vocabulary))
|
||||
AppFabMenu(items = menuItems, modifier = modifier, title = stringResource(R.string.label_add_vocabulary), showFabText = showFabText)
|
||||
|
||||
if (showAddVocabularyDialog) {
|
||||
AddVocabularyDialog(
|
||||
|
||||
@@ -88,6 +88,7 @@ fun DashboardContent(
|
||||
onNavigateToCategoryDetail: (Int) -> Unit,
|
||||
onNavigateToCategoryList: () -> Unit,
|
||||
onShowWordPairExerciseDialog: () -> Unit,
|
||||
onScroll: (Boolean) -> Unit = {},
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
@@ -167,6 +168,11 @@ fun DashboardContent(
|
||||
lazyListState.firstVisibleItemScrollOffset
|
||||
)
|
||||
}
|
||||
|
||||
// Detect scroll and notify parent
|
||||
LaunchedEffect(lazyListState.isScrollInProgress) {
|
||||
onScroll(lazyListState.isScrollInProgress)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
settingsViewModel.saveDashboardScrollState(
|
||||
|
||||
@@ -4,6 +4,7 @@ package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -112,6 +113,9 @@ fun MainVocabularyScreen(
|
||||
var wpTrainingMode by remember { mutableStateOf(false) }
|
||||
var wpDueTodayOnly by remember { mutableStateOf(false) }
|
||||
|
||||
var isScrolling by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
if (showCustomExerciseDialog) {
|
||||
StartExerciseDialog(
|
||||
onDismiss = { showCustomExerciseDialog = false },
|
||||
@@ -293,6 +297,8 @@ fun MainVocabularyScreen(
|
||||
VocabularyTab.entries.find { it.route == currentRoute } ?: VocabularyTab.Dashboard
|
||||
}
|
||||
|
||||
val showFabText = selectedTab == VocabularyTab.Dashboard && !isScrolling
|
||||
|
||||
val repoEmpty =
|
||||
vocabularyViewModel.vocabularyItems.collectAsState(initial = emptyList()).value.isEmpty()
|
||||
|
||||
@@ -333,7 +339,8 @@ fun MainVocabularyScreen(
|
||||
onNavigateToCategoryList = {
|
||||
navController.navigate("category_list_screen")
|
||||
},
|
||||
onShowWordPairExerciseDialog = { showWordPairExerciseDialog = true }
|
||||
onShowWordPairExerciseDialog = { showWordPairExerciseDialog = true },
|
||||
onScroll = { isScrolling = it }
|
||||
)
|
||||
}
|
||||
composable(VocabularyTab.Statistics.route) {
|
||||
@@ -390,7 +397,7 @@ fun MainVocabularyScreen(
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
VocabularyMenu(modifier = Modifier.onSizeChanged { menuHeightPx = it.height })
|
||||
VocabularyMenu(modifier = Modifier.onSizeChanged { menuHeightPx = it.height }, showFabText = showFabText)
|
||||
}
|
||||
|
||||
// Place the FAB separately and animate its bottom padding based on the menu height
|
||||
@@ -405,16 +412,19 @@ fun MainVocabularyScreen(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateContentSize()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = AppIcons.Quiz,
|
||||
contentDescription = null
|
||||
)
|
||||
if(showFabText) {
|
||||
Text(
|
||||
text = stringResource(R.string.label_start_exercise),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
@@ -25,6 +26,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
@@ -43,7 +45,9 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -454,28 +458,52 @@ private fun FrequencyPill(zipfFrequency: Float?) {
|
||||
if (zipfFrequency == null) return
|
||||
|
||||
val semanticColors = MaterialTheme.semanticColors
|
||||
|
||||
// Determine the label and color based on the Zipf value
|
||||
val (label, color) = when {
|
||||
zipfFrequency <= 3f -> Pair(stringResource(R.string.text_rare), semanticColors.wrongContainer)
|
||||
zipfFrequency <= 4f -> Pair(stringResource(R.string.text_infrequent), semanticColors.stageGradient2)
|
||||
zipfFrequency <= 4.5f -> Pair(stringResource(R.string.text_uncommon), semanticColors.stageGradient3)
|
||||
zipfFrequency <= 2.0f -> Pair(stringResource(R.string.text_rare), semanticColors.wrongContainer)
|
||||
zipfFrequency <= 3.0f -> Pair(stringResource(R.string.text_infrequent), semanticColors.stageGradient2)
|
||||
zipfFrequency <= 4.0f -> Pair(stringResource(R.string.text_uncommon), semanticColors.stageGradient3)
|
||||
zipfFrequency <= 5.5f -> Pair(stringResource(R.string.text_common), semanticColors.stageGradient4)
|
||||
zipfFrequency <= 6.5f -> Pair(stringResource(R.string.text_frequent), semanticColors.stageGradient5)
|
||||
zipfFrequency <= 7.5f -> Pair(stringResource(R.string.text_very_frequent), semanticColors.stageGradient6)
|
||||
zipfFrequency <= 6.0f -> Pair(stringResource(R.string.text_frequent), semanticColors.stageGradient5)
|
||||
else -> Pair(stringResource(R.string.text_very_frequent), semanticColors.successContainer)
|
||||
}
|
||||
|
||||
// Normalize progress: Zipf 1.0 to 8.0 mapped to 0f to 1f
|
||||
val progress = ((zipfFrequency - 1f) / (8f - 1f)).coerceIn(0f, 1f)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.width(80.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = color.copy(alpha = 0.5f),
|
||||
modifier = Modifier.padding(horizontal = 4.dp)
|
||||
color = color.copy(alpha = 0.2f),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
// The "Slider" representation
|
||||
LinearProgressIndicator(
|
||||
progress = { progress },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
.clip(CircleShape),
|
||||
color = color,
|
||||
trackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f),
|
||||
strokeCap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
Reference in New Issue
Block a user