Implement a StageIndicator to visualize vocabulary learning progress and refine the VocabularyCard UI.

This commit is contained in:
jonasgaudian
2026-02-19 17:47:44 +01:00
parent 863920143d
commit d6a9ccf4e3
6 changed files with 372 additions and 77 deletions

View File

@@ -101,7 +101,7 @@ fun DailyReviewScreen(
state = listState, state = listState,
modifier = Modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(16.dp) contentPadding = PaddingValues(16.dp)
) { ) {
items( items(

View File

@@ -33,11 +33,13 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AddCircleOutline import androidx.compose.material.icons.filled.AddCircleOutline
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.LocalMall import androidx.compose.material.icons.filled.LocalMall
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tune import androidx.compose.material.icons.filled.Tune
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -58,6 +60,7 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -70,6 +73,8 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.VocabularyCategory import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.model.VocabularyItem import eu.gaudian.translator.model.VocabularyItem
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.insertBreakOpportunities import eu.gaudian.translator.view.composable.insertBreakOpportunities
@@ -127,22 +132,29 @@ fun SelectionTopBar(
Row( Row(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { // 1. Close Button
IconButton(onClick = onCloseClick) { IconButton(onClick = onCloseClick) {
Icon(imageVector = AppIcons.Close, contentDescription = stringResource(R.string.label_close_selection_mode)) Icon(
} imageVector = AppIcons.Close,
Spacer(modifier = Modifier.width(8.dp)) contentDescription = stringResource(R.string.label_close_selection_mode)
Text(
text = stringResource(R.string.d_selected, selectionCount),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
) )
} }
Row {
// 2. Title Text (Gets weight to prevent pushing icons off-screen)
Text(
text = stringResource(R.string.d_selected, selectionCount),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// 3. Action Icons Group
Row(verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = onSelectAllClick) { IconButton(onClick = onSelectAllClick) {
Icon( Icon(
imageVector = AppIcons.SelectAll, imageVector = AppIcons.SelectAll,
@@ -320,6 +332,7 @@ fun AllCardsView(
vocabularyItems: List<VocabularyItem>, vocabularyItems: List<VocabularyItem>,
allLanguages: List<Language>, allLanguages: List<Language>,
selection: Set<Long>, selection: Set<Long>,
stageMapping: Map<Int, VocabularyStage> = emptyMap(),
onItemClick: (VocabularyItem) -> Unit, onItemClick: (VocabularyItem) -> Unit,
onItemLongClick: (VocabularyItem) -> Unit, onItemLongClick: (VocabularyItem) -> Unit,
onDeleteClick: (VocabularyItem) -> Unit, onDeleteClick: (VocabularyItem) -> Unit,
@@ -350,7 +363,7 @@ fun AllCardsView(
} else { } else {
LazyColumn( LazyColumn(
state = listState, state = listState,
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 100.dp) contentPadding = PaddingValues(bottom = 100.dp)
) { ) {
@@ -359,10 +372,12 @@ fun AllCardsView(
key = { it.id } key = { it.id }
) { item -> ) { item ->
val isSelected = selection.contains(item.id.toLong()) val isSelected = selection.contains(item.id.toLong())
val stage = stageMapping[item.id] ?: VocabularyStage.NEW
VocabularyCard( VocabularyCard(
item = item, item = item,
allLanguages = allLanguages, allLanguages = allLanguages,
isSelected = isSelected, isSelected = isSelected,
stage = stage,
onItemClick = { onItemClick(item) }, onItemClick = { onItemClick(item) },
onItemLongClick = { onItemLongClick(item) }, onItemLongClick = { onItemLongClick(item) },
onDeleteClick = { onDeleteClick(item) } onDeleteClick = { onDeleteClick(item) }
@@ -372,14 +387,12 @@ fun AllCardsView(
} }
} }
/**
* Individual vocabulary card component
*/
@Composable @Composable
fun VocabularyCard( fun VocabularyCard(
item: VocabularyItem, item: VocabularyItem,
allLanguages: List<Language>, allLanguages: List<Language>,
isSelected: Boolean, isSelected: Boolean,
stage: VocabularyStage = VocabularyStage.NEW,
onItemClick: () -> Unit, onItemClick: () -> Unit,
onItemLongClick: () -> Unit, onItemLongClick: () -> Unit,
onDeleteClick: () -> Unit, onDeleteClick: () -> Unit,
@@ -392,14 +405,15 @@ fun VocabularyCard(
Card( Card(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(12.dp)) // Slightly rounder for a modern look
.combinedClickable( .combinedClickable(
onClick = onItemClick, onClick = onItemClick,
onLongClick = onItemLongClick onLongClick = onItemLongClick
), ),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainer, containerColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.surfaceContainer // Fixed the contentColor bug here:
contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface
), ),
border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null
) { ) {
@@ -410,50 +424,46 @@ fun VocabularyCard(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Column(modifier = Modifier.weight(1f)) { Column(
modifier = Modifier
.weight(1f)
.padding(end = 12.dp) // Ensures text doesn't bleed into the trailing icon
) {
// Top row: First word + Language Pill // Top row: First word + Language Pill
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
text = insertBreakOpportunities(item.wordFirst), text = insertBreakOpportunities(item.wordFirst),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface,
// This modifier allows the text to wrap without squishing the pill
modifier = Modifier.weight(1f, fill = false)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Surface( LanguagePill(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f), text = langFirst,
shape = RoundedCornerShape(4.dp) backgroundColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f),
) { textColor = MaterialTheme.colorScheme.onSurfaceVariant
Text( )
text = langFirst,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(6.dp)) // Slightly more breathing room
// Bottom row: Second word + Language Pill // Bottom row: Second word + Language Pill
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
text = insertBreakOpportunities(item.wordSecond), text = insertBreakOpportunities(item.wordSecond),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant,
// Applied to the second text as well for consistency
modifier = Modifier.weight(1f, fill = false)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Surface( LanguagePill(
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f), text = langSecond,
shape = RoundedCornerShape(8.dp) backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f),
) { textColor = MaterialTheme.colorScheme.primary
Text( )
text = langSecond,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
)
}
} }
} }
@@ -464,18 +474,124 @@ fun VocabularyCard(
tint = MaterialTheme.colorScheme.primary tint = MaterialTheme.colorScheme.primary
) )
} else { } else {
IconButton(onClick = { /* Options menu could go here */ }) { // Stage indicator showing the vocabulary item's learning stage
Icon( StageIndicator(stage = stage)
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.cd_options),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
} }
} }
} }
} }
@Composable
fun StageIndicator(
stage: VocabularyStage,
modifier: Modifier = Modifier
) {
// Convert VocabularyStage to a step number (0-6)
val step = when (stage) {
VocabularyStage.NEW -> 0
VocabularyStage.STAGE_1 -> 1
VocabularyStage.STAGE_2 -> 2
VocabularyStage.STAGE_3 -> 3
VocabularyStage.STAGE_4 -> 4
VocabularyStage.STAGE_5 -> 5
VocabularyStage.LEARNED -> 6
}
// 1. Calculate how full the ring should be (0.0 to 1.0)
val maxSteps = 6f
val progress = step / maxSteps
// 2. Determine the ring color based on the stage
val indicatorColor = when (stage) {
VocabularyStage.NEW -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)
VocabularyStage.STAGE_1, VocabularyStage.STAGE_2 -> Color(0xFFE57373) // Soft Red
VocabularyStage.STAGE_3 -> Color(0xFFFFB74D) // Soft Orange
VocabularyStage.STAGE_4 -> Color(0xFFFFD54F) // Soft Yellow
VocabularyStage.STAGE_5 -> Color(0xFFAED581) // Light Green
VocabularyStage.LEARNED -> Color(0xFF81C784) // Solid Green
}
Box(
contentAlignment = Alignment.Center,
modifier = modifier.size(36.dp) // Keeps it neatly sized within the row
) {
// The background track (empty ring)
CircularProgressIndicator(
progress = { 1f },
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.08f),
strokeWidth = 3.dp,
modifier = Modifier.fillMaxSize()
)
// The colored progress ring
if (stage != VocabularyStage.NEW) {
CircularProgressIndicator(
progress = { progress },
color = indicatorColor,
strokeWidth = 3.dp,
strokeCap = StrokeCap.Round, // Gives the progress bar nice rounded ends
modifier = Modifier.fillMaxSize()
)
}
// The center content (Number or Icon)
when (stage) {
VocabularyStage.NEW -> {
// An empty dot or small icon to denote it's untouched
Icon(
imageVector = Icons.Rounded.Star, // Or any generic 'new' icon
contentDescription = "New Word",
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f),
modifier = Modifier.size(16.dp)
)
}
VocabularyStage.LEARNED -> {
// A checkmark for mastery
Icon(
imageVector = Icons.Rounded.Check,
contentDescription = "Learned",
tint = indicatorColor,
modifier = Modifier.size(20.dp)
)
}
else -> {
// Display the actual level number (1 through 5)
Text(
text = step.toString(),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
}
}
}
}
// Extracted for consistency and cleaner code
@Composable
private fun LanguagePill(
text: String,
backgroundColor: Color,
textColor: Color
) {
if (text.isNotEmpty()) {
Surface(
color = backgroundColor,
shape = RoundedCornerShape(6.dp) // Consistent corner rounding for all pills
) {
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
color = textColor,
// Guaranteed to never wrap awkwardly
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
}
/** /**
* Grid view of categories * Grid view of categories
*/ */
@@ -515,13 +631,11 @@ fun CategoryCard(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Card( AppCard(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.height(140.dp) .height(140.dp)
.clickable(onClick = onClick), .clickable(onClick = onClick),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -709,6 +823,7 @@ fun VocabularyCardPreview() {
), ),
allLanguages = emptyList(), allLanguages = emptyList(),
isSelected = false, isSelected = false,
stage = VocabularyStage.NEW,
onItemClick = {}, onItemClick = {},
onItemLongClick = {}, onItemLongClick = {},
onDeleteClick = {} onDeleteClick = {}
@@ -716,6 +831,154 @@ fun VocabularyCardPreview() {
} }
} }
@Suppress("HardCodedStringLiteral")
@Preview(showBackground = true)
@Composable
fun VocabularyCardStage1Preview() {
MaterialTheme {
VocabularyCard(
item = VocabularyItem(
id = 2,
wordFirst = "Goodbye",
wordSecond = "Adiós",
languageFirstId = 1,
languageSecondId = 2,
createdAt = null,
features = null,
zipfFrequencyFirst = null,
zipfFrequencySecond = null
),
allLanguages = emptyList(),
isSelected = false,
stage = VocabularyStage.STAGE_1,
onItemClick = {},
onItemLongClick = {},
onDeleteClick = {}
)
}
}
@Suppress("HardCodedStringLiteral")
@Preview(showBackground = true)
@Composable
fun VocabularyCardStage3Preview() {
MaterialTheme {
VocabularyCard(
item = VocabularyItem(
id = 3,
wordFirst = "Thank you",
wordSecond = "Gracias",
languageFirstId = 1,
languageSecondId = 2,
createdAt = null,
features = null,
zipfFrequencyFirst = null,
zipfFrequencySecond = null
),
allLanguages = emptyList(),
isSelected = false,
stage = VocabularyStage.STAGE_3,
onItemClick = {},
onItemLongClick = {},
onDeleteClick = {}
)
}
}
@Suppress("HardCodedStringLiteral")
@Preview(showBackground = true)
@Composable
fun VocabularyCardStage5Preview() {
MaterialTheme {
VocabularyCard(
item = VocabularyItem(
id = 4,
wordFirst = "Please",
wordSecond = "Por favor",
languageFirstId = 1,
languageSecondId = 2,
createdAt = null,
features = null,
zipfFrequencyFirst = null,
zipfFrequencySecond = null
),
allLanguages = emptyList(),
isSelected = false,
stage = VocabularyStage.STAGE_5,
onItemClick = {},
onItemLongClick = {},
onDeleteClick = {}
)
}
}
@Suppress("HardCodedStringLiteral")
@Preview(showBackground = true)
@Composable
fun VocabularyCardLearnedPreview() {
MaterialTheme {
VocabularyCard(
item = VocabularyItem(
id = 5,
wordFirst = "Yes",
wordSecond = "",
languageFirstId = 1,
languageSecondId = 2,
createdAt = null,
features = null,
zipfFrequencyFirst = null,
zipfFrequencySecond = null
),
allLanguages = emptyList(),
isSelected = false,
stage = VocabularyStage.LEARNED,
onItemClick = {},
onItemLongClick = {},
onDeleteClick = {}
)
}
}
@Preview(showBackground = true)
@Composable
fun StageIndicatorNewPreview() {
MaterialTheme {
StageIndicator(stage = VocabularyStage.NEW)
}
}
@Preview(showBackground = true)
@Composable
fun StageIndicatorStage1Preview() {
MaterialTheme {
StageIndicator(stage = VocabularyStage.STAGE_1)
}
}
@Preview(showBackground = true)
@Composable
fun StageIndicatorStage3Preview() {
MaterialTheme {
StageIndicator(stage = VocabularyStage.STAGE_3)
}
}
@Preview(showBackground = true)
@Composable
fun StageIndicatorStage5Preview() {
MaterialTheme {
StageIndicator(stage = VocabularyStage.STAGE_5)
}
}
@Preview(showBackground = true)
@Composable
fun StageIndicatorLearnedPreview() {
MaterialTheme {
StageIndicator(stage = VocabularyStage.LEARNED)
}
}
@Suppress("HardCodedStringLiteral") @Suppress("HardCodedStringLiteral")
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable

View File

@@ -139,6 +139,7 @@ fun LibraryScreen(
} }
val vocabularyItems by vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList()) val vocabularyItems by vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
val stageMapping by vocabularyViewModel.stageMapping.collectAsStateWithLifecycle(initialValue = emptyMap())
// Handle export state // Handle export state
LaunchedEffect(exportState) { LaunchedEffect(exportState) {
@@ -263,6 +264,7 @@ fun LibraryScreen(
vocabularyItems = vocabularyItems, vocabularyItems = vocabularyItems,
allLanguages = allLanguages, allLanguages = allLanguages,
selection = selection, selection = selection,
stageMapping = stageMapping,
listState = lazyListState, listState = lazyListState,
onItemClick = { item -> onItemClick = { item ->
if (isInSelectionMode) { if (isInSelectionMode) {

View File

@@ -82,21 +82,21 @@ fun WeeklyActivityChartWidget(
) )
} }
} else { } else {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
// Reduced horizontal padding to give the chart more space // Reduced horizontal padding to give the chart more space
.padding(vertical = 24.dp, horizontal = 12.dp) .padding(vertical = 24.dp, horizontal = 12.dp)
) { ) {
WeeklyChartLegend() WeeklyChartLegend()
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
InteractiveLineChart(weeklyStats = weeklyStats) InteractiveLineChart(weeklyStats = weeklyStats)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
ChartFooter(weeklyStats = weeklyStats) ChartFooter(weeklyStats = weeklyStats)
}
} }
}
} }
@Composable @Composable
@@ -195,15 +195,15 @@ private fun InteractiveLineChart(weeklyStats: List<WeeklyActivityStat>) {
Offset(i * xSpacing, height - ((stat.answeredRight * animationProgress) / yMax) * height) Offset(i * xSpacing, height - ((stat.answeredRight * animationProgress) / yMax) * height)
} }
// Path 1: Correct (Bottom, Dashed) // Define Paths
val pathCorrect = Path().apply { smoothCurve(pointsCorrect) } val pathCorrect = Path().apply { smoothCurve(pointsCorrect) }
drawPath( val fillPathCorrect = Path().apply {
path = pathCorrect, smoothCurve(pointsCorrect)
color = colorCorrect, lineTo(width, height)
style = Stroke(width = 6f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f)) lineTo(0f, height)
) close()
}
// Path 2: Completed (Top, Solid with Fill)
val pathCompleted = Path().apply { smoothCurve(pointsCompleted) } val pathCompleted = Path().apply { smoothCurve(pointsCompleted) }
val fillPathCompleted = Path().apply { val fillPathCompleted = Path().apply {
smoothCurve(pointsCompleted) smoothCurve(pointsCompleted)
@@ -212,14 +212,32 @@ private fun InteractiveLineChart(weeklyStats: List<WeeklyActivityStat>) {
close() close()
} }
// Draw semi-transparent fills first
drawPath( drawPath(
path = fillPathCompleted, path = fillPathCompleted,
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = listOf(colorCompleted.copy(alpha = 0.3f), Color.Transparent), colors = listOf(colorCompleted.copy(alpha = 0.25f), Color.Transparent),
startY = 0f, startY = 0f,
endY = height endY = height
) )
) )
drawPath(
path = fillPathCorrect,
brush = Brush.verticalGradient(
colors = listOf(colorCorrect.copy(alpha = 0.25f), Color.Transparent),
startY = 0f,
endY = height
)
)
// Draw solid strokes on top of the fills
drawPath(
path = pathCorrect,
color = colorCorrect,
style = Stroke(width = 6f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f))
)
drawPath( drawPath(
path = pathCompleted, path = pathCompleted,
color = colorCompleted, color = colorCompleted,

View File

@@ -669,10 +669,20 @@ private fun PackCard(
onGetClick: () -> Unit, onGetClick: () -> Unit,
onAddToLibraryClick: () -> Unit, onAddToLibraryClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
languageViewModel: LanguageViewModel = hiltViewModel(),
) { ) {
val info = packState.info val info = packState.info
val gradient = gradientForId(info.id) val gradient = gradientForId(info.id)
// Get language names from language IDs
val languageIds = info.languageIds
val firstLanguageName by languageViewModel.getLanguageByIdFlow(languageIds.getOrNull(0)).collectAsState(initial = null)
val secondLanguageName by languageViewModel.getLanguageByIdFlow(languageIds.getOrNull(1)).collectAsState(initial = null)
val languageDisplayText = listOfNotNull(firstLanguageName?.name, secondLanguageName?.name)
.joinToString("")
.ifEmpty { info.category }
Surface( Surface(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@@ -785,7 +795,7 @@ private fun PackCard(
) )
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
Text( Text(
text = info.category, text = languageDisplayText,
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1 maxLines = 1

View File

@@ -164,6 +164,7 @@ fun AllCardsListScreen(
val vocabularyItems: List<VocabularyItem> = itemsToShow.ifEmpty { val vocabularyItems: List<VocabularyItem> = itemsToShow.ifEmpty {
vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList()).value vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList()).value
} }
val stageMapping by vocabularyViewModel.stageMapping.collectAsStateWithLifecycle(initialValue = emptyMap())
// Handle export state // Handle export state
LaunchedEffect(exportState) { LaunchedEffect(exportState) {
@@ -298,6 +299,7 @@ fun AllCardsListScreen(
vocabularyItems = vocabularyItems, vocabularyItems = vocabularyItems,
allLanguages = allLanguages, allLanguages = allLanguages,
selection = selection, selection = selection,
stageMapping = stageMapping,
listState = lazyListState, listState = lazyListState,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()