From 863920143d4af5ec3bd47e649fe674d2d2488d73 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:16:24 +0100 Subject: [PATCH] Refactor the `WeeklyActivityChartWidget` into an interactive smooth line chart and update vocabulary import labels. --- .../eu/gaudian/translator/model/Vocabulary.kt | 2 +- .../gaudian/translator/view/MainActivity.kt | 3 +- .../translator/view/home/HomeScreen.kt | 14 +- .../widgets/WeeklyActivityChartWidget.kt | 456 ++++++++++++++---- .../view/vocabulary/NewWordScreen.kt | 16 +- .../view/vocabulary/card/VocabularyCard.kt | 2 +- .../translator/viewmodel/ProgressViewModel.kt | 2 +- app/src/main/res/values-de-rDE/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values/strings.xml | 3 +- 10 files changed, 374 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/model/Vocabulary.kt b/app/src/main/java/eu/gaudian/translator/model/Vocabulary.kt index cb7375c..12174ac 100644 --- a/app/src/main/java/eu/gaudian/translator/model/Vocabulary.kt +++ b/app/src/main/java/eu/gaudian/translator/model/Vocabulary.kt @@ -59,7 +59,7 @@ data class VocabularyItem( } fun hasFeatures(): Boolean { - return !features.isNullOrBlank() + return !features.isNullOrBlank() && features != "{}" } } diff --git a/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt b/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt index 15d9b8f..0da572e 100644 --- a/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt +++ b/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt @@ -267,7 +267,8 @@ fun TranslatorApp( "new_word", "new_word_review", "vocabulary_detail/{itemId}", - "daily_review" + "daily_review", + "explore_packs" ) || currentRoute?.startsWith("start_exercise") == true || currentRoute?.startsWith("vocabulary_exercise") == true val showBottomNavLabels by settingsViewModel.showBottomNavLabels.collectAsStateWithLifecycle(initialValue = false) diff --git a/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt b/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt index 43a7078..333bc0f 100644 --- a/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt @@ -5,8 +5,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -397,12 +399,16 @@ fun BottomStatsSection( val learnedWords by viewModel.totalWordsCompleted.collectAsState() Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { // Total Words AppCard( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .fillMaxHeight(), onClick = { navController.navigate(Screen.Library.route) } ) { Column(modifier = Modifier.padding(20.dp)) { @@ -415,7 +421,9 @@ fun BottomStatsSection( // Learned AppCard( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .fillMaxHeight(), onClick = { navController.navigate(NavigationRoutes.STATS_LANGUAGE_PROGRESS) } ) { Column(modifier = Modifier.padding(20.dp)) { diff --git a/app/src/main/java/eu/gaudian/translator/view/stats/widgets/WeeklyActivityChartWidget.kt b/app/src/main/java/eu/gaudian/translator/view/stats/widgets/WeeklyActivityChartWidget.kt index 61f7f2e..33e47ef 100644 --- a/app/src/main/java/eu/gaudian/translator/view/stats/widgets/WeeklyActivityChartWidget.kt +++ b/app/src/main/java/eu/gaudian/translator/view/stats/widgets/WeeklyActivityChartWidget.kt @@ -4,14 +4,15 @@ package eu.gaudian.translator.view.stats.widgets import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.Canvas import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -31,19 +32,31 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.drawText +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import eu.gaudian.translator.R import eu.gaudian.translator.ui.theme.ThemePreviews -import eu.gaudian.translator.ui.theme.semanticColors import eu.gaudian.translator.viewmodel.WeeklyActivityStat import kotlinx.coroutines.delay +import kotlin.math.roundToInt /** - * A widget that displays weekly activity statistics in a visually appealing bar chart. - * It's designed to be consistent with the app's modern, floating UI style. + * A widget that displays weekly activity statistics in a visually appealing smooth line chart. + * It's designed to be consistent with the app's modern UI style using the theme's colors. * * @param weeklyStats A list of [WeeklyActivityStat] for the last 7 days. */ @@ -51,20 +64,15 @@ import kotlinx.coroutines.delay fun WeeklyActivityChartWidget( weeklyStats: List ) { - val maxValue = remember(weeklyStats) { - (weeklyStats.flatMap { listOf(it.newlyAdded, it.completed, it.answeredRight) }.maxOrNull() ?: 0).let { - if (it < 10) 10 else ((it / 5) + 1) * 5 - } - } - val hasNoData = remember(weeklyStats) { - weeklyStats.all { it.newlyAdded == 0 && it.completed == 0 && it.answeredRight == 0 } + weeklyStats.all { it.completed == 0 && it.answeredRight == 0 } } if (hasNoData) { Box( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .padding(16.dp), contentAlignment = Alignment.Center ) { Text( @@ -74,63 +82,278 @@ fun WeeklyActivityChartWidget( ) } } else { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) { - WeeklyChartLegend() - Spacer(modifier = Modifier.height(16.dp)) - - Row( + Column( modifier = Modifier .fillMaxWidth() - .height(220.dp), - verticalAlignment = Alignment.Bottom + // Reduced horizontal padding to give the chart more space + .padding(vertical = 24.dp, horizontal = 12.dp) ) { - // Y-Axis Labels - Column( - modifier = Modifier - .fillMaxHeight() - .padding(end = 8.dp), - verticalArrangement = Arrangement.SpaceBetween, - horizontalAlignment = Alignment.End - ) { - Text(maxValue.toString(), style = MaterialTheme.typography.labelSmall) - Text((maxValue / 2).toString(), style = MaterialTheme.typography.labelSmall) - Text("0", style = MaterialTheme.typography.labelSmall) - } + WeeklyChartLegend() + Spacer(modifier = Modifier.height(24.dp)) - // Chart Bars - Row( - modifier = Modifier - .weight(1f) - .fillMaxHeight(), - horizontalArrangement = Arrangement.SpaceAround, - verticalAlignment = Alignment.Bottom - ) { - weeklyStats.forEach { stat -> - Column( - modifier = Modifier.weight(1f), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Bottom - ) { - Row( - verticalAlignment = Alignment.Bottom, - horizontalArrangement = Arrangement.spacedBy(2.dp), - modifier = Modifier - .weight(1f) - .fillMaxWidth(0.8f) - ) { - Bar(value = stat.newlyAdded, maxValue = maxValue, color = MaterialTheme.semanticColors.stageGradient1) - Bar(value = stat.completed, maxValue = maxValue, color = MaterialTheme.semanticColors.stageGradient3) - Bar(value = stat.answeredRight, maxValue = maxValue, color = MaterialTheme.semanticColors.stageGradient5) + InteractiveLineChart(weeklyStats = weeklyStats) + + Spacer(modifier = Modifier.height(24.dp)) + ChartFooter(weeklyStats = weeklyStats) + } + } +} + +@Composable +private fun InteractiveLineChart(weeklyStats: List) { + var selectedIndex by remember { mutableStateOf(3) } // Default selection + val textMeasurer = rememberTextMeasurer() + + val colorCompleted = MaterialTheme.colorScheme.primary + val colorCorrect = MaterialTheme.colorScheme.tertiary + + val gridColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f) + val tooltipLineColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) + val dotCenterColor = MaterialTheme.colorScheme.surfaceVariant + val tooltipBgColor = MaterialTheme.colorScheme.inverseSurface + val tooltipTextColor = MaterialTheme.colorScheme.inverseOnSurface + + var startAnimation by remember { mutableStateOf(false) } + val animationProgress by animateFloatAsState( + targetValue = if (startAnimation) 1f else 0f, + animationSpec = tween(durationMillis = 1000), + label = "chartAnimation" + ) + + LaunchedEffect(Unit) { + delay(100) + startAnimation = true + } + + val yAxisMax = remember(weeklyStats) { + val max = weeklyStats.flatMap { listOf(it.completed, it.answeredRight) }.maxOrNull() ?: 0 + if (max < 10) 10 else ((max / 10) + 1) * 10 + } + val yMax = yAxisMax.toFloat() + + Row(modifier = Modifier.fillMaxWidth()) { + // Left Side: Y-Axis Amounts + Column( + modifier = Modifier + .height(180.dp) + // Reduced end padding to save space + .padding(end = 8.dp, top = 2.dp, bottom = 2.dp), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.End + ) { + Text( + text = yAxisMax.toString(), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = (yAxisMax / 2).toString(), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = "0", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // Right Side: Chart Area + Column(modifier = Modifier.weight(1f)) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(180.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val itemWidth = size.width / (weeklyStats.size - 1).coerceAtLeast(1) + selectedIndex = (offset.x / itemWidth).roundToInt().coerceIn(0, weeklyStats.lastIndex) + } + } + .pointerInput(Unit) { + detectHorizontalDragGestures { change, _ -> + val itemWidth = size.width / (weeklyStats.size - 1).coerceAtLeast(1) + selectedIndex = (change.position.x / itemWidth).roundToInt().coerceIn(0, weeklyStats.lastIndex) + } + } + ) { + Canvas(modifier = Modifier.fillMaxSize()) { + val width = size.width + val height = size.height + val xSpacing = width / (weeklyStats.size - 1).coerceAtLeast(1) + + drawLine(gridColor, Offset(0f, 0f), Offset(width, 0f), strokeWidth = 2f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))) + drawLine(gridColor, Offset(0f, height / 2f), Offset(width, height / 2f), strokeWidth = 2f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))) + drawLine(gridColor, Offset(0f, height), Offset(width, height), strokeWidth = 2f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))) + + if (animationProgress == 0f) return@Canvas + + val pointsCompleted = weeklyStats.mapIndexed { i, stat -> + Offset(i * xSpacing, height - ((stat.completed * animationProgress) / yMax) * height) + } + val pointsCorrect = weeklyStats.mapIndexed { i, stat -> + Offset(i * xSpacing, height - ((stat.answeredRight * animationProgress) / yMax) * height) + } + + // Path 1: Correct (Bottom, Dashed) + val pathCorrect = Path().apply { smoothCurve(pointsCorrect) } + drawPath( + path = pathCorrect, + color = colorCorrect, + style = Stroke(width = 6f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f)) + ) + + // Path 2: Completed (Top, Solid with Fill) + val pathCompleted = Path().apply { smoothCurve(pointsCompleted) } + val fillPathCompleted = Path().apply { + smoothCurve(pointsCompleted) + lineTo(width, height) + lineTo(0f, height) + close() + } + + drawPath( + path = fillPathCompleted, + brush = Brush.verticalGradient( + colors = listOf(colorCompleted.copy(alpha = 0.3f), Color.Transparent), + startY = 0f, + endY = height + ) + ) + drawPath( + path = pathCompleted, + color = colorCompleted, + style = Stroke(width = 8f) + ) + + // Interactive Highlights & Dual Separated Tooltips + selectedIndex?.let { index -> + val stat = weeklyStats[index] + val x = index * xSpacing + val yCompleted = height - ((stat.completed * animationProgress) / yMax) * height + val yCorrect = height - ((stat.answeredRight * animationProgress) / yMax) * height + + // Vertical line marker + drawLine( + color = tooltipLineColor, + start = Offset(x, 0f), + end = Offset(x, height), + strokeWidth = 3f + ) + + // Dots on lines + drawCircle(color = dotCenterColor, radius = 12f, center = Offset(x, yCompleted)) + drawCircle(color = colorCompleted, radius = 7f, center = Offset(x, yCompleted)) + + drawCircle(color = dotCenterColor, radius = 12f, center = Offset(x, yCorrect)) + drawCircle(color = colorCorrect, radius = 7f, center = Offset(x, yCorrect)) + + // Measure text + val textStyle = TextStyle(color = tooltipTextColor, fontWeight = FontWeight.Bold, fontSize = 13.sp) + val textResCompleted = textMeasurer.measure(stat.completed.toString(), textStyle) + val textResCorrect = textMeasurer.measure(stat.answeredRight.toString(), textStyle) + + val dotRadius = 5f + val gap = 6f + val padX = 12f + val padY = 8f + + val w1 = padX * 2 + dotRadius * 2 + gap + textResCompleted.size.width + val h1 = padY * 2 + textResCompleted.size.height + + val w2 = padX * 2 + dotRadius * 2 + gap + textResCorrect.size.width + val h2 = padY * 2 + textResCorrect.size.height + + // Tooltip Overlap Prevention Logic + val completedIsHigher = yCompleted <= yCorrect + var yPosCompleted = if (completedIsHigher) yCompleted - h1 - 12f else yCompleted + 12f + var yPosCorrect = if (completedIsHigher) yCorrect + 12f else yCorrect - h2 - 12f + + // Prevent clipping out of canvas bounds natively first + if (yPosCompleted < 0f && completedIsHigher) yPosCompleted = 0f + if (yPosCorrect < 0f && !completedIsHigher) yPosCorrect = 0f + if (yPosCompleted + h1 > height && !completedIsHigher) yPosCompleted = height - h1 + if (yPosCorrect + h2 > height && completedIsHigher) yPosCorrect = height - h2 + + // Overlap resolution + val topRectY = minOf(yPosCompleted, yPosCorrect) + val topRectH = if (topRectY == yPosCompleted) h1 else h2 + val bottomRectY = maxOf(yPosCompleted, yPosCorrect) + + val gapBetweenTooltips = 8f + if (topRectY + topRectH + gapBetweenTooltips > bottomRectY) { + val midPointY = (yCompleted + yCorrect) / 2f + val adjustedTopY = midPointY - (topRectH + gapBetweenTooltips / 2f) + val adjustedBottomY = midPointY + (gapBetweenTooltips / 2f) + + if (topRectY == yPosCompleted) { + yPosCompleted = adjustedTopY + yPosCorrect = adjustedBottomY + } else { + yPosCorrect = adjustedTopY + yPosCompleted = adjustedBottomY } - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stat.day, - style = MaterialTheme.typography.bodySmall + } + + // Final Canvas Bounds Check post-resolution + val finalMinY = minOf(yPosCompleted, yPosCorrect) + if (finalMinY < 0f) { + yPosCompleted -= finalMinY + yPosCorrect -= finalMinY + } + val finalMaxY = maxOf(yPosCompleted + h1, yPosCorrect + h2) + if (finalMaxY > height) { + val shift = finalMaxY - height + yPosCompleted -= shift + yPosCorrect -= shift + } + + // Draw Completed Tooltip + val t1X = (x - w1 / 2f).coerceIn(0f, width - w1) + drawRoundRect(color = tooltipBgColor, topLeft = Offset(t1X, yPosCompleted), size = Size(w1, h1), cornerRadius = CornerRadius(16f, 16f)) + drawCircle(color = colorCompleted, radius = dotRadius, center = Offset(t1X + padX + dotRadius, yPosCompleted + h1 / 2f)) + drawText(textLayoutResult = textResCompleted, topLeft = Offset(t1X + padX + dotRadius * 2 + gap, yPosCompleted + padY)) + + // Draw Correct Tooltip + val t2X = (x - w2 / 2f).coerceIn(0f, width - w2) + drawRoundRect(color = tooltipBgColor, topLeft = Offset(t2X, yPosCorrect), size = Size(w2, h2), cornerRadius = CornerRadius(16f, 16f)) + drawCircle(color = colorCorrect, radius = dotRadius, center = Offset(t2X + padX + dotRadius, yPosCorrect + h2 / 2f)) + drawText(textLayoutResult = textResCorrect, topLeft = Offset(t2X + padX + dotRadius * 2 + gap, yPosCorrect + padY)) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // X-Axis Labels (Freed from fixed widths, prevented from wrapping) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + weeklyStats.forEachIndexed { index, stat -> + val isSelected = index == selectedIndex + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stat.day.uppercase().take(3) + ".", + color = if (isSelected) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 11.sp, // Slightly smaller to ensure fit across all devices + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + softWrap = false // Prevents the text from splitting into multiple lines + ) + if (isSelected) { + Spacer(modifier = Modifier.height(4.dp)) + Box( + modifier = Modifier + .height(2.dp) + .width(20.dp) + .background(MaterialTheme.colorScheme.primary) ) + } else { + // Invisible spacer to prevent layout jumping when line appears + Spacer(modifier = Modifier.height(6.dp)) } } } @@ -139,41 +362,29 @@ fun WeeklyActivityChartWidget( } } -@Composable -private fun RowScope.Bar(value: Int, maxValue: Int, color: Color) { - var startAnimation by remember { mutableStateOf(false) } - val barHeight by animateFloatAsState( - targetValue = if (startAnimation) value.toFloat() / maxValue.toFloat() else 0f, - animationSpec = tween(durationMillis = 1000), - label = "barHeightAnimation" - ) - - LaunchedEffect(Unit) { - delay(200) // Small delay to ensure the UI is ready before animating - @Suppress("AssignedValueIsNeverRead") - startAnimation = true +private fun Path.smoothCurve(points: List) { + if (points.isEmpty()) return + moveTo(points.first().x, points.first().y) + for (i in 1 until points.size) { + val prev = points[i - 1] + val curr = points[i] + val controlX = (prev.x + curr.x) / 2f + cubicTo( + controlX, prev.y, + controlX, curr.y, + curr.x, curr.y + ) } - - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight(barHeight) - .clip(RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp)) - .background(color) - ) } @Composable private fun WeeklyChartLegend() { Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - LegendItem(color = MaterialTheme.semanticColors.stageGradient1, label = stringResource(R.string.label_added)) - LegendItem(color = MaterialTheme.semanticColors.stageGradient3, label = stringResource(R.string.label_completed)) - LegendItem(color = MaterialTheme.semanticColors.stageGradient5, label = stringResource(R.string.label_correct)) + LegendItem(color = MaterialTheme.colorScheme.primary, label = stringResource(R.string.label_completed).uppercase()) + LegendItem(color = MaterialTheme.colorScheme.tertiary, label = stringResource(R.string.label_correct).uppercase()) } } @@ -185,8 +396,51 @@ private fun LegendItem(color: Color, label: String) { .size(10.dp) .background(color, shape = CircleShape) ) - Spacer(modifier = Modifier.width(6.dp)) - Text(text = label, style = MaterialTheme.typography.labelMedium, fontSize = 12.sp) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = label, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontWeight = FontWeight.Bold, + fontSize = 11.sp, + letterSpacing = 0.5.sp + ) + } +} + +@Composable +private fun ChartFooter(weeklyStats: List) { + val bestDay = remember(weeklyStats) { + weeklyStats.maxByOrNull { it.completed + it.answeredRight }?.day?.uppercase() ?: "" + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Melhor Dia:", + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 13.sp + ) + Spacer(modifier = Modifier.width(8.dp)) + Box( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)) + .padding(horizontal = 10.dp, vertical = 6.dp) + ) { + Text( + text = bestDay, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold, + fontSize = 12.sp + ) + } + } + + } } @@ -194,13 +448,13 @@ private fun LegendItem(color: Color, label: String) { @Composable fun WeeklyActivityChartWidgetPreview() { val sampleStats = listOf( - WeeklyActivityStat("Mon", 10, 5, 20), - WeeklyActivityStat("Tue", 12, 3, 15), - WeeklyActivityStat("Wed", 8, 8, 25), - WeeklyActivityStat("Thu", 15, 2, 18), - WeeklyActivityStat("Fri", 5, 10, 30), - WeeklyActivityStat("Sat", 7, 6, 22), - WeeklyActivityStat("Sun", 9, 4, 17) + WeeklyActivityStat("Seg", 30, 15, 10), + WeeklyActivityStat("Ter", 45, 20, 12), + WeeklyActivityStat("Qua", 80, 25, 15), + WeeklyActivityStat("Qui", 84, 35, 18), + WeeklyActivityStat("Sex", 50, 40, 22), + WeeklyActivityStat("Sáb", 70, 30, 20), + WeeklyActivityStat("Dom", 60, 25, 18) ) Box( modifier = Modifier diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt index be3e163..3f0a80c 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt @@ -57,7 +57,6 @@ import eu.gaudian.translator.utils.StatusMessageId import eu.gaudian.translator.utils.StatusMessageService import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.NavigationRoutes -import eu.gaudian.translator.view.composable.AppActionCard import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppIconContainer @@ -67,7 +66,6 @@ import eu.gaudian.translator.view.composable.AppOutlinedTextField import eu.gaudian.translator.view.composable.AppSlider import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.InspiringSearchField -import eu.gaudian.translator.view.composable.LabeledSection import eu.gaudian.translator.view.composable.Screen import eu.gaudian.translator.view.composable.SourceLanguageDropdown import eu.gaudian.translator.view.composable.TargetLanguageDropdown @@ -700,12 +698,6 @@ fun ExplorePacksProminentCard( color = MaterialTheme.colorScheme.onSurfaceVariant ) } - Icon( - imageVector = Icons.Default.DriveFolderUpload, - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.primary - ) } } } @@ -734,7 +726,7 @@ fun ImportCsvCard( Spacer(modifier = Modifier.width(16.dp)) Column(modifier = Modifier.weight(1f)) { Text( - text = stringResource(R.string.label_import_csv), + text = stringResource(R.string.label_import_csv_or_lists), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) @@ -745,12 +737,6 @@ fun ImportCsvCard( color = MaterialTheme.colorScheme.onSurfaceVariant ) } - Icon( - imageVector = Icons.Default.DriveFolderUpload, - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.primary - ) } } } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt index 132d92f..43cd51a 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt @@ -435,7 +435,7 @@ private fun VocabularyCardContent( onMoveToStageClick = onMoveToStageClick, onDeleteClick = onDeleteClick, - showAnalyzeGrammarButton = item.features.isNullOrBlank(), + showAnalyzeGrammarButton = !item.hasFeatures(), onAnalyzeGrammarClick = { vocabularyViewModel.fetchAndApplyGrammaticalDetailsForList(listOf(item)) }, diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt index b37f1b1..a3c6052 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt @@ -203,7 +203,7 @@ class ProgressViewModel @Inject constructor( // Calculate localized day name val calendarDay = ((date.dayOfWeek.ordinal + 1) % 7) + 1 - val localizedDay = DateFormatSymbols.getInstance(Locale.getDefault()).shortWeekdays[calendarDay].uppercase() + val localizedDay = DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[calendarDay] WeeklyActivityStat( // 3. Get the actual day name from the date and take the first 3 letters. diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 36b5bf1..40e2b26 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -902,7 +902,6 @@ Extrahiere ein neues Wort in deine Liste Nach oben scrollen Einstellungen - CSV importieren KI-Generator Neue Wörter Kürzlich hinzugefügt diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2989b56..be75d99 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -898,7 +898,6 @@ Extrair uma nova palavra para a sua lista Rolar para o topo Configurações - Importar CSV Gerador de IA Novas Palavras Adicionados recentemente diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1bbd27..fdf9b5d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,7 +77,7 @@ %1$d words need attention Expand your vocabulary - Discover curated vocabulary packs + Discover lists to download Import words from CSV or lists Description @@ -1168,4 +1168,5 @@ About Vocabulary Packs + Import Lists or CSV