Implement a StageIndicator to visualize vocabulary learning progress and refine the VocabularyCard UI.
This commit is contained in:
@@ -101,7 +101,7 @@ fun DailyReviewScreen(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
contentPadding = PaddingValues(16.dp)
|
||||
) {
|
||||
items(
|
||||
|
||||
@@ -33,11 +33,13 @@ import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.AddCircleOutline
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
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.Tune
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.VocabularyCategory
|
||||
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.insertBreakOpportunities
|
||||
|
||||
@@ -127,22 +132,29 @@ fun SelectionTopBar(
|
||||
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
// 1. Close Button
|
||||
IconButton(onClick = onCloseClick) {
|
||||
Icon(imageVector = AppIcons.Close, contentDescription = stringResource(R.string.label_close_selection_mode))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.d_selected, selectionCount),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
Icon(
|
||||
imageVector = AppIcons.Close,
|
||||
contentDescription = stringResource(R.string.label_close_selection_mode)
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
Icon(
|
||||
imageVector = AppIcons.SelectAll,
|
||||
@@ -320,6 +332,7 @@ fun AllCardsView(
|
||||
vocabularyItems: List<VocabularyItem>,
|
||||
allLanguages: List<Language>,
|
||||
selection: Set<Long>,
|
||||
stageMapping: Map<Int, VocabularyStage> = emptyMap(),
|
||||
onItemClick: (VocabularyItem) -> Unit,
|
||||
onItemLongClick: (VocabularyItem) -> Unit,
|
||||
onDeleteClick: (VocabularyItem) -> Unit,
|
||||
@@ -350,7 +363,7 @@ fun AllCardsView(
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 100.dp)
|
||||
) {
|
||||
@@ -359,10 +372,12 @@ fun AllCardsView(
|
||||
key = { it.id }
|
||||
) { item ->
|
||||
val isSelected = selection.contains(item.id.toLong())
|
||||
val stage = stageMapping[item.id] ?: VocabularyStage.NEW
|
||||
VocabularyCard(
|
||||
item = item,
|
||||
allLanguages = allLanguages,
|
||||
isSelected = isSelected,
|
||||
stage = stage,
|
||||
onItemClick = { onItemClick(item) },
|
||||
onItemLongClick = { onItemLongClick(item) },
|
||||
onDeleteClick = { onDeleteClick(item) }
|
||||
@@ -372,14 +387,12 @@ fun AllCardsView(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual vocabulary card component
|
||||
*/
|
||||
@Composable
|
||||
fun VocabularyCard(
|
||||
item: VocabularyItem,
|
||||
allLanguages: List<Language>,
|
||||
isSelected: Boolean,
|
||||
stage: VocabularyStage = VocabularyStage.NEW,
|
||||
onItemClick: () -> Unit,
|
||||
onItemLongClick: () -> Unit,
|
||||
onDeleteClick: () -> Unit,
|
||||
@@ -392,14 +405,15 @@ fun VocabularyCard(
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clip(RoundedCornerShape(12.dp)) // Slightly rounder for a modern look
|
||||
.combinedClickable(
|
||||
onClick = onItemClick,
|
||||
onLongClick = onItemLongClick
|
||||
),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainer,
|
||||
contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.surfaceContainer
|
||||
containerColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||
// 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
|
||||
) {
|
||||
@@ -410,52 +424,48 @@ fun VocabularyCard(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
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
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = insertBreakOpportunities(item.wordFirst),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
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))
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
) {
|
||||
Text(
|
||||
LanguagePill(
|
||||
text = langFirst,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
backgroundColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f),
|
||||
textColor = 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
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = insertBreakOpportunities(item.wordSecond),
|
||||
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))
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(
|
||||
LanguagePill(
|
||||
text = langSecond,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f),
|
||||
textColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
@@ -464,15 +474,121 @@ fun VocabularyCard(
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
} else {
|
||||
IconButton(onClick = { /* Options menu could go here */ }) {
|
||||
// Stage indicator showing the vocabulary item's learning stage
|
||||
StageIndicator(stage = stage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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.Default.MoreVert,
|
||||
contentDescription = stringResource(R.string.cd_options),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,13 +631,11 @@ fun CategoryCard(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
AppCard(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(140.dp)
|
||||
.clickable(onClick = onClick),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -709,6 +823,7 @@ fun VocabularyCardPreview() {
|
||||
),
|
||||
allLanguages = emptyList(),
|
||||
isSelected = false,
|
||||
stage = VocabularyStage.NEW,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
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 = "Sí",
|
||||
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")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
|
||||
@@ -139,6 +139,7 @@ fun LibraryScreen(
|
||||
}
|
||||
|
||||
val vocabularyItems by vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val stageMapping by vocabularyViewModel.stageMapping.collectAsStateWithLifecycle(initialValue = emptyMap())
|
||||
|
||||
// Handle export state
|
||||
LaunchedEffect(exportState) {
|
||||
@@ -263,6 +264,7 @@ fun LibraryScreen(
|
||||
vocabularyItems = vocabularyItems,
|
||||
allLanguages = allLanguages,
|
||||
selection = selection,
|
||||
stageMapping = stageMapping,
|
||||
listState = lazyListState,
|
||||
onItemClick = { item ->
|
||||
if (isInSelectionMode) {
|
||||
|
||||
@@ -195,15 +195,15 @@ private fun InteractiveLineChart(weeklyStats: List<WeeklyActivityStat>) {
|
||||
Offset(i * xSpacing, height - ((stat.answeredRight * animationProgress) / yMax) * height)
|
||||
}
|
||||
|
||||
// Path 1: Correct (Bottom, Dashed)
|
||||
// Define Paths
|
||||
val pathCorrect = Path().apply { smoothCurve(pointsCorrect) }
|
||||
drawPath(
|
||||
path = pathCorrect,
|
||||
color = colorCorrect,
|
||||
style = Stroke(width = 6f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f))
|
||||
)
|
||||
val fillPathCorrect = Path().apply {
|
||||
smoothCurve(pointsCorrect)
|
||||
lineTo(width, height)
|
||||
lineTo(0f, height)
|
||||
close()
|
||||
}
|
||||
|
||||
// Path 2: Completed (Top, Solid with Fill)
|
||||
val pathCompleted = Path().apply { smoothCurve(pointsCompleted) }
|
||||
val fillPathCompleted = Path().apply {
|
||||
smoothCurve(pointsCompleted)
|
||||
@@ -212,14 +212,32 @@ private fun InteractiveLineChart(weeklyStats: List<WeeklyActivityStat>) {
|
||||
close()
|
||||
}
|
||||
|
||||
// Draw semi-transparent fills first
|
||||
drawPath(
|
||||
path = fillPathCompleted,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(colorCompleted.copy(alpha = 0.3f), Color.Transparent),
|
||||
colors = listOf(colorCompleted.copy(alpha = 0.25f), Color.Transparent),
|
||||
startY = 0f,
|
||||
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(
|
||||
path = pathCompleted,
|
||||
color = colorCompleted,
|
||||
|
||||
@@ -669,10 +669,20 @@ private fun PackCard(
|
||||
onGetClick: () -> Unit,
|
||||
onAddToLibraryClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
languageViewModel: LanguageViewModel = hiltViewModel(),
|
||||
) {
|
||||
val info = packState.info
|
||||
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(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -785,7 +795,7 @@ private fun PackCard(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = info.category,
|
||||
text = languageDisplayText,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1
|
||||
|
||||
@@ -164,6 +164,7 @@ fun AllCardsListScreen(
|
||||
val vocabularyItems: List<VocabularyItem> = itemsToShow.ifEmpty {
|
||||
vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList()).value
|
||||
}
|
||||
val stageMapping by vocabularyViewModel.stageMapping.collectAsStateWithLifecycle(initialValue = emptyMap())
|
||||
|
||||
// Handle export state
|
||||
LaunchedEffect(exportState) {
|
||||
@@ -298,6 +299,7 @@ fun AllCardsListScreen(
|
||||
vocabularyItems = vocabularyItems,
|
||||
allLanguages = allLanguages,
|
||||
selection = selection,
|
||||
stageMapping = stageMapping,
|
||||
listState = lazyListState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
Reference in New Issue
Block a user